Тестирование в Django

Тестирование имеет решающее значение в жизненном цикле разработки программного обеспечения, гарантируя, что приложения соответствуют требуемым стандартам качества и функционируют должным образом. Он систематически оценивает программные компоненты, модули или системы для выявления ошибок, багов или отклонений от желаемого поведения.

Тестирование помогает снизить риски, повысить надежность и улучшить общее взаимодействие с пользователем.

Тестирование с Django

Для тестирования в Django необходимо сначала загрузить библиотеку. Чтобы скачать Django, мы используем pip. В этой статье мы будем использовать pip для управления нашими загрузками на нашем локальном компьютере. Для этого нам нужно запустить команду ниже в нашем терминале:

pip install django

После установки Django создаем проект с django-adminключевым словом

django-admin startproject progy .

Это создает базовый шаблон для нашего проекта. Затем мы создаем приложение. Приложение — это автономный модуль или компонент, который инкапсулирует определенную функциональность или функцию веб-приложения. Мы будем называть наше приложение account.

django-admin startapp account

Это будет выглядеть примерно так после создания.

Скриншот приложения Django в VScode

Далее мы включаем наше приложение INSTALLED APPSв[settings.py]

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',

        "account", # our app
    ]

И в нашу базу urls.py мы включаем файл URL-адреса приложения.

Примечание. Создайте urls.pyфайл, чтобы избежать ошибок.
from django.contrib import admin
from django.urls import path, include

url_patterns = [
    path('admin/', admin.site.urls),
    path('', include('account.urls')),
]

Типы тестов

Существуют различные типы тестов, которые обычно выполняются в процессе разработки программного обеспечения, в том числе:

  • Модульные тесты: эти тесты сосредоточены на тестировании отдельных модулей или компонентов изолированно.
  • Интеграционные тесты: эти тесты проверяют взаимодействие и интеграцию между несколькими модулями.
  • Системные тесты: эти тесты проверяют общую функциональность программной системы.
  • Приемочные тесты: эти тесты выполняются пользователями или клиентами для проверки того, что программное обеспечение соответствует их требованиям.

Тестирование может выполняться вручную или с использованием автоматизированных сред и инструментов тестирования. Автоматизированное тестирование предпочтительнее в современной разработке программного обеспечения из-за его эффективности, воспроизводимости и масштабируемости.

Это позволяет создавать наборы тестов, которые могут выполняться автоматически, что позволяет разработчикам выявлять проблемы на раннем этапе и оптимизировать процесс разработки.

Тестирование — это не отдельная деятельность в разработке программного обеспечения, а неотъемлемая часть рабочего процесса разработки. Это должно выполняться постоянно на протяжении всего цикла разработки, начиная с ранних стадий сбора требований и проектирования, через реализацию и вплоть до развертывания и обслуживания.

Такой итеративный подход к тестированию обеспечивает быстрое обнаружение и исправление дефектов, что снижает затраты и усилия, связанные с доработкой и исправлением ошибок.

Написание модульных тестов

Написание модульных тестов является фундаментальным аспектом разработки программного обеспечения, и Django предоставляет комплексную среду тестирования, облегчающую этот процесс. Это наиболее распространенная форма теста в Django. Мы рассмотрим тестирование представлений.

Наш views.py код выглядит следующим образом:

from django.http import HttpResponse

    def hello_world(request):
        """A simple view that returns a string "Hello, world!""""
        return HttpResponse("Hello, world!")

    def add_numbers(request):
        """A view that returns the sum of two numbers."""
        if request.method == 'POST':
            num1 = int(request.POST.get('num1'))
            num2 = int(request.POST.get('num2'))
            sum = num1 + num2
            return HttpResponse(f"The sum of {num1} and {num2} is {sum}.")
        else:
            return HttpResponse("Please submit the form to add two numbers.")

В Django модульные тесты обычно организованы в классы тестовых случаев. Мы создаем новый класс, который наследуется от Django django.test.TestCaseв нашем файле tests.py. Единичные случаи будут основаны на двух функциях, которые у нас есть выше, hello_world и add_numbers соответственно.

Перед этим давайте обновим наше приложение urls.py

from django.urls import path
from . import views

app_name = 'accounts'

urlpatterns = [
    path('hello/', views.hello_world, name='hello_world'),
    path('add/', views.add_numbers, name='add_numbers'),
]

Модульные тесты для этих функций будут выглядеть так:

