Разделы портала

Онлайн-тренинги

.
Основы тестирования на Python: unittest
08.04.2021 00:00

Автор: Энди Найт (Andy Knight)
Оригинал статьи
Перевод: Ольга Алифанова

Обзор

unittest – это стандартный Python-фреймворк для юнит-тестов. Создатели вдохновлялись JUnit, и он включается в стандартный дистрибутив CPython. unittest содержит базовый класс TestCase, дающий методы для утверждений и рутин настройки и очистки. Все классы тест-кейсов должны наследоваться от TestCase. Каждый метод в подклассе TestCase, чье имя начинается с "test", будет прогоняться как тест-кейс. Тесты можно группировать и загружать с использованием класса TestSuite и методов загрузки – используя их совместно, можно создавать свои собственные тест-загрузчики. unittest также может генерировать XML-отчеты (как и JUnit), используя unittest-xml-reporting.

unittest поддерживается и в Python 2, и в Python 3. Однако для версий старше Python 2.7 нужно пользоваться портированием unittest2.

Установка

Базовый unittest не нуждается в отдельной установке, потому что поставляется вместе с Python. Дополнительные модули можно при необходимости установить через pip.

> pip install unittest2
> pip install unittest-xml-reporting

Структура проекта

Модули кода продукта и тест—код unittest  должны находиться в разных пакетах Python внутри одного проекта. Тест-модули должны иметь имена test_*.py, и должны находиться в пакетах, чтобы они были обнаружены при запуске тестов. Помните, что пакет Python – это просто директория с файлом по имени “__init__.py“.

Product code modules and unittest test code modules should be placed into separate Python packages within the same project. Test modules must be named “test_*.py” and must be put into packages in order for discovery to work when launching tests. Remember, a Python package is simply a directory with a file named “__init__.py“.

[project root directory]
|‐‐ [product code packages]
`‐‐ tests
|‐‐ __init__.py
`‐‐ test_*.py

Пример кода

Демо-проект по имени example-py-unittest находится в моем репозитории GitHub python-testing-101. Структура проекта такова:

example-py-unittest
|-- com.automationpanda.example
|   |-- __init__.py
|   `-- calc.py
|-- com.automationpanda.tests
|   |-- __init__.py
|   `-- test_calc.py
`-- README.md

Модуль com.automationpanda.example.calc содержит класс Calculator с базовыми математическими методами.

class Calculator(object):
def __init__(self):
self._last_answer = 0.0
    @property
def last_answer(self):
return self._last_answer
    def add(self, a, b):
self._last_answer = a + b return self.last_answer
    def subtract(self, a, b):
        self._last_answer = a - b
        return self.last_answer
    def multiply(self, a, b):
self._last_answer = a * b
return self.last_answer
    def divide(self, a, b):
# automatically raises ZeroDivisionError
self._last_answer = a * 1.0 / b
return self.last_answer

Модуль com.automationpanda.tests.test_calc содержит подкласс unittest.TestCase, показанный ниже. Тест-класс использует метод setup для создания объекта Calculator, используемого в каждом методе. Методы утверждений – это assertEqual и assertRaises. Свежая копия CalculatorTest создается для запуска каждого тест-метода.

from com.automationpanda.example.calc import Calculator
NUMBER_1 = 3.0
NUMBER_2 = 2.0
FAILURE = 'incorrect value'

class CalculatorTest(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
def test_last_answer_init(self):
value = self.calc.last_answer
self.assertEqual(value, 0.0, FAILURE)
    def test_add(self):
value = self.calc.add(NUMBER_1, NUMBER_2)
self.assertEqual(value, 5.0, FAILURE)
self.assertEqual(value, self.calc.last_answer, FAILURE)
    def test_subtract(self):
value = self.calc.subtract(NUMBER_1, NUMBER_2)
self.assertEqual(value, 1.0, FAILURE)
self.assertEqual(value, self.calc.last_answer, FAILURE)
    def test_subtract_negative(self):
value = self.calc.subtract(NUMBER_2, NUMBER_1)
self.assertEqual(value, -1.0, FAILURE)
self.assertEqual(value, self.calc.last_answer, FAILURE)
    def test_multiply(self):
value = self.calc.multiply(NUMBER_1, NUMBER_2)
self.assertEqual(value, 6.0, FAILURE)
self.assertEqual(value, self.calc.last_answer, FAILURE)
    def test_divide(self):
value = self.calc.divide(NUMBER_1, NUMBER_2)
self.assertEqual(value, 1.5, FAILURE)
self.assertEqual(value, self.calc.last_answer, FAILURE)
def test_divide_by_zero(self):
self.assertRaises(ZeroDivisionError, self.calc.divide, NUMBER_1, 0)

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

Для запуска тестов из командной строки измените директорию на корневую директорию проекта и запустите модуль unittest напрямую из команды python:

# Найти и прогнать все тесты проекта

> python -m unittest discover

# Запустить все тесты модуля

> python -m unittest com.automationpanda.tests.test_calc

# Запустить все тесты для тест-класса

> python -m unittest com.automationpanda.tests.test_calc.CalculatorTest

# Запустить все тесты в конкретном Python-файле

> python -m unittest com/automationpanda/tests/test_calc.py

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

> python -m unittest discover
.............
----------------------------------------------------------------------
Ran 13 tests in 0.002s
OK

Чтобы создать XML-отчеты, установите unittest-xml-reporting и добавьте следующую "main"-логику в конец модуля тест-кейса. Пример ниже создаст и сохранит XML-отчет в директорию по имени "test reports".

if __name__ == '__main__':
import xmlrunner
    unittest.main(
testRunner=xmlrunner.XMLTestRunner(output='test-reports'),
failfast=False,
buffer=False,
catchbreak=False)

Давайте запустим тест-модуль напрямую из командной строки.

# Запуск тест-модуля напрямую
# Делайте это, если для запуска теста прописана "main"-логика
# Примеры: XML-файл результатов, кастомные наборы тестов
> python -m com.automationpanda.tests.test_calc

За и против

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

Я рекомендую использовать unittest, если вам нужен базовый фреймворк для юнит-тестов без дополнительных зависимостей. В прочих случаях есть фреймворки получше – например, pytest.

Обсудить в форуме