Тестирование в Python [unittest]. Часть 2. TestCase

Продолжаем изучать unittestframework для тестирования в Python. В этой статье сосредоточимся на общем обзоре основных структурных элементов unittest (test casetest suite, test runner), рассмотрим способы запуска тестов и подробно остановимся на классе TestCase.

  1. Основные структурные элементы unittest.
  2. Запуск тестов из командной строки и с использованием графического приложения.
  3. Работа с TestCase.

Основные структурные элементы unittest

unittest – это framework для тестирования в Python, который позволяет разрабатывать автономные тесты, собирать тесты в коллекции, обеспечивает независимость тестов от framework’а отчетов и т.д. Основными структурными элемента каркаса unittest являются:

Test fixture

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

Test case

Test case – это элементарная единица тестирования, в рамках которой проверяется работа компонента тестируемой программы (метод, класс, поведение и т.п.). Для реализации этой сущности используется класс TestCase.

Test suite

Test suite – это коллекция тестов, которая может в себя включать как отдельные test case’ы так и целые коллекции (т.е. можно создавать коллекции коллекций). Коллекции используются с целью объединения тестов для совместного запуска.

Test runner

Test runner – это компонент, которые оркестрирует (координирует взаимодействие) запуск тестов и предоставляет пользователю результат их выполнения. Test runner может иметь графический интерфейс, текстовый интерфейс или возвращать какое-то заранее заданное значение, которое будет описывать результат прохождения тестов.

Вся работа по написанию тестов заключается в том, что мы разрабатываем отдельные тесты в рамках test case’ов, собираем их в модули и запускаем, если нужно объединить несколько test case’ов, для их совместного запуска, они помещаются в test suite’ы, которые помимо test case’ов могут содержать другие test suite’ы.

Запуск тестов из командной строки и с использованием графического приложения

Запуск тестов можно сделать как из командной строки, так и с помощью графического интерфейса пользователя (GUI), рассмотрим каждый из этих способов более подробно. В качестве примера приложения, будет выступать utest_calc.py из предыдущей статьи.

Интерфейс командной строки (CLI)

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

Запуск всех тестов в модуле utest_calc.py.

> python -m unittest test_calc.py

Запуск тестов из класса CalcTest.

> python -m unittest utest_calc.CalcTest

Запуск теста test_sub().

> python -m unittest utest_calc.CalcTest.test_sub

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

> python -m unittest -v utest_calc.py

Если осуществить запуск без указания модуля с тестами, будет запущен Test Discovery.

> python -m unittest

Более подробно о Test Discovery будет рассказано в одной из следующих частей. Справку по ключам запуска можно получить из документации.

Графический интерфейс пользователя (GUI)

Для запуска и анализа результатов работы тестов можно использовать GUI. Списко инструментов доступен здесь, но он далеко не полный. Для пример рассмотрим работу с Cricket. Для установки Cricket  можно воспользоваться менеджером pip:

> pip install cricket

После этого на ваш компьютер будет установлен cricket-unittest.

Для запуска тестов в данном приложении, перейдите в каталог с вашим тестирующим кодом и в командной строке запустите cricket-unittest, для этого просто наберите это название и нажмите Enter.

> cricket-unittest

Приложение, при запуске, автоматически загрузит тесты.

python unittest GUI

Для запуска тестов нажмите “Run all”. Как видно, все тесты завершились удачно – они окрасились в зеленый цвет.

python unittest GUI (test OK)

Работа с TestCase

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

Приведем еще раз класс CalcTest из предыдущего урока:

import unittest
import calc
 
class CalcTests(unittest.TestCase):
    def test_add(self):
        self.assertEqual(calc.add(1, 2), 3)
        
    def test_sub(self):
        self.assertEqual(calc.sub(4, 2), 2)
        
    def test_mul(self):
        self.assertEqual(calc.mul(2, 5), 10)
        
    def test_div(self):
        self.assertEqual(calc.div(8, 4), 2)

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

Для того, чтобы была возможность использовать компоненты unittest (в том числе и TestCase), в самом начале программы нужно импортировать модуль unittest стандартным образом.

При выборе имени класса наследника от TestCase можете руководствоваться следующим правилом: [ИмяТестируемойСущности]Tests. [ИмяТестируемойСущности] – это некоторая логическая единица, тесты для которой нужно написать. В нашем случае – это калькулятор, поэтому мы выбрали имя CalcTests. Если бы у нашего калькулятора был большой набор поддерживаемых функций, то тестирование простых функций (сложение, вычитание, умножение и деление) можно было бы вынести в отдельный класс и назвать его например так: CalcSimpleActionsTests. При написании программ на Python старайтесь придерживаться PEP 8 — Style Guide for Python Code – это рекомендации по стилевому оформлению кода.

Для того, чтобы метод класса выполнялся как тест, необходимо, чтобы он начинался со слова test. Несмотря на то, что методы framework’а unittest написаны не в соответствии с PEP 8 (ввиду того, что идейно он наследник xUnit), мы все же рекомендуем следовать правилам стиля для Python везде, где это возможно. Поэтому имена тестов будем начинать с префикса test_. Далее, под словом тест будем понимать метод класса-наследника от TestCase, который начинается с префикса test_.

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

  • методы, используемые при запуске тестов;
  • методы, используемые при непосредственном написании тестов (проверка условий, сообщение об ошибках);
  • методы, позволяющие собирать информацию о самом тесте.

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

Методы, используемые при запуске тестов.

К этим методам относятся:

setUp()

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

tearDown()

Метод вызывается после завершения работы теста. Используется для “приборки” за тестом. 

Заметим, что методы setUp() и tearDown() вызываются для всех тестов в рамках класса, в котором они переопределены. По умолчанию, эти методы ничего не делают. Если их добавить в utest_calc.py, то перед [после] тестов test_add(), test_sub(), test_mul(), test_div() будут выполнены setUp() [tearDown()].

unittest TestCase setUp/tearDown work

setUpClass()

Метод действует на уровне класса, т.е. выполняется перед запуском тестов класса. При этом синтаксис требует наличие декоратора @classmethod.

@classmethod
def setUpClass(cls):
    ...

tearDownClass()

Запускается после выполнения всех методов класса, требует наличия декоратора @classmethod.

@classmethod
def tearDownClass(cls):
    ...

skipTest(reason)

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

Методы, используемые при непосредственном написании тестов (проверка условий, сообщение об ошибках).

TestCase класс предоставляет набор assert-методов для проверки и генерации ошибок:

assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)

Assert’ы для контроля выбрасываемых исключений и warning’ов:

assertRaises(exc, fun, *args, **kwds) Функция fun(*args, **kwds) вызывает исключение exc
assertRaisesRegex(exc, r, fun, *args, **kwds) Функция fun(*args, **kwds) вызывает исключение exc, сообщение которого совпадает с регулярным выражением r
assertWarns(warn, fun, *args, **kwds) Функция fun(*args, **kwds) выдает сообщение warn
assertWarnsRegex(warn, r, fun, *args, **kwds) Функция fun(*args, **kwds) выдает сообщение warn и оно совпадает с регулярным выражением r

Assert’ы для проверки различных ситуаций:

assertAlmostEqual(a, b) round(a-b, 7) == 0
assertNotAlmostEqual(a, b) round(a-b, 7) != 0
assertGreater(a, b) a > b
assertGreaterEqual(a, b) a >= b
assertLess(a, b) a < b
assertLessEqual(a, b) a <= b
assertRegex(s, r) r.search(s)
assertNotRegex(s, r) not r.search(s)
assertCountEqual(a, b) a и b имеют одинаковые элементы (порядок неважен)

Типо-зависимые assert’ы, которые используются при вызове assertEqual(). Приводятся на тот случай, если необходимо использовать конкретный метод.

assertMultiLineEqual(a, b) строки (strings)
assertSequenceEqual(a, b) последовательности (sequences)
assertListEqual(a, b) списки (lists)
assertTupleEqual(a, b) кортежи (tuplse)
assertSetEqual(a, b) множества или неизменяемые множества (frozensets)
assertDictEqual(a, b) словари (dicts)

Дополнительно хотелось бы отметить метод fail().

fail(msg=None)

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

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

countTestCases()

Возвращает количество тестов в объекте класса-наследника от TestCase.

id()

Возвращает строковый идентификатор теста. Как правило это полное имя метода, включающее имя модуля и имя класса.

shortDescription()

Возвращает описание теста, которое представляет собой первую строку docstring’а метода, если его нет, то возвращает None.

Расширим код нашего тестового проекта utest_calc.py, так чтобы показать некоторые из возможностей, которые предоставляет класс TestCase.

import unittest
import calc


class CalcTest(unittest.TestCase):
   """Calc tests"""

   @classmethod
   def setUpClass(cls):
       """Set up for class"""
       print("setUpClass")
       print("==========")

   @classmethod
   def tearDownClass(cls):
       """Tear down for class"""
       print("==========")
       print("tearDownClass")

   def setUp(self):
       """Set up for test"""
       print("Set up for [" + self.shortDescription() + "]")

   def tearDown(self):
       """Tear down for test"""
       print("Tear down for [" + self.shortDescription() + "]")
       print("")

   def test_add(self):
       """Add operation test"""
       print("id: " + self.id())
       self.assertEqual(calc.add(1, 2), 3)

   def test_sub(self):
       """Sub operation test"""
       print("id: " + self.id())
       self.assertEqual(calc.sub(4, 2), 2)

   def test_mul(self):
       """Mul operation test"""
       print("id: " + self.id())
       self.assertEqual(calc.mul(2, 5), 10)

   def test_div(self):
       """Div operation test"""
       print("id: " + self.id())
       self.assertEqual(calc.div(8, 4), 2)


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

Запустив это модуль в командной строке:

> python -m unittest -v utest_calc.py

Получим следующий результат:

setUpClass
==========
test_add (simple_ex.CalcTest)
Add operation test ... Set up for [Add operation test]
id: simple_ex.CalcTest.test_add
Tear down for [Add operation test]
ok

test_div (simple_ex.CalcTest)
Div operation test ... Set up for [Div operation test]
id: simple_ex.CalcTest.test_div
Tear down for [Div operation test]
ok

test_mul (simple_ex.CalcTest)
Mul operation test ... Set up for [Mul operation test]
id: simple_ex.CalcTest.test_mul
Tear down for [Mul operation test]
ok

test_sub (simple_ex.CalcTest)
Sub operation test ... Set up for [Sub operation test]
id: simple_ex.CalcTest.test_sub
Tear down for [Sub operation test]
ok
==========
tearDownClass
----------------------------------------------------------------------
Ran 4 tests in 0.016s
OK

Как видно из примера, вначале был запущен метод setUpClass(), потом последовательно (в алфавитном порядке) были выполнены тесты, перед запуском каждого теста выполнялся метод setUp(), по окончании – tearDown()Каждый метод содержит docstring в виде комментария в первой строке. Для доступа к этому описанию использовался метод shortDescription(). В теле теста присутствует строка, печатающая идентификатор, получаемый с помощью функции id().

<<< Тестирование в Python [unittest]. Часть 1   Тестирование в Python [unittest]. Часть 3 >>>

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *