Django: 무엇을 테스트하고, 왜, 어떻게 하는지.(코드)

이것은 두 부분 시리즈의 계속입니다. 테스트의 이론 부분을 보십시오.

카탈로그

  • Coverage
  • Testing Models
  • Testing Views
  • Testing Forms
  • Next Topics To Cover
  • Conclusion
  • 이 블로그에서 우리는 어떻게 테스트부터 시작하고 어떻게 실현하는지 토론할 것이다.

    뉴스 보도


    우선, 우리는 무엇을 테스트해야 하는지 알아야 한다.앞서 말씀드린 바와 같이 Django의 바닐라 모델이나 Django ORM과python의 내장 함수를 테스트할 필요가 없습니다.
    이것이 바로 범위의 편리함이다. 초보자에게는 우리가 테스트해야 할 기능을 찾을 수 있는 편리한 방식이다.

    테스트 모델


    프로젝트에 새로 작성된 index.html 폴더에서 htmlscov를 엽니다.book-library > htmlscov > index.html우리는 이러한 상황을 보게 될 것이다.

    우리한테 가자catalog/models.py.

    빨간색 줄은 우리가 테스트해야 할 코드 줄을 표시하고, 백분율은 테스트가 얼마나 많은 코드를 덮어썼는지 표시한다.
    Dell Book 모델을 고려하면 다음과 같습니다.
    class Book(models.Model):
        """Model representing a book
        """
        title = models.CharField(max_length=200)
        author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
        summary = models.TextField(
            max_length=1000, help_text='Enter a brief description of the book')
        isbn = models.CharField('ISBN', max_length=13, unique=True,
                                help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
        genre = models.ManyToManyField(Genre, help_text='Select a genre for this book', related_name='books')
    
        def __str__(self):
            return self.title
    
        def get_absolute_url(self):
            """Returns the url to access a detail record for this book.
            """
            return reverse('book-detail', args=[str(self.id)])
    

    테스트 모델 실현


    class TestModels(TestCase):
        @classmethod
        def setUpTestData(self):
            thriller = Genre.objects.create(name='thriller')
            scifi = Genre.objects.create(name='scifi')
            book = Book.objects.create(
                title='test title',
                summary='test summary',
                isbn='test isbn',
            )
            book.genre.set([thriller.pk, scifi.pk])
    
       def test_book_has_genre(self):
            """checks for genres in book
            """
            book = Book.objects.first()
            self.assertEqual(book.genre.count(), 2)
    
        def test_book_str(self):
            """Checks str for book
            """
            book = Book.objects.first()
            self.assertEqual(str(book), "test title")
    
        def test_book_absolute_url_with_200(self):
            book = Book.objects.first()
            self.assertEqual(book.get_absolute_url(), '/catalog/book/1')
            response = self.client.get(book.get_absolute_url())
            self.assertEqual(response.status_code, 200)
    
    

    검측str


    먼저 우리는 데이터를 설정한 다음에 테스트str를 표시한다.
        def test_book_str(self):
            """Checks str for book
            """
            book = Book.objects.first()
            self.assertEqual(str(book), "test title")
    
    주의해야 할 점은 테스트 방법의 명칭은 시종test_으로 시작해야 하며 테스트의 내용을 이해하기 위해 정확해야 한다는 것이다.단, 이름이 정확하지 않거나 의미가 없다고 생각되면, 무엇을 해야 하는지 설명하기 위해docstring을 사용하십시오.

    테스트 다대다


        def test_book_has_genre(self):
            """checks for genres in book
            """
            book = Book.objects.first()
            self.assertEqual(book.genre.count(), 2)
    
    우리가 이 책을 창작할 때, 우리는 이 책을 위해 두 가지 장르를 지정하였다.우리는 단지 책 속의 장르 수를 검사할 뿐이다. 이것은 장르 분배의 성공을 알려준다.

    절대 Url 테스트


        def test_book_absolute_url_with_200(self):
            book = Book.objects.first()
            self.assertEqual(book.get_absolute_url(), '/catalog/book/1')
            response = self.client.get(book.get_absolute_url())
            self.assertEqual(response.status_code, 200)
    
    우리 Book 모델에서 우리는 책 한 권의 정보를 얻는 get_absolute_url라는 방법이 있다.우리는 상태 코드가 200인지 확인하고 get_absolute_url 방법이 실제적으로 정확한 URL인지 확인합니다.

    테스트 뷰


    우리의 관점을 검증하기 위해서 우리는 이용할 것이다.
    주의: 이것은 엄격한 의미의 단원 테스트가 아니다.실제로 Django 보기 코드를 위한 독립된 단위 테스트를 작성하는 것은 상당히 어렵다.
    우리는 보기의 여러 내용을 검사할 수 있지만, 이것들은 흔히 볼 수 있는 내용들이다.
  • 로그인한 사용자만 볼 수 있는지 확인합니다(사용자가 로그인해야 접근할 수 있는 경우).
  • 정확한 사용자가 권한을 부여받아 볼 수 있는지 검사한다.
  • 검사GETPOST가 모두 제 방식대로 작동하는지 여부.
  • 구현할 수 있는 유효성을 검사합니다.
  • 우리는 index시도를 참고하여 테스트를 진행할 것이다.
    from .models import Book, Author, BookInstance, Genre
    from django.contrib.auth.decorators import login_required
    
    @login_required(login_url='/login/')
    def index(request):
        """View function for home page of site."""
    
        # Generate counts of some of the main objects
        num_books = Book.objects.all().count()
        num_instances = BookInstance.objects.all().count()
    
        # Available books (status = 'a')
        num_instances_available = BookInstance.objects.filter(status__exact='a').count()
        num_authors = Author.objects.count()
    
        # Number of visits to this view, as counted in the session variable.
        num_visits = request.session.get('num_visits', 0)
        request.session['num_visits'] = num_visits + 1
    
        context = {
            'num_books': num_books,
            'num_instances': num_instances,
            'num_instances_available': num_instances_available,
            'num_authors': num_authors,
            'num_visits': num_visits,
        }
    
        # Render the HTML template index.html with the data in the context variable
        return render(request, 'index.html', context=context)
    

    테스트 보기 구현


    from django.test import TestCase
    from catalog.models import Author
    from django.urls import reverse
    from django.contrib.auth.models import User
    from model_bakery import baker
    
    
    class IndexViewTest(TestCase):
        @classmethod
        def setUpTestData(cls):
            User.objects.create_user(username='test_user', password='test_password')
            books = baker.make('catalog.Book', _quantity=10)
    
        def test_view_deny_anonymous(self):
            """Test the view for unauthenticated user if unauthenticated will redirect to login page
            """
            response = self.client.get('/catalog/')
            self.assertRedirects(response, '/login/?next=/catalog/')
            response = self.client.post('/catalog/')
            self.assertRedirects(response, '/login/?next=/catalog/')
    
        def test_view_url_accesible_by_name(self):
            """Test view is accesible by the reverse method
            """
            self.client.login(username='test_user', password='test_password')
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            response = self.client.post(reverse('index'))
            self.assertEqual(response.status_code, 200)
    
        def test_view_uses_correct_template_(self):
            """Test view is using correct template
            """
            self.client.login(username='test_user', password='test_password')
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            self.assertTemplateUsed(response, 'index.html')
    
        def test_view_has_context_num_books(self):
            self.client.login(username='test_user', password='test_password')
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            self.assertTrue('num_books' in response.context)
            self.assertEqual(response.context['num_books'], 10)
    

    무단 사용자 거부


        def test_view_deny_anonymous(self):
            """Test the view for unauthenticated user if unauthenticated will redirect to login page
            """
            response = self.client.get('/catalog/')
            self.assertRedirects(response, '/login/?next=/catalog/')
            response = self.client.post('/catalog/')
            self.assertRedirects(response, '/login/?next=/catalog/')
    
    인증되지 않은 사용자가 거부되었는지 확인하고 로그인 페이지로 다시 지정합니다.

    URL은 이름으로 액세스됨


        def test_view_url_accessible_by_name(self):
            """Test view is accessible by the reverse method
            """
            self.client.login(username='test_user', password='test_password')
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            response = self.client.post(reverse('index'))
            self.assertEqual(response.status_code, 200)
    
    우리는 reverse 방법이 유효한지 확인하고 200GET 방법의 상태 코드POST를 얻었다.

    올바른 템플릿 사용 방법


        def test_view_uses_correct_template_(self):
            """Test view is using correct template
            """
            self.client.login(username='test_user', password='test_password')
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            self.assertTemplateUsed(response, 'index.html')
    
    이것은 assertTemplateUsed 방법이 정확한 템플릿을 사용했는지 검사할 것입니다.

    시험서 수량


        def test_view_has_context_num_books(self):
            self.client.login(username='test_user', password='test_password')
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            self.assertTrue('num_books' in response.context)
            self.assertEqual(response.context['num_books'], 10)
    
    이런 방법에서 우리는 도서의 수량 분배가 정확한지 시험한다.우리는 이곳에서 model-bakery를 사용하여 10개Book의 실례를 만들었다.

    테스트 테이블


    표는 단원 테스트를 쉽게 진행할 수 있다.우리는 표에서 검증 테스트를 진행할 수 있다.그것들은 값 사전을 받아들여 검증하고 오류나 삭제된 데이터를 되돌려줍니다.
    우리의 프로젝트에서 우리는 표가 없기 때문에 본 강좌를 위해 표를 하나 만들자.
    from django import forms
    from .models import Book
    
    
    class AddBookForm(forms.ModelForm):
        class Meta:
            model = Book
            fields = ["title"]
    
        def clean_title(self):
            title = self.cleaned_data['title']
            if not title:
                return title
            if not title[0].isupper():
                self.add_error("title", "Should start with an uppercase")
            if title.endswith("."):
                self.add_error("title", "Should not end with a full stop")
    
            return title
    
    우리는 모델에 몇 가지 검증을 추가해서 그것을 테스트할 것이다.
    우리 테스트는 이랬을 거야.
    from django.test import TestCase
    from catalog.forms import AddBookForm
    
    
    class AddBookFormTests(TestCase):
        def test_title_starting_lowercase(self):
            form = AddBookForm(data={"title": "a lowercase title"})
    
            self.assertEqual(
                form.errors["title"], ["Should start with an uppercase"]
            )
    
        def test_title_ending_full_stop(self):
            form = AddBookForm(data={"title": "A stopped title."})
    
            self.assertEqual(
                form.errors["title"], ["Should not end with a full stop"]
            )
    
        def test_title_with_ampersand(self):
            form = AddBookForm(data={"title": ""})
    
            self.assertEqual(
                form.errors["title"], ["This field is required."]
            )
    
    우리가 표에서 보듯이, 우리는 주로 테스트 검증을 한다.셀 테스트와 함께 통합 테스트를 추가하려는 경우 클라이언트를 사용하여 다음을 수행하여 유효성을 검사할 수 있습니다.
        def test_title_starting_lowercase(self):
            response = self.client.post(
                "/books/add/", data={"title": "a lowercase title"}
            )
    
            self.assertEqual(response.status_code, HTTPStatus.OK)
            self.assertContains(
                response, "Should start with an uppercase letter", html=True
            )
    

    다음에 토론할 화제


    나는 이 시리즈에서 가능한 한 많이 소개하려고 했지만, 테스트에 있어서는 해야 할 일이 매우 많다.나는 곧 발표될 블로그에서 이 화제들을 소개하려고 시도할 것이다.짧은 목록은 다음과 같습니다.
  • 지속적인 통합: 새로운 제출이 진행될 때마다 모든 테스트를 자동으로 실행합니다.GitHub 작업, Travis CI 및 Circle CI를 사용하여 수행할 수 있습니다.
  • Mock: 테스트 중인 애플리케이션의 일부를 가상 버전으로 바꿉니다.
  • Pytest: unittest와 유사한 테스트 프레임워크입니다.우리도 이걸로 대체할 수 있다unittest
  • 모델 Bakery: 프로젝트에서 이 점을 작은 실현으로 보았지만 더 많은 논의가 필요합니다.
  • TDD: 테스트 드라이브 개발은 사실상 상당히 고급스러운 화제입니다. 시간이 걸려야 습관이 될 수 있지만 그 장점을 시작하고 이해하기 시작하면 다시는 돌아보고 싶지 않습니다.
  • 결론


    한마디로 테스트를 작성할 구체적인 방법은 없다.이것은 당신이 종사하고 있는 프로젝트나 당신이 있는 회사에 달려 있다.모든 사람은 자신의 방식으로 코드를 테스트할 수 있다.
    내가 너에게 줄 수 있는 마지막 조언은 테스트를 쓰는 것을 두려워하지 말라는 것이다.그래, 처음에는 무서울 수도 있지만, 테스트를 작성하기 시작하면 코드를 더 잘 이해할 수 있다. 이것은 자신을 의심하게 하고, 모든 프로그램이 잘못될 수 있는 상황을 생각하게 하며, 더 좋은 프로그래머가 되도록 자극할 것이다.
    오늘 여기서 궁금한 사항이 있으시면 댓글로 남겨주시거나 다른 질문이 있으시면 주저 없이 아래 플랫폼을 통해 연락 주세요GitHub.

    좋은 웹페이지 즐겨찾기