怎么让「不许出现 N+1」变成一条自动化测试?assertNumQueries 失败时能看到什么?这篇写 6 个真实测试并运行,把通过与失败的输出都贴出来。
test_atomic_counts_savepoints (lab.tests.QueryCountTests.test_atomic_counts_savepoints) ... ok test_capture_upper_bound (lab.tests.QueryCountTests.test_capture_upper_bound) ... ok test_fail_n_plus_one (lab.tests.QueryCountTests.test_fail_n_plus_one) ... FAIL test_fixed_with_select_related (lab.tests.QueryCountTests.test_fixed_with_select_related) ... ok test_pass_exact (lab.tests.QueryCountTests.test_pass_exact) ... ok test_atomic_counts_begin_commit (lab.tests.TxTests.test_atomic_counts_begin_commit) ... ok Ran 6 tests in 0.004s FAILED (failures=1)
测试文件里五个通过、一个故意写错(assertNumQueries(2) 而实际是 6 次)。
FAIL: test_fail_n_plus_one (lab.tests.QueryCountTests.test_fail_n_plus_one)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<实验目录>", line 12, in test_fail_n_pl
with self.assertNumQueries(2): [p.author.name for p in Post.objects.all()]
~~~~~~~~~~~~~~~~~~~~~^^^
AssertionError: 6 != 2 : 6 queries executed, 2 expected
Captured queries were:
1. SELECT "lab_post"."id", "lab_post"."title", "lab_post"."author_id" FROM "lab_post"
2. SELECT "lab_author"."id", "lab_author"."name" FROM "lab_author" WHERE "lab_author"."id" = 1 LIMIT 21
3. SELECT "lab_author"."id", "lab_author"."name" FROM "lab_author" WHERE "lab_author"."id" = 1 LIMIT 21
4. SELECT "lab_author"."id", "lab_author"."name" FROM "lab_author" WHERE "lab_author"."id" = 1 LIMIT 21
5. SELECT "lab_author"."id", "lab_author"."name" FROM "lab_author" WHERE "lab_author"."id" = 1 LIMIT 21
6. SELECT "lab_author"."id", "lab_author"."name" FROM "lab_author" WHERE "lab_author"."id" = 1 LIMIT 21
----------------------------------------------------------------------这是 assertNumQueries 最有价值的一点:失败信息就是 N+1 的证据。
TestCase 包了一层事务,所以 atomic 变成 SAVEPOINT / RELEASE SAVEPOINT(3 条:SAVEPOINT + INSERT + RELEASE);TransactionTestCase 没有外层事务,atomic 是 BEGIN / COMMIT(同样 3 条)。数字相同,但里面的内容不同。
TestCase : ['SAVEPOINT "s8399224704_x2"', 'INSERT INTO "lab_author" ("name") ', 'RELEASE SAVEPOINT "s8399224704_x2"']
TransactionTest : ['BEGIN', 'INSERT INTO "lab_author" ("name") ', 'COMMIT']因为 TestCase 把每个测试包在事务里,涉及 on_commit(第 45 篇)和 select_for_update(第 46 篇)的测试要注意选对基类。