Метод assertNumQueries

Метод assertNumQueries применяется в тестах Django для проверки того, что выполняется ожидаемое количество SQL-запросов. Он принимает ожидаемое число запросов и вызываемый объект или блок кода, который будет выполнен в контексте подсчёта запросов. Метод полезен для контроля производительности базы данных и предотвращения регрессий.

Синтаксис

pythonself.assertNumQueries(num, func, *args, **kwargs)

Параметры метода:

Также метод может использоваться как контекстный менеджер:

pythonwith self.assertNumQueries(num):
  # блок кода, где подсчитываются запросы

Пример

Рассмотрим простой пример использования метода для проверки количества запросов при получении списка статей:

pythonfrom django.test import TestCase
from myapp.models import Article, Category

class ArticleTest(TestCase):
  @classmethod
  def setUpTestData(cls):
    category = Category.objects.create(name="Technology")
    for i in range(3):
      Article.objects.create(
        title=f"Article {i}",
        category=category
      )
  
  def test_article_queryset(self):
    # Проверяем, что запрос выполняется один раз
    with self.assertNumQueries(1):
      articles = Article.objects.all()
      res = list(articles)

Пример

Пример использования метода для обнаружения проблемы N+1 запросов при обращении к связанным полям:

pythonfrom django.test import TestCase
from myapp.models import Article, Category

class ArticleTest(TestCase):
  @classmethod
  def setUpTestData(cls):
    category = Category.objects.create(name="Technology")
    for i in range(3):
      Article.objects.create(
        title=f"Article {i}",
        category=category
      )
  
  def test_article_without_prefetch(self):
    # Без prefetch_related будет N+1 запрос
    # 1 запрос для статей + 3 запроса для категорий = 4 запроса
    with self.assertNumQueries(4):
      articles = Article.objects.all()
      for article in articles:
        print(article.category.name)
  
  def test_article_with_prefetch(self):
    # С prefetch_related будет всего 2 запроса
    with self.assertNumQueries(2):
      articles = Article.objects.prefetch_related("category")
      for article in articles:
        print(article.category.name)

Пример

Использование метода с функцией, выполняющей сложные запросы и агрегации:

pythonfrom django.test import TestCase
from myapp.models import Article, Comment
from django.db.models import Count

class ArticleTest(TestCase):
  @classmethod
  def setUpTestData(cls):
    article = Article.objects.create(
      title="Test Article",
      status="published"
    )
    for i in range(5):
      Comment.objects.create(
        article=article,
        text=f"Comment {i}",
        is_approved=i % 2 == 0
      )
  
  def test_article_with_annotate(self):
    # Проверяем количество запросов при использовании аннотаций
    def get_articles_with_count():
      res = Article.objects.annotate(
        comment_count=Count("comments", filter=Q(comments__is_approved=True))
      ).values("title", "comment_count")
      return list(res)
    
    # Ожидаем 1 запрос для аннотированной выборки
    with self.assertNumQueries(1):
      res = get_articles_with_count()
      print(res)

Результат выполнения кода:

python[{'title': 'Test Article', 'comment_count': 3}]

Смотрите также

  • метод assertContains,
    который проверяет наличие содержимого в ответе
  • метод assertRedirects,
    который проверяет перенаправления в ответе
  • метод assertTemplateUsed,
    который проверяет использование шаблона
  • метод setUpTestData,
    который создаёт тестовые данные для класса