Этим уроком открывается цикл практических занятий по написанию тестов с применением различных инструментов.
unittest - инструмент для тестирования в Python. Это стандартный модуль для написания юнит-тестов на Python. Unittest это порт JUnit с Java. Иными словами, и в коде модуля, и при написании тестов легко прослеживается ООП стиль, что весьма удобно для тестирования процедур и классов.
Документация доступна по следующим ссылкам: python3, python2
В данном инструменте много возможностей: проверки (assert), декораторы, позволяющие пропустить отдельный тест (@skip, *@skipIf*) или обозначить сломанные тесты (@expectedFailure*) и этим не заканчивается список. Использование assert'ов с лихвой покрывает нужды при написании тестов.
Полезная черта unittest - автоматизированное тестирование. Есть и другие:
В использовании unittest присутствуют несколько концепций
test case -это наименьшая единица тестирования. Он проверяет конкретный ответ для конкретного набора входных данных.
test suite представляет собой сборник тестовых случаев, тестовых наборов. Используется для агрегирования тестов, которые должны выполняться вместе.
test fixture - это фиксированное состояние объектов используемых в качестве исходного при выполнении тестов.
Цель использования fixture - если у вас сложный test case, то подготовка нужного состояния легко может занимать много ресурсов (например, вы считаете функцию с определенной точностью и каждый следующий знак точности в расчетах занимает день). Используя fixture (на сленге - фикстуры) предварительную подготовку состояния пропускаем и сразу приступаем к тестированию.
Test fixture может выступать, например, в виде:
test runner - это компонент, который организует выполнение тестов и предоставляет результат пользователю.
При написании тестов следует исходить из следующих принципов:
К написанию тестов стоит относится также как и к основному коду.
Написание тестов является хорошей инвестицией в будущее программы:
Есть и другие причины писать тесты. В целом, практика показывает, что до тестов надо дорасти - в какой-то момент приходит понимание зачем же тратить на них время.
В качестве примеров использования unittest продемонстрирую и опишу основные возможности модуля. На мой взгляд это те 20% которые помогут сделать 80% результата.
Рассмотрим следующий код:
# -*- encoding: utf-8 -*-
import unittest
class TestUM(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_numbers_3_4(self):
self.assertEqual(3 * 4, 12)
def test_strings_a_3(self):
self.assertEqual('a' * 3, 'aaa')
if __name__ == '__main__':
unittest.main()
В данном примере показан общий шаблон для большинства тестов - здесь и наследование от TestCase, здесь и два простых теста, а также перегрузка встроенных в TestCase методов:
Список подобных готовых функций такой:
Если запустить скрипт:
python example.py
То получим:
python example1.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Теперь мы знаем как писать тест, как запускать. Перейдем к освещению вариантов использования тестов.
# -*- encoding: utf-8 -*-
import unittest
class TestUM(unittest.TestCase):
def testAssertTrue(self):
"""
Вызывает ошибку если значение аргумента != True
:return:
"""
self.assertTrue(True)
def testFailUnless(self):
"""
Устаревшее название для assertTrue()
Вызывает ошибку если значение аргумента != True
:return:
"""
self.failUnless(True)
def testFailIf(self):
"""
Устаревшая функция, теперь называется assertFalse()
:return:
"""
self.failIf(False)
def testAssertFalse(self):
"""
Если значение аргумент != False, то кидает ошибку
:return:
"""
self.assertFalse(False)
def testEqual(self):
"""
Проверка равенства двух аргументов
:return:
"""
self.failUnlessEqual(1, 3 - 2)
def testNotEqual(self):
"""
Проверка НЕ равенства двух аргументов
:return:
"""
self.failIfEqual(2, 3 - 2)
def testEqualFail(self):
"""
Ругается если значение аргументов равно
:return:
"""
self.failIfEqual(1, 2)
def testNotEqualFail(self):
"""
Ругается если значение аргументов не равно
:return:
"""
self.failUnlessEqual(2, 3 - 1)
def testNotAlmostEqual(self):
"""
Старое название функции.
Теперь называется assertNotAlmostEqual()
Сравнивает два аргумента с округлением, можно задать это округление
Ругается если значения равны
:return:
"""
self.failIfAlmostEqual(1.1, 3.3 - 2.0, places=1)
def testAlmostEqual(self):
"""
Старое название функции
Теперь называется assertAlmostEqual()
Сравнивает два аргумента с округлением, можно задать это округление
Ругается если значения не равны
:return:
"""
self.failUnlessAlmostEqual(1.1, 3.3 - 2.0, places=0)
if __name__ == '__main__':
unittest.main()
Прямо в док-строках описал когда выбрасывают ошибки эти проверки. В примере показаны как новые названия, так и их старые аналоги (в старых проектах такие еще встречаются)
Перейдем к более практическим примерам. На них и рассмотрим еще некоторые важные способности unittest
Приведу пример, как можно протестировать конструктор
# -*- encoding: utf-8 -*-
import unittest
class MyClass(object):
def __init__(self, foo):
if foo != 1:
raise ValueError("foo is not equal to 1!")
class MyClass2(object):
def __init__(self):
pass
class TestFoo(unittest.TestCase):
def testInsufficientArgs(self):
foo = 0
self.failUnlessRaises(ValueError, MyClass, foo)
def testArgs(self):
self.assertRaises(TypeError, MyClass2, ("fsa", "fds"))
if __name__ == '__main__':
unittest.main()
В коде видно, что есть класс, в конструкторе которого кидается исключение если значение аргумента не удовлетворяет условию. Это условие ловится функцией self.assertRaises или ее старым названием self.failUnlessRaises
Во втором примере показан тест на количество аргументов в классе.
Продемонстрирую пример тестирования структуры данных. В данном случае есть класс Point, в котором хранится два float’а x,y. А дальше весь код такой же как и для других тестов.
# -*- encoding: utf-8 -*-
import unittest
class Point(object):
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __str__(self):
return f"({self.x}, {self.y})"
def __eq__(self, other):
return True if ((self.x == other.x) and (self.y == other.y)) else False
def __ne__(self, other):
return True if ((self.x != other.x) or (self.y != other.y)) else False
class TestPoint(unittest.TestCase):
def setUp(self):
self.A = Point(5, 6)
self.B = Point(6, 10)
self.C = Point(5.0, 6.0)
self.D = Point(-5, -6)
def test_init(self):
self.assertEqual((self.A.x, self.A.y), (float(5), float(6)), "Полученные значения не являются вещественными!!!")
self.assertEqual((self.B.x, self.B.y), (float(6), float(10)),
"Полученные значения не являются вещественными!!!")
self.assertEqual((self.C.x, self.C.y), (float(5), float(6)), "Полученные значения не являются вещественными!!!")
self.assertEqual((self.D.x, self.D.y), (float(-5), float(-6)),
"Полученные значения не являются вещественными!!!")
def test_str(self):
self.assertTrue(str(self.A) == "(5.0, 6.0)", "Неправильный вывод на экран!!!")
self.assertTrue(str(self.B) == "(6.0, 10.0)", "Неправильный вывод на экран!!!")
self.assertTrue(str(self.C) == "(5.0, 6.0)", "Неправильный вывод на экран!!!")
self.assertTrue(str(self.D) == "(-5.0, -6.0)", "Неправильный вывод на экран!!!")
def test_eq(self):
self.assertTrue(self.A == self.C,
"Данные две точки равны, а в результате тестирования, они оказались неравными!!!")
self.assertFalse(self.A == self.B,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
self.assertFalse(self.A == self.D,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
def test_ne(self):
self.assertFalse(self.A != self.C,
"Данные две точки равны, а в результате тестирования, они оказались неравными!!!")
self.assertTrue(self.A != self.B,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
self.assertTrue(self.A != self.D,
"Данные две точки неравны, а в результате тестирования, они оказались равными!!!")
if __name__ == '__main__':
unittest.main()
Примера под тестирование БД не приведу, однако, расскажу об общих соображениях.
Работа с базой не так проста, как с обычной функцией, ведь база - это не просто программный код, база данных - это объект, сохраняющий своё состояние. И если мы начнём в процессе тестирования изменять данные в базе, то после каждого теста база будет изменяться. Это может помешать последующим тестам и необратимо испортить базу данных.
Ключ к решению проблемы - транзакции. Одна из особенностей этого механизма состоит в том, что до тех пор пока транзакция не завершена, вы всегда можете отменить все изменения и вернуть базу в состояние на момент начала транзакции. Алгоритм такой:
Даже если в тестируемом коде останутся незакрытые транзакции, внешний ROLLBACK всё равно откатит все изменения корректно.
Помимо транзакций стоит обратить внимание, что тестирование следует делать на тестовой базе данных. Идеальным случаем будет полная инициализация окружения перед исполнением теста.
В уроке описаны базовые элементы работы с unittest. Не были затронуты статусы тестов, отчеты о тестировании. Однако, данной теории хватит для написания тестов. Давайте это сейчас и проверим, переходите к практике.
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Статья «Ловушки обучения»
Вебинар «Как самостоятельно учиться»
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт