unittest

Contents
Введение
Пример применения
Структура проекта
Тестируем решение квадратного уравнения
Добавить unittest в PyCharm
Похожие статьи

Введение

Unittest это библиотека для тестирования, которая входит в Python по умолчанию

Он содержит и тестовую среду, и Test Runner. У unittest есть ряд требований для написания и выполнения тестов:

*имеется в виду, что в unittest нужно для каждого типа утверждений использовать свой метод, например:

assertEqual - чтобы утвердить равенство
assertNotEqual - чтобы утвердить неравенство
assertTrue - чтобы утвердить истинность

И так далее, подробности на сайте docs.python.org

Этим unittest отличается от, например, PyTest , где утверждение делается всегда одинаково - с помощью assert

Разберём простейший пример использования.

Создадим рабочую директорию app, файл calc.py в корне и файл test_calc.py в поддиректории tests

Также создадим и активируем виртуальное окружение

python -m venv venv
source venv/bin/activate

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

app ├── calc.py ├── tests │   └── test_calc.py └── venv ├── bin ├── include ├── lib ├── lib64 -> lib └── pyvenv.cfg

# calc.py def add(x, y): """Add Function""" return x + y + 3 def subtract(x, y): """Subtract Function""" return x - y def multiply(x, y): """Multiply Function""" return x * y def divide(x, y): """Divide Function""" if y == 0: raise ValueError("Can not divide by zero!") return x / y

Как видите, в функции add() специально допущена ошибка - вместо сложения двух переменных к ним ещё добавляется число 3

# test_calc.py import unittest import calc # https://docs.python.org/3/library/unittest.html#unittest.TestCase.debug class TestCalc(unittest.TestCase): def test_add(self): result = calc.add(10, 5) self.assertEqual(result, 15) # python3 -m unittest tests/test_calc.py

Для запуска теста перейдём в директорию с файлами и в консоли выполним команду

python -m unittest tests/test_calc.py

F ====================================================================== FAIL: test_add (tests.test_calc.TestCalc) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/andrei/python/unittest/app/tests/test_calc.py", line 12, in test_add self.assertEqual(result, 15) AssertionError: 18 != 15 ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1)

Исправим ошибку

# calc.py def add(x, y): """Add Function""" return x + y ...

python3 -m unittest test_calc.py

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

Чтобы запустить этот тест в PyCharm или запускать его из консоли, но без дополнительного указания -m unittest добавим в конец файла test_calc.py две строчки:

# test_calc.py ... if __name__ == '__main__': unittest.main()

Теперь можно запускать тест командой

python -m tests.test_calc

Напишем тесты для всех функций из calc.py

Снова специально допустим ошибку, например, в третьем тесте

import unittest import calc class TestCalc(unittest.TestCase): def test_add(self): self.assertEqual(calc.add(10, 5), 15) self.assertEqual(calc.add(-1, 1), 0) self.assertEqual(calc.add(-1, -1), -2) def test_subtract(self): self.assertEqual(calc.subtract(10, 5), 5) self.assertEqual(calc.subtract(-1, 1), -2) self.assertEqual(calc.subtract(-1, -1), 0) def test_multiply(self): self.assertEqual(calc.multiply(10, 5), 70) self.assertEqual(calc.multiply(-1, 1), -1) self.assertEqual(calc.multiply(-1, -1), 1) def test_divide(self): self.assertEqual(calc.divide(10, 5), 2) self.assertEqual(calc.divide(-1, 1), -1) self.assertEqual(calc.divide(-1, -1), 1) if __name__ == '__main__': unittest.main()

Запустим тест

python3 test_calc.py

..F. ====================================================================== FAIL: test_multiply (__main__.TestCalc) ---------------------------------------------------------------------- Traceback (most recent call last): File "C:/Users/username/.PyCharmCE2018.3/config/scratches/test_calc.py", line 17, in test_multiply self.assertEqual(calc.multiply(10, 5), 70) AssertionError: 50 != 70 ---------------------------------------------------------------------- Ran 4 tests in 0.001s FAILED (failures=1) Process finished with exit code 1

Обратите внимание на первую строчку, точки означают успешное выполнение теста. F - провал теста.

..F. означает, что первый, второй и четвёртый тесты прошли успешно, а в третьем ошибка

Assertion который вернул FALSE также видно

self.assertEqual(calc.multiply(10, 5), 70)

И ошибка AssertionError: 50 != 70

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

self.assertEqual(calc.multiply(10, 5), 70)

Меняем на

self.assertEqual(calc.multiply(10, 5), 50)

Запустим тест

python3 test_calc.py

.... ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK Process finished with exit code 0

Тестируем решение квадратного уравнения

Создадим ещё два файла quadratic.py и test_quadratic.py

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

app ├── calc.py ├── __pycache__ ├── quadratic.py ├── tests │   ├── __pycache__ │   ├── test_calc.py │   └── test_quadratic.py └── venv ├── bin ├── include ├── lib ├── lib64 -> lib └── pyvenv.cfg

Квадратные уравнения это уравнения вида

a*x^2 + b*x + c

x^2 и x подразумеваются по умолчанию, поэтому достаточно задать a, b и c - и сразу станет понятно как выглядит квадратное уравнение.

Первым делом проверим, что a, b и c это числа

def quadratic_solve(a ,b, c): if not all( map( lambda p: isinstance(p, (int, float)), (a, b, c) ) ): raise TypeError("Not valid argument type") print("Types are OK")

Здесь я использовал функции: all() , map() и лямбда функцию

Если что-то неясно - перейдите по ссылкам на функции либо постетите раздел «Функции»

Пример теста

# test_quadratic.py import unittest from quadratic import quadratic_solve class TestQuadratic(unittest.TestCase): def test_raises_type_error(self): try: # Специально передаём строку quadratic_solve("", 1, 1.5) except TypeError as err: print("\nOK, caught type error", err) else: self.fail("NOK, missed type error")

python -m unittest -v tests/test_quadratic.py

test_raises_type_error (tests.test_quadratic.TestQuadratic) ... OK, caught type error Not valid argument type ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK

Тест можно переписать в более нативном виде - без try и except. unittest уже продумал такую ситуацию

def test_raises_type_error(self): with self.assertRaises(TypeError): quadratic_solve("", 1, 1.5)

Теперь можно дописать код решения квадратного уравнения

from math import sqrt def quadratic_solve(a ,b, c): if not all( map( lambda p: isinstance(p, (int, float)), (a, b, c) ) ): raise TypeError("Not valid argument type") print("Types are OK") if a == 0: if b == 0: # a и b 0: решения нет return None, None return -c / b, None d = b ** 2 - 4 * a * c if d < 0: return None, None d_root = sqrt(d) divider = 2 * a x1 = (-b + d_root) / divider x2 = (-b - d_root) / divider if d == 0: x2 = None elif x2 > x1: x1, x2 = x2, x1 return x1, x2

И написать тесты на само решение

# test_quadratic.py import unittest from quadratic import quadratic_solve class TestQuadratic(unittest.TestCase): def test_raises_type_error(self): with self.assertRaises(TypeError): quadratic_solve("", 1, 1.5) def test_result_is_tuple(self): res = quadratic_solve(0, 0, 0) self.assertIsInstance(res, tuple) def test_zero_a_and_b(self): res = quadratic_solve(0, 0, 1) self.assertEqual(res, (None, None)) def test_two_roots(self): res = quadratic_solve(1, -1, -2) self.assertEqual(res, (2.0, -1.0)) def test_single_root(self): res = quadratic_solve(1, -2, 1) self.assertEqual(res, (2.0, None))

test_raises_type_error (tests.test_quadratic.TestQuadratic) ... ok test_result_is_tuple (tests.test_quadratic.TestQuadratic) ... Types are OK ok test_single_root (tests.test_quadratic.TestQuadratic) ... Types are OK ok test_two_roots (tests.test_quadratic.TestQuadratic) ... Types are OK ok test_zero_a_and_b (tests.test_quadratic.TestQuadratic) ... Types are OK ok ---------------------------------------------------------------------- Ran 5 tests in 0.000s OK

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

Несколько тестов состоят в том, что в одну и ту же функцию передаётся какое-то значение и затем результат сравнивается с эталоном. Избавиться от лишнего кода поможет параметризация тестов. С этим хорошо справляется PyTest

Теория для unittest

Для тех, кто интересуется устарел ил unittest или нет, моё скромное мнение состоит в том, что нет.

В мире Python более модным считается PyTest одна из причин - более простой синтаксис.

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

Test Fixture

Единичный тест называется тест кейсом (Test Case).

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

Например, получить один и тот же объект или запустить Selenium Webdriver или что-то другое.

Чтобы не писать в каждом тест-кейсе одно и то же можно воспользоваться методами setUp и tearDown которые создаются один раз для каждого класса и будут запускаться перед каждым тест-кейсом.

Такая комбинация setUp + tearDown называется Test Fixture

Test Fixture = setUp + tearDown

Составляющие части любого теста

Порядок выполнения тестов обычно следующий:

Подготовка к тесту

Непосредственное действие, например, запуск определённой функции

Проверка результата на соответствие ожиданию.

Arrange → Act → Assert

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

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

Related Articles
Тестирование
Python
Banner Image

Search on this site

Subscribe to @aofeed channel for updates

Visit Channel

@aofeed

Feedbak and Questions in Telegram

@aofeedchat