from django.test import TestCase
    from django.urls import reverse

    class HelloWorldViewTest(TestCase):
        def test_hello_world(self):
            url = reverse('accounts:hello_world')
            response = self.client.get(url)
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.content.decode(), "Hello, world!")

    class AddNumbersViewTest(TestCase):
        def test_add_numbers_get(self):
            url = reverse('accounts:add_numbers')
            response = self.client.get(url)
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, "Please submit the form to add two numbers.")

        def test_add_numbers_post(self):
            url = reverse('accounts:add_numbers')
            data = {'num1': '5', 'num2': '3'}
            response = self.client.post(url, data)
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, "The sum of 5 and 3 is 8.")

Чтобы убедиться, что наши тесты работают и увидеть результат, мы используем команду, python manage.py test и это дает нам:

Скриншот запущенного теста.

Этот процесс создает тестовую базу данных и затем уничтожает ее.

Написание интеграционных тестов

В контексте веб-разработки с Django интеграционные тесты включают проверку взаимодействия между различными частями приложения, такими как представления, модели, шаблоны и база данных.

Для нашего интеграционного теста мы создадим модель models.py для тестирования.

 from django.db import models

    class Name(models.Model):
        """A name model."""
        name = models.CharField(max_length=100)
        description = models.TextField()

        def __str__(self):
            return self.name

И наш views.pyвернет список имен, которые есть в нашей базе данных.

def name_list(request):
    """A view that returns a list of names."""
    names = Name.objects.all()
    return render(request, 'account/name_list.html', {'names': names})

The urls.py

from django.urls import path
from . import views

app_name = 'accounts'

urlpatterns = [
    path('names/', views.name_list, name='name_list'),
]

И наш интеграционный тест tests.py будет таким:

from django.test import TestCase, Client
    from django.urls import reverse
    from .models import Name

    class NameIntegrationTest(TestCase):
        def setUp(self):
            self.client = Client()
            self.url = reverse('accounts:name_list')

        def test_name_list_integration(self):
            # Create test data
            Name.objects.create(name='John', description='John Doe')
            Name.objects.create(name='Jane', description='Jane Smith')

            # Send a GET request to the URL
            response = self.client.get(self.url)

            # Assert the response status code and content
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, 'John')
            self.assertContains(response, 'Jane')

Важно отметить, что интеграционные тесты дополняют модульные тесты, которые фокусируются на изолированном тестировании отдельных модулей или компонентов. Оба типа тестов важны для обеспечения общего качества и надежности программного приложения.

Написание функциональных тестов

Функциональные тесты, также известные как сквозные тесты или приемочные тесты, представляют собой тестирование программного обеспечения, которое проверяет функциональность приложения с точки зрения пользователя. В Django функциональные тесты имитируют взаимодействие пользователя с приложением и проверяют ожидаемое поведение. Используя наш существующий пример из models.py, views.pyи urls.py, мы создаем функциональный тест в нашем tests.pyс Selenium в этой статье.

Selenium — это фреймворк с открытым исходным кодом, обычно используемый для автоматизации веб-браузеров. Он предоставляет набор инструментов и библиотек для взаимодействия с веб-браузерами и автоматизации действий браузера, таких как нажатие кнопок, заполнение форм и навигация по веб-страницам. Он также поддерживает множество языков, и Python — один из них.

Наш tests.py становится

from django.test import LiveServerTestCase
from selenium import webdriver
from .models import Name
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

class NameFunctionalTest(LiveServerTestCase):
    def setUp(self):
        self.selenium = webdriver.Chrome()
        super().setUp()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_name_list_functional(self):
        # Create test data
        Name.objects.create(name='John', description='John Doe')
        Name.objects.create(name='Jane', description='Jane Smith')

        # Simulate user interactions using Selenium
        self.selenium.get(self.live_server_url + '/names/')
        self.assertIn('Name List', self.selenium.title)
        names =self.selenium.find_elements(By.TAG_NAME, 'li')
        self.assertEqual(len(names), 2)
        self.assertEqual(names[0].text, 'John - John Doe')
        self.assertEqual(names[1].text, 'Jane - Jane Smith')

        # Simulate the page returns a 200
        self.assertEqual(self.selenium.title, 'Name List')

После запуска тестовой команды браузер Chrome быстро откроется и выполнит тест.

Тестовое покрытие и отчетность

Покрытие тестами — это мера степени, в которой наш код тестируется нашим набором тестов. Тестовое покрытие необходимо для обеспечения качества и надежности нашего программного обеспечения.

В Django есть различные инструменты для измерения тестового покрытия и создания отчетов. Одним из популярных инструментов является coverage.py библиотека Python, которая отслеживает, какие части кода выполняются во время выполнения теста.

Чтобы использовать coverage.py для тестового покрытия и отчетности в Django, давайте выполним следующие шаги.

1. Установите покрытие с помощью pip, выполнив следующую команду

pip install coverage

2. Запустите тесты с покрытием с помощью coverage команды

coverage run manage.py test

3. После запуска тестов мы можем сгенерировать отчет о покрытии с помощью coverage report команды. Эта команда показывает сводку покрытия в терминале:

coverage report
coverage html # to generate an HTML report that is well detailed

Чтобы настроить покрытие, .coveragercможно создать файл конфигурации в корневом каталоге нашего проекта Django. Например, покрытие моего теста для моего кода было:

Скриншот каталога .coveragc

Тестовые фикстуры и имитация

Тестовые фикстуры и имитация — важные методы тестирования программного обеспечения, которые помогают создавать контролируемые среды и изолировать зависимости для обеспечения надежных и предсказуемых результатов тестирования.

Текстовые фикстуры

Тестовые фикстуры — это набор предопределенных данных, конфигурации или состояний, которые настраиваются перед запуском тестов. Они помогают создать последовательную и известную отправную точку для тестов, позволяя нам воспроизводить определенные условия и поведение. В Django мы можем использовать фикстуры для загрузки данных в нашу тестовую базу данных.

Чтобы создать файл фиксации, мы сначала создаем файл JSON, определяющий тестовые данные, которые мы хотим использовать. Например, давайте создадим sample.json файл со следующими записями:

[
  {
    "name": "Herlet Skim",
    "description": "A random person that exists out of nowhere but has a pretty interesting life."
  },
  {
    "name": "John Doe",
    "description": "A fictional character who is often used as a placeholder name."
  },
  {
    "name": "Jane Doe",
    "description": "A fictional character who is often used as a placeholder name for a woman."
  },
  {
    "name": "Sherlock Holmes",
    "description": "A fictional detective created by Sir Arthur Conan Doyle."
  },
  {
    "name": "Dr. Watson",
    "description": "A fictional doctor and the partner of Sherlock Holmes."
  }
]

Получив sample.json файл, мы можем загрузить фикстуры, используя fixtures атрибут тестового примера. Указав файл фикстуры в fixtures атрибуте, Django загрузит тестовые данные, определенные в фикстуре, перед запуском метода тестирования.

from django.test import TestCase

class MyTestCase(TestCase):
    fixtures = ['sample.json']

    def test_something(self):
        # Test logic here 

Имитация(mocking)

Имитация, с другой стороны, — это процесс замены реальных объектов или функций специфичными для теста версиями, которые воспроизводят их поведение. В Python/Django unittest.mock модуль предоставляет мощные возможности имитации.

Чтобы начать работу с mocking, мы импортируем mock модуль

from unittest.mock import Mock

Используя наши предыдущие данные из models.py, views.pyи urls.py, мы создаем новые tests.py для насмешек

from django.test import TestCase, RequestFactory
from unittest.mock import Mock, patch
from .models import Name
from .views import name_list

class NameListViewTest(TestCase):
    def setUp(self):
        self.factory = RequestFactory()

    def test_name_list_view(self):
        # Create some sample Name objects
        Name.objects.create(name='John Doe', description='Description 1')
        Name.objects.create(name='Jane Smith', description='Description 2')

        # Create a mock request object
        request = self.factory.get('/names/')

        # Create a mock queryset for the Name objects
        mock_queryset = Mock(spec=Name.objects.all())
        mock_queryset.return_value = [
            Mock(name='John Doe', description='Description 1'),
            Mock(name='Jane Smith', description='Description 2')
        ]

        # Patch the Name.objects.all() method to return the mock queryset
        with patch('account.views.Name.objects.all', mock_queryset):
            # Call the name_list view
            response = name_list(request)

        # Assert that the response has the expected status code
        self.assertEqual(response.status_code, 200)

        # Assert that the response contains the expected data
        self.assertContains(response, 'John Doe')
        self.assertContains(response, 'Jane Smith')

Лучшие практики тестирования

Когда дело доходит до тестирования, следование рекомендациям может значительно повысить эффективность и результативность нашего процесса тестирования. Некоторые из лучших практик тестирования, которые следует учитывать:

1. Начните со стратегии тестирования: определите четкую стратегию тестирования, в которой излагаются цели, задачи и подход к тестированию программного обеспечения. Определите типы тестов, которые необходимо выполнить (модульные тесты, интеграционные тесты и т. д.), определите объем тестирования и установите рекомендации по покрытию тестами.

2. Пишите код, который можно тестировать. Разрабатывайте код так, чтобы его было легко тестировать. Следуйте таким принципам, как SOLID и DRY, чтобы писать модульный, несвязанный и повторно используемый код. Отделите бизнес-логику от представления и внешних зависимостей, что упрощает модульное тестирование.

3. Следуйте шаблону AAA: при написании модульных тестов структурируйте их, используя шаблон Arrange-Act-Assert (AAA). Расположите тестовые данные, задайте необходимые предварительные условия, воздействуйте на тестируемый код и подтвердите ожидаемые результаты или поведение.

4. Тестирование пограничных случаев и граничных условий. Убедитесь, что тесты охватывают широкий спектр сценариев, включая пограничные случаи и граничные условия. Проверяйте входные данные при их минимальных и максимальных значениях, пустых или нулевых значениях и любых особых случаях, которые могут повлиять на поведение кода.

5. Мониторинг покрытия кода. Измеряйте и контролируйте покрытие кода, чтобы убедиться, что тесты адекватно охватывают кодовую базу. Стремитесь к высокому охвату кода, чтобы повысить уверенность в надежности и правильности программного обеспечения.

6. Поддерживайте и обновляйте тесты: обновляйте тесты в соответствии с изменениями в кодовой базе. По мере внесения изменений в приложение пересматривайте и обновляйте тесты соответствующим образом. Устаревшие тесты могут давать ложноположительные или ложноотрицательные результаты, что приводит к недостоверным результатам.

Автоматизация тестирования и непрерывная интеграция

Автоматизация тестирования и непрерывная интеграция являются ключевыми методами разработки программного обеспечения, помогающими повысить эффективность, качество и надежность. Рассмотрим эти два понятия более подробно.

Автоматизация тестирования

Это включает в себя использование инструментов и сценариев для автоматизации выполнения тестов, сокращения ручных операций и обеспечения более быстрого и частого тестирования. Он включает в себя написание кода для автоматизации настройки, выполнения и проверки тестовых случаев. Некоторые из преимуществ автоматизации тестирования включают в себя:

1. Повышенная эффективность: автоматизированные тесты могут выполняться намного быстрее по сравнению с ручными тестами, что позволяет быстрее получать обратную связь о качестве программного обеспечения.

2. Увеличение охвата тестами. Благодаря автоматизации тестирования становится проще достичь высокого охвата тестами, выполняя большее количество тестов в более короткие сроки.

3. Интеграция с CI/CD. Автоматизированные тесты можно легко интегрировать в конвейер CI/CD, обеспечивая непрерывное тестирование и более быструю доставку программного обеспечения.

4. Согласованность. Автоматизированные тесты обеспечивают последовательное выполнение тестов и исключают человеческие ошибки, которые могут возникнуть при ручном тестировании.

Популярные среды и инструменты автоматизации тестирования для Django включают unittest , pytest , Selenium , WebDriver для автоматизации браузера и встроенную среду тестирования Django.

Непрерывная интеграция (CI)

Непрерывная интеграция — это практика разработки, при которой разработчики часто объединяют свои изменения кода в общий репозиторий. Процесс CI включает в себя автоматическое создание и тестирование программного обеспечения при каждой фиксации кода. Некоторые из ключевых аспектов CI:

1. Автоматизированная сборка: системы CI автоматически создают программное обеспечение при каждой фиксации кода, гарантируя, что кодовая база остается в согласованном и функциональном состоянии.

2. Автоматизированное тестирование. Автоматизированные тесты, включая модульные тесты, интеграционные тесты и даже тесты пользовательского интерфейса, выполняются как часть процесса CI. Это гарантирует, что изменения кода не приведут к регрессии или нарушению существующей функциональности.

3. Интеграция с контролем версий: системы CI интегрированы с системами контроля версий, такими как Git, что позволяет им отслеживать изменения кода и автоматически запускать процесс сборки и тестирования.

4. Готовность к развертыванию: CI помогает гарантировать, что программное обеспечение всегда находится в состоянии, пригодном для развертывания. Если сборка и тесты проходят успешно, программное обеспечение готово к развертыванию.

Популярные инструменты CI включают Jenkins, Travis CI, CircleCI и GitLab CI/CD.

Сочетая автоматизацию тестирования и непрерывную интеграцию, мы можем создать надежный и эффективный процесс разработки программного обеспечения. Автоматизированные тесты также обеспечивают быструю обратную связь об изменениях кода, обнаруживают проблемы на раннем этапе и улучшают общее качество программного обеспечения.

Заключение

Тестирование является важным аспектом цикла разработки программного обеспечения, поскольку оно помогает обеспечить качество кода, его надежность и правильность.

Применяя комплексный подход к тестированию в наших проектах Django, мы можем улучшить стабильность, ремонтопригодность и общее качество наших приложений.

Это помогает разработчикам создавать продукты, которые соответствуют ожиданиям пользователей, соответствуют спецификациям и выдерживают сценарии реального использования.

Включая методы тестирования в свои рабочие процессы разработки, группы разработчиков программного обеспечения могут добиться более высокого уровня гарантии качества и уровня удовлетворенности клиентов.