Djangoテストコードセグメントでn+1個の問題を検索
ORMを使用するフレームワークでもN+1の問題が発生します.この記事では,テストコードでN+1問題を予測し修正する方法を共有する.
各フレームワークでn+1の問題を検出するパッケージは、優れた開発者によって共有されています.張高はhttps://github.com/jmcarp/nplusoneセットあり、利用しようとしています.
まずパッケージをインストールします.インストール方法はパッケージのreadmeを参照してください.
次の例示的なコードは、リソースに基づいて簡単なrest apiを記述するサンプルコードである.
2つのモデルがあれば、Article▼Comment関係は1対多です.
->失敗の原因は、ArticleSerializer()にサブモデルCommentがプリフェッチされていないためです.
既存のArticleViewのクエリーセットにアノテーションモデルを追加します.
=================================================================================== 1 passed in 7.85s ===================================================================================
各フレームワークでn+1の問題を検出するパッケージは、優れた開発者によって共有されています.張高はhttps://github.com/jmcarp/nplusoneセットあり、利用しようとしています.
まずパッケージをインストールします.インストール方法はパッケージのreadmeを参照してください.
次の例示的なコードは、リソースに基づいて簡単なrest apiを記述するサンプルコードである.
2つのモデルがあれば、Article▼Comment関係は1対多です.
Model
from django.db import models
# Create your models here.
class Article(models.Model):
# user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=144)
subtitle = models.CharField(max_length=144, blank=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '[{}] {}'.format(self.title, self.subtitle)
class Comment(models.Model):
article = models.ForeignKey(to=Article, related_name="comments", on_delete=models.CASCADE)
content = models.TextField()
一対の多関係モデル.Article - CommentView
from django.shortcuts import render
# Create your views here.
from requests import Response
from rest_framework import viewsets
from .serializers import ArticleSerializer, CommentSerializer
from .models import Article, Comment
from rest_framework import permissions
class ArticleView(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = ()
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def get_serializer(self, *args, **kwargs):
return super().get_serializer(*args, **kwargs)
def get_serializer_context(self):
context = super(ArticleView, self).get_serializer_context()
return context
class CommentView(viewsets.ModelViewSet):
serializer_class = CommentSerializer
permission_classes = ()
def get_queryset(self):
return Comment.objects.filter(article=self.kwargs['article_pk'])
def perform_create(self, serializer):
serializer.save()
リソースをrest形式で返します.Serializer
from rest_framework import serializers
from .models import Article, Comment
from django.contrib.auth.models import User
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = (
'id',
'article',
'content'
)
class ArticleSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = (
'id',
'title',
'subtitle',
'content',
'created_at',
'comments',
)
read_only_fields = ('created_at',)
ArticleSerializerからサブアノテーションモデルをインポート(重要、プリフェッチなし)Urls
from django.urls import path, include
from rest_framework_nested import routers
from .views import ArticleView, CommentView
router = routers.SimpleRouter()
router.register(r'articles', ArticleView, basename='articles')
articles_router = routers.NestedSimpleRouter(router, r'articles', lookup='article')
articles_router.register(r'comments', CommentView, basename='article-comments')
urlpatterns = [
path('', include(router.urls)),
path('', include(articles_router.urls))
]
TestCode
from unittest import mock
import pytest
from django.conf import settings
from django.test import Client
from api.models import Article, Comment
@pytest.fixture
def logger(monkeypatch):
mock_logger = mock.Mock()
monkeypatch.setattr(settings, 'NPLUSONE_LOGGER', mock_logger)
return mock_logger
def check_nplusone_problem(logger):
if len(logger.log.call_args_list) != 0: # 에러 문자가 포함되어 있다.
args = logger.log.call_args[0]
assert ("Potential n+1 query detected on" in args[1]) is False # prefetch 가 필요한 경우
assert ("Potential unnecessary eager load detected on" in args[1]) is False # 쓸데없이 prefetch를 한 경우
assert not logger.log.called # 정상적인 경우 호출 하면 안 된다.
def create_mock_article() -> Article:
"""
Article 목 데이터를 생성한다.
:return:
"""
# Given
name: str = "TEST_title"
description: str = "description"
# When
article: Article = Article()
article.title = name
article.subtitle = 'subtitle'
article.content = description
article.save()
comment: Comment = Comment()
comment.content = "AAA"
comment.article = article
comment.save()
return article
@pytest.mark.django_db
def test_route_article_GET(logger) -> None:
"""
AlbumLV 뷰의 url method GET 테스트 할 수 있다.
:return:
"""
# Given
create_mock_article()
create_mock_article()
create_mock_article()
client = Client()
# When
response = client.get('/api/articles/')
# Then
print(response.data)
check_nplusone_problem(logger)
assert response.status_code == 200
テストコードの実行に失敗しました
->失敗の原因は、ArticleSerializer()にサブモデルCommentがプリフェッチされていないためです.
失敗事例の修正
既存のArticleViewのクエリーセットにアノテーションモデルを追加します.
class ArticleView(viewsets.ModelViewSet):
queryset = Article.objects.prefetch('comments').all()
テストコード実行結果成功~
=================================================================================== 1 passed in 7.85s ===================================================================================
Reference
この問題について(Djangoテストコードセグメントでn+1個の問題を検索), 我々は、より多くの情報をここで見つけました https://velog.io/@kim6515516/Django-테스트코드-단에서-n1-문제-예방하기テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol