DevGang
Авторизоваться

Пишите профессиональные модульные тесты на Python

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

Пример запуска

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

Создайте новый каталог с именем python_tests и добавьте два файла, а именно:

  1. car.py 
  2. test_car.py

Сделайте каталог пакетом Python, добавив файл __init__.py. Структура ваших файлов должна выглядеть так:

python_tests/
        -__init__.py
        - car.py
        - test_car.py

Файл car.py будет использоваться для написания логики программы самоуправляемого автомобиля, которую мы будем использовать в этом примере, а файл test_car.py будет использоваться для написания всех тестов.

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

class SelfDrivingCar(object):
 
    def __init__(self):
        self.speed = 0
        self.destination = None
 
    def _accelerate(self):
        self.speed += 1
         
    def _decelerate(self):
        if self.speed > 0:
            self.speed -= 1
                     
    def _advance_to_destination(self):
        distance = self._calculate_distance_to_object_in_front()
 
        if distance < 10:
            self.stop()
 
        elif distance < self.speed / 2:
            self._decelerate()
 
        elif self.speed < self._get_speed_limit():
            self._accelerate()
 
    def _has_arrived(self):
        pass
 
    def _calculate_distance_to_object_in_front(self):
        pass
 
    def _get_speed_limit(self):
        pass
 
    def stop(self):
        self.speed = 0
 
    def drive(self, destination):
        self.destination = destination
        while not self._has_arrived():            
            self._advance_to_destination()
        self.stop()
 
    def __init__(self):
        self.speed = 0
        self.destination = None
         
    def _accelerate(self):
        self.speed += 1
 
    def _decelerate(self):
        if self.speed > 0:
            self.speed -= 1
 
    def _advance_to_destination(self):
        distance = self._calculate_distance_to_object_in_front()
        if distance < 10:
            self.stop()
 
        elif distance < self.speed / 2:
            self._decelerate()
        elif self.speed < self._get_speed_limit():
            self._accelerate()
 
    def _has_arrived(self):
        pass
 
    def _calculate_distance_to_object_in_front(self):
        pass
 
    def _get_speed_limit(self):
        pass
 
    def stop(self):
        self.speed = 0
 
    def drive(self, destination):
        self.destination = destination
        while not self._has_arrived():            
            self._advance_to_destination()
        self.stop()

Вот модульный тест для метода stop(), чтобы подогреть ваш аппетит. Я расскажу подробности позже.

import unittest
from car import SelfDrivingCar
 
class SelfDrivingCarTest(unittest.TestCase):
 
    def setUp(self):
        self.car = SelfDrivingCar()
 
    def test_stop(self):
        self.car.speed = 5
        self.car.stop()
 
        # Verify the speed is 0 after stopping
        self.assertEqual(0, self.car.speed)
 
        # Verify it is Ok to stop again if the car is already stopped
        self.car.stop()
 
        self.assertEqual(0, self.car.speed)

Руководство по модульному тестированию

Более 2 миллионов тем и плагинов WordPress, веб-шаблонов и шаблонов электронной почты, наборов пользовательского интерфейса и многого другого

Загрузите тысячи тем и плагинов WordPress, веб-шаблонов, элементов пользовательского интерфейса и многое другое с членством в Envato Elements. Получите неограниченный доступ к растущей библиотеке из миллионов творческих и кодовых ресурсов.

Совершить

Написание хороших модульных тестов — тяжелая работа. Написание модульных тестов требует времени. Когда вы вносите изменения в свой код, вам, как правило, также необходимо изменить тесты. Иногда у вас будут ошибки в тестовом коде. Это означает, что вы должны быть действительно преданы делу. Преимущества огромны даже для небольших проектов, но они не бесплатны.

Будьте дисциплинированы

Вы должны быть дисциплинированными. Быть последовательным. Убедитесь, что тесты всегда проходят. Не допускайте поломки тестов, потому что вы «знаете», что код в порядке.

Автоматизировать

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

Непроверенный код неисправен по определению

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

Фон

Что такое единица?

Модуль для модульного тестирования — это файл/модуль, содержащий набор связанных функций или класс. Если у вас есть файл с несколькими классами, вы должны написать модульный тест для каждого класса.

Использовать TDD или не использовать TDD

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

Причина в том, что я проектирую с помощью кода. Я пишу код, смотрю на него, переписываю, снова смотрю и снова переписываю очень быстро. Написание тестов сначала ограничивает меня и замедляет.

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

Модуль Unittest

Модуль unittest поставляется со стандартной библиотекой Python. Он предоставляет класс TestCase, от которого вы можете получить свой класс. Каждый экземпляр теста должен быть производным от класса TestCase. Чтобы получить класс TestCase, вам нужно импортировать его из модуля unittest, как показано ниже.

from  unittest import TestCase

Затем вы можете переопределить метод setUp(), чтобы подготовить тестовую фикстуру перед каждым тестом, и/или метод класса classSetUp(), чтобы подготовить тестовую фикстуру для всех тестов (не сбрасывать между отдельными тестами).

Существуют соответствующие методы tearDown() и classTearDown(), которые вы также можете переопределить.

Вот соответствующие части нашего класса SelfDrivingCarTest. Я использую только метод setUp(). Я создаю новый экземпляр SelfDrivingCar и сохраняю его в файле self.car, чтобы он был доступен для каждого теста.

import unittest
 
class SelfDrivingCarTest(unittest.TestCase):
 
    def setUp(self):
        self.car = SelfDrivingCar()

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

  1. Подготовьте среду (необязательно).
  2. Подготовьте среду (необязательно).
  3. Вызовите тестируемый код.
  4. Утверждают, что фактический результат соответствует ожидаемому.

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

Например, метод stop() класса SelfDrivingCar ничего не возвращает, но изменяет внутреннее состояние, устанавливая скорость на 0. Метод assertEqual(), предоставленный базовым классом TestCase, используется здесь для проверки того, что вызов stop () работал как положено.

def test_stop(self):
        self.car.speed = 5
        self.car.stop()
 
        # Verify the speed is 0 after stopping
        self.assertEqual(0, self.car.speed)
 
        # Verify it is Ok to stop again if the car is already stopped
        self.car.stop()
 
        self.assertEqual(0, self.car.speed)

На самом деле здесь два теста. Первый тест заключается в том, чтобы убедиться, что если скорость автомобиля равна 5 и вызывается функция stop(), то скорость становится равной 0. Затем, еще один тест заключается в том, чтобы убедиться, что ничего не произойдет при повторном вызове функции stop(), когда автомобиль уже остановился.

Позже я представлю еще несколько тестов для дополнительной функциональности.

Модуль Доктест

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

Я не использую и не рекомендую doctest для крупномасштабных систем. Надлежащее модульное тестирование требует много работы. Тестовый код обычно намного больше тестируемого кода. Строки документации — просто неподходящая среда для написания комплексных тестов. Но они классные. Вот как выглядит факториальная (factorial) функция с doc-тестами:

import math
 
def factorial(n):
    """Return the factorial of n, an exact integer >= 0.
    If the result is small enough to fit in an int, return an int.
    Else return a long.
 
    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
 
    >>> [factorial(long(n)) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
 
    >>> factorial(30)
    265252859812191058636308480000000L
 
    >>> factorial(30L)
    265252859812191058636308480000000L
 
    >>> factorial(-1)
 
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0
 
 
    Factorials of floats are OK, but the float must be an exact integer:
 
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
 
    >>> factorial(30.0)
    265252859812191058636308480000000L
 
 
    It must also not be ridiculously large:
 
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
 
    """
 
    if not n >= 0:
        raise ValueError("n must be >= 0")
 
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
 
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
 
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result
 
if __name__ == "__main__":
    import doctest
    doctest.testmod()

Как видите, строка документации намного больше, чем код функции. Это не способствует читабельности.

Запуск тестов

ХОРОШО. Вы написали свои модульные тесты. Для большой системы у вас будут десятки/сотни/тысячи модулей и классов, возможно, в нескольких каталогах. Как вы проводите все эти тесты?

Модуль unittest предоставляет различные средства для группировки тестов и их программного запуска путем загрузки и запуска тестов (Loading and Running Tests).

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

if __name__ == '__main__':
    unittest.main()

Продолжайте и добавьте тестовый скрипт в конец файла test_car.py, как показано ниже.

import unittest
from car import SelfDrivingCar
  
class SelfDrivingCarTest(unittest.TestCase):
 
    def setUp(self):
        self.car = SelfDrivingCar()
     
    def test_stop(self):
        self.car.speed = 5
        self.car.stop()
 
        # Verify the speed is 0 after stopping
        self.assertEqual(0, self.car.speed)
 
        # Verify it is Ok to stop again if the car is already stopped
        self.car.stop()
        self.assertEqual(0, self.car.speed)
 
if __name__ == '__main__':
    unittest.main()

Для запуска тестов запустите программу python

python test_car.py

Вы должны увидеть вывод ниже

----------------------------------------------------------------------
Ran 1 test in 0.000s
 
OK

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

if __name__ == '__main__':
    unittest.main(verbosity=2)

Вывод будет выглядеть следующим образом:

test_stop (__main__.SelfDrivingCarTest) ... ok
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
OK

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

Еще один и самый простой способ — тестовое обнаружение. Эта опция была добавлена только в Python 2.7. До версии 2.7 вы могли использовать nose для обнаружения и запуска тестов. У Nose есть несколько других преимуществ, таких как запуск тестовых функций без необходимости создавать класс для ваших тестовых случаев. Но для целей этой статьи давайте придерживаться unittest.

Как следует из названия, обнаруживает поиск в каталоге и запускает все файлы с именем test*.py.

Чтобы обнаружить и запустить тесты на основе юнит-тестов, просто введите в командной строке:

python -m unittest discover

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

.
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
OK

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

python -m unittest discover -v

Вывод будет:

test_stop (tests.SelfDrivingCarTest) ... ok
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
OK

Как видно из вывода, вы запустили только один тест, то есть SelfDrivingCarTest.

Есть несколько флагов, управляющих операцией:

python -m unittest -h
 
Usage: python -m unittest [options] [tests]
 
 
Options:
  -h, --help       Show this message
  -v, --verbose    Verbose output
  -q, --quiet      Minimal output
  -f, --failfast   Stop on first failure
  -c, --catch      Catch control-C and display results
  -b, --buffer     Buffer stdout and stderr during test runs
 
Examples:
  python -m unittest test_module               - run tests from test_module
  python -m unittest module.TestClass          - run tests from module.TestClass
  python -m unittest module.Class.test_method  - run specified test method
 
[tests] can be a list of any number of test modules, classes and test
methods.
 
Alternative Usage: python -m unittest discover [options]
 
Options:
  -v, --verbose    Verbose output
  -f, --failfast   Stop on first failure
  -c, --catch      Catch control-C and display results
  -b, --buffer     Buffer stdout and stderr during test runs
  -s directory     Directory to start discovery ('.' default)
  -p pattern       Pattern to match test files ('test*.py' default)
  -t directory     Top level directory of project (default to
                   start directory)
 
For test discovery all test modules must be importable from the top
level directory of the project.

Тестовое покрытие

Покрытие тестами — область, которой часто пренебрегают. Покрытие означает, какая часть вашего кода фактически протестирована вашими тестами. Например, если у вас есть функция с оператором if-else и вы тестируете только ветвь if, то вы не знаете, работает ли ветвь else или нет. В следующем примере кода функция add() проверяет тип своих аргументов. Если оба являются целыми числами, он просто добавляет их.

Если оба являются строками, он пытается преобразовать их в целые числа и сложить. В противном случае он вызывает исключение. Функция test_add() тестирует функцию add() с аргументами, которые являются как целыми числами, так и аргументами с плавающей запятой, и проверяет правильность поведения в каждом случае. Но тестовое покрытие неполное. Случай строковых аргументов не проверялся. В итоге тест проходит успешно, но опечатка в ветке, где аргументами являются обе строки, не обнаружена (видите там 'intg'?).

import unittest
 
 
def add(a, b):
    """This function adds two numbers a, b and returns their sum
    a and b may integers
    """
 
    if isinstance(a, int) and isinstance(b, int):
        return a + b
    elseif isinstance(a, str) and isinstance(b, str):
        return int(a) + intg(b)
    else:
        raise Exception('Invalid arguments')
 
class Test(unittest.TestCase):
    def test_add(self):
        self.assertEqual(5, add(2, 3))
        self.assertEqual(15, add(-6, 21))
        self.assertRaises(Exception, add, 4.0, 5.0)
 
unittest.main()

Практические модульные тесты

Написание юнит-тестов промышленного уровня — непростая задача. Есть несколько вещей, которые следует учитывать, и компромиссы, которые необходимо сделать.

Дизайн для тестируемости

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

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

Например, наш класс SelfDrivingCar отвечает за высокоуровневую работу автомобиля: ехать, останавливаться, перемещаться. У него есть метод calculate_distance_to_object_in_front(), который еще не реализован. Эта функциональность, вероятно, должна быть реализована совершенно отдельной подсистемой. Это может включать в себя считывание данных с различных датчиков, взаимодействие с другими беспилотными автомобилями, целый стек машинного зрения для анализа изображений с нескольких камер.

Давайте посмотрим, как это работает на практике. SelfDrivingCar примет аргумент с именем object_detector, у которого есть метод с именем calculate_distance_to_object_in_front(), и делегирует эту функциональность этому объекту. Теперь нет необходимости проводить модульное тестирование, потому что за это отвечает ответственный object_detector (и его нужно тестировать). Вы по-прежнему хотите модульно протестировать тот факт, что вы правильно используете object_detector.

class SelfDrivingCar(object):
 
    def __init__(self, object_detector):
        self.object_detector
        self.speed = 0
        self.destination = None
 
    def _calculate_distance_to_object_in_front(self):
        return self.object_detector.calculate_distance_to_object_in_f

Затраты/Выгоды

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

Например, наш класс беспилотных автомобилей является сверхкритическим. Если метод stop() не работает должным образом, наша беспилотная машина может убить людей, уничтожить имущество и обрушить весь рынок беспилотных автомобилей. Если вы разрабатываете самоуправляемый автомобиль, я подозреваю, что ваши модульные тесты для метода stop() будут немного более строгими, чем мои.

С другой стороны, если одна кнопка в вашем веб-приложении на странице, расположенной тремя уровнями ниже вашей главной страницы, немного мерцает, когда кто-то нажимает на нее, вы можете это исправить, но, вероятно, не будете добавлять специальный модульный тест для этого случая. Просто экономика этого не оправдывает.

Тестирование мышления

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

Например, предположим, что ваш код должен что-то вычислить. Для этого ему необходимо загрузить некоторые данные из базы данных, прочитать файл конфигурации и динамически обращаться к некоторому REST API для получения актуальной информации. Все это может потребоваться по разным причинам, но объединение всего этого в одну функцию усложнит модульное тестирование. Это все еще возможно с насмешкой, но гораздо лучше правильно структурировать свой код.

Чистые функции

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

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

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

Почему? Потому что код не изменился. Он по-прежнему ожидает XML-файл конфигурации, и ваш тест по-прежнему создает для него XML-файл. Но в продакшене ваш код получит файл JSON, который не сможет разобрать.

Тестирование обработки ошибок

Обработка ошибок — еще одна вещь, которую важно протестировать. Это тоже часть дизайна. Кто отвечает за правильность ввода? Каждая функция и метод должны быть ясны об этом. Если это обязанность функции, она должна проверить свой ввод, но если это обязанность вызывающей стороны, то функция может просто заниматься своими делами и считать ввод правильным. Общая правильность системы будет обеспечиваться наличием тестов для вызывающей стороны, чтобы убедиться, что она передает только правильный ввод в вашу функцию.

Как правило, вы хотите проверить входные данные в общедоступном интерфейсе для вашего кода, потому что вы не обязательно знаете, кто будет вызывать ваш код. Давайте посмотрим на метод drive() самоуправляемого автомобиля. Этот метод ожидает параметр «назначение». Параметр «destination» будет использоваться позже в навигации, но метод drive ничего не делает для проверки его правильности.

Предположим, что пункт назначения должен быть кортежем широты и долготы. Существуют всевозможные тесты, которые можно выполнить, чтобы убедиться, что он действителен (например, пункт назначения находится посреди моря). Для наших целей давайте просто удостоверимся, что это кортеж с плавающей запятой в диапазоне от 0,0 до 90,0 для широты и от -180,0 до 180,0 для долготы.

Вот обновленный класс SelfDrivingCar. Я тривиально реализовал некоторые из нереализованных методов, потому что метод drive() прямо или косвенно вызывает некоторые из этих методов.

class SelfDrivingCar(object):
 
    def __init__(self, object_detector):
        self.object_detector = object_detector
        self.speed = 0
        self.destination = None
 
    def _accelerate(self):
        self.speed += 1
 
    def _decelerate(self):
        if self.speed > 0:
            self.speed -= 1
 
    def _advance_to_destination(self):
        distance = self._calculate_distance_to_object_in_front()
        if distance < 10:
            self.stop()
 
        elif distance < self.speed / 2:
            self._decelerate()
 
        elif self.speed < self._get_speed_limit():
            self._accelerate()
 
    def _has_arrived(self):
        return True
 
    def _calculate_distance_to_object_in_front(self):
        return self.object_detector.calculate_distance_to_object_in_front()
 
    def _get_speed_limit(self):
        return 65
 
    def stop(self):
        self.speed = 0
 
    def drive(self, destination):
        self.destination = destination
        while not self._has_arrived():
            self._advance_to_destination()
        self.stop()

Чтобы протестировать обработку ошибок в тесте, я передам недопустимые аргументы и проверю, что они правильно отклонены. Вы можете сделать это, используя метод self.assertRaises() модуля unittest.TestCase. Этот метод работает успешно, если тестируемый код действительно вызывает исключение.

Давайте посмотрим на это в действии. Метод test_drive() передает широту и долготу за пределы допустимого диапазона и ожидает, что метод drive() вызовет исключение.

import unittest
 
from car import SelfDrivingCar
 
class MockObjectDetector(object):
    def calculate_distance_to_object_in_front(self):
        return 20
 
class SelfDrivingCarTest(unittest.TestCase):
    def setUp(self):
        self.car = SelfDrivingCar(MockObjectDetector())
 
    def test_stop(self):
        self.car.speed = 5
        self.car.stop()
 
        # Verify the speed is 0 after stopping
        self.assertEqual(0, self.car.speed)
 
        # Verify it is Ok to stop again if the car is already stopped
        self.car.stop()
        self.assertEqual(0, self.car.speed)
 
    def test_drive(self):
        # Valid destination
        self.car.drive((55.0, 66.0))
 
        # Invalid destination wrong range
        self.assertRaises(Exception, self.car.drive, (-55.0, 200.0))

Тест не пройден, потому что метод drive() не проверяет свои аргументы на корректность и не вызывает исключения. Вы получаете красивый отчет с полной информацией о том, что не удалось, где и почему.

python -m unittest discover -v
 
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... FAIL
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok
 
======================================================================
FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)
----------------------------------------------------------------------
 
Traceback (most recent call last):
  File "/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py", line 29, in test_drive
    self.assertRaises(Exception, self.car.drive, (-55.0, 200.0))
AssertionError: Exception not raised
 
----------------------------------------------------------------------
Ran 2 tests in 0.000s
 
FAILED (failures=1)

Чтобы исправить это, давайте обновим метод drive(), чтобы он действительно проверял диапазон его аргументов:

def drive(self, destination):
 
        lat, lon = destination
        if not (0.0 <= lat <= 90.0):
            raise Exception('Latitude out of range')
        if not (-180.0 <= lon <= 180.0):
            raise Exception('Latitude out of range')
 
        self.destination = destination
        while not self._has_arrived():
            self._advance_to_destination()
        self.stop()

Теперь все тесты проходят.

python -m unittest discover -v
 
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok
 
----------------------------------------------------------------------
Ran 2 tests in 0.000s
 
OK

Тестирование частных методов

Стоит ли тестировать каждую функцию и метод? В частности, следует ли тестировать приватные методы, вызываемые только вашим кодом? Обычно неудовлетворительный ответ: «Это зависит».

Постараюсь быть здесь полезным и рассказать, от чего это зависит. Вы точно знаете, кто вызывает ваш частный метод — это ваш собственный код. Если ваши тесты для общедоступных методов, которые вызывают ваш частный метод, являются исчерпывающими, то вы уже исчерпывающе протестировали свои частные методы. Но если приватный метод очень сложен, вы можете протестировать его самостоятельно. Используйте свое суждение.

Как организовать модульные тесты.

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

Вот система, которую я использую. Тесты должны быть полностью отделены от тестируемого кода (поэтому я не использую doctest). В идеале ваш код должен быть в пакете. Тесты для каждого пакета должны находиться в родственном каталоге вашего пакета. В каталоге тестов должен быть один файл для каждого модуля вашего пакета с именем test_<module name>.

Например, если в вашем пакете три модуля: module_1.py, module_2.py и module_3.py, у вас должно быть три тестовых файла: test_module_1.py, test_module_2.py и test_module_3.py в каталоге с тестами.

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

Тестирование в приложениях Django

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

Когда вы создаете приложение Django в новом проекте Django, Django предоставляет файл test.py, в котором должны находиться все ваши тесты. Реализация тестов в вашем приложении Django экономит время и помогает командам работать вместе.

Ниже приведен простой пример тестов из документации Django с использованием модуля unittest.

from django.test import TestCase
from myapp.models import Animal
# Create your tests here.
 
 
class AnimalTestCase(TestCase):
    def setUp(self):
        Animal.objects.create(name="lion", sound="roar")
        Animal.objects.create(name="cat", sound="meow")
 
    def test_animals_can_speak(self):
        """Animals that can speak are correctly identified"""
        lion = Animal.objects.get(name="lion")
        cat = Animal.objects.get(name="cat")
        self.assertEqual(lion.speak(), 'The lion says "roar"')
        self.assertEqual(cat.speak(), 'The cat says "meow"')

Чтобы запустить приведенные выше тесты в приложении Django, используйте команду manage.py.

python manage.py test

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

python manage.py test app_name

Вывод

Модульные тесты — это основа надежного кода. В этом руководстве я изучил некоторые принципы и рекомендации по модульному тестированию и объяснил причины нескольких лучших практик. Чем больше система, которую вы создаете, тем важнее становятся модульные тесты. Но юнит-тестов недостаточно. Для больших систем необходимы и другие типы тестов: интеграционные тесты, тесты производительности, нагрузочные тесты, тесты на проникновение, приемочные тесты и т. д.

Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу