Тестирование в Python [unittest]. Часть 1. Введение

Тестирование в Python [unittest]. Часть 1. Введение

Nuances of programming

Автономное тестирование. Основные понятия

Трудно представить какой-то современный программный проект без тестирования. При этом тестирование осуществляется практических на всех этапах разработки продукта: начиная, непосредственно, с процесса создания функций, методов и классов и т.д., когда пишутся unit-тесты (а иногда и раньше, в случае, если используется TDD), и заканчивая функциональным и нагрузочным тестированием уже готового, развернутого продукта.

В рамках данного цикла статей, мы остановимся только на автономном тестировании. В качестве определения данного понятия воспользуемся тем, что дает Рой Ошероув в своей книге “Искусство автономного тестирования с примерами на C#”: автономный тест – это автоматизированная часть кода, которая вызывает тестируемую единицу работы и затем проверяет некоторые предположения о единственном конечном результате этой единицы. В качестве тестируемый единицы, в данном случае, может выступать как отдельный метода (функция), так и совокупность классов (или функций). Идея автономной единицы в том, что она представляет собой некоторую логически законченную сущность вашей программы. Автономное тестирование ещё называют модульным или unit-тестированием (unit-testing). Здесь и далее под словом тестирование будет пониматься именно автономное тестирование.

Важной характеристикой unit-теста является его повторяемость, т.е. результат его работы не зависит от окружения (внешнего мира), если же приходится обращаться к внешнему миру в процессе выполнения теста, то необходимо предусмотреть возможность подмены “мира” какой-то статичной сущностью.

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

Framework’и для проведения автономного тестирования в Python

В мире Python существуют три framework’а, которые получили наибольшее распространение:

  • unittest
  • nose
  • pytest

unittest

unittest – это framework для тестирования, входящий в стандартную библиотеку языка Python. Его архитектура выполнена в стиле xUnitxUnit представляет собой семейство framework’ов для тестирования в разных языках программирования, в Java – это JUnitC# – NUnit и т.д. Если вы уже сталкивались с данным каркасов в других языках, то это упростит понимание unittest. Т.к. данный цикл статей будет построен вокруг unittest, то мы не будет сейчас подробно на нем останавливаться.

nose

Девизом nose является фраза “nose extends unittest to make testing easier”, что можно перевести как “nose расширяет unittest, делая тестирование проще”. nose идеален, когда нужно сделать тесты “по-быстрому”, без предварительного планирования и выстраивания архитектуры приложения с тестами. Функционал nose можно расширять и настраивать с помощью плагинов.

pytest

pytest довольно мощный инструмент для тестирования, и многие разработчики оставляют свой выбор на нем. pytest по “духу” ближе к языку Python нежели unittest. Как было сказано выше, unittest в своей базе – xUnit, что накладывает определенные обязательства при разработке тестов (создание классов-наследников от unittest.TestCase, выполнение определенной процедуры запуска тестов и т.п.). При разработке на pytest ничего этого делать не нужно, вы просто пишете функции, которые должны начинаться с “test_” и используете assert’ы, встроенные в Python (unittest используется свои). У pytest есть ещё много интересных и полезных особенностей, но для их разбора понадобится отдельный цикл статей).

Пример тестирования приложения без framework’а

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

Модуль calc.py:

def add(a, b):
    return a + b
    
def sub(a, b):
    return a-b
 
def mul(a, b):
    return a * b
 
def div(a, b):
    return a / b


Для того, чтобы протестировать эту библиотеку, мы можем создать отдельный файл с названием test_calc.py и поместить туда функции, которые проверяют корректность работы функций из calc.py. Сделаем это.

Содержимое test_calc.py:

import calc
 
def test_add():
    if calc.add(1, 2) == 3:
        print("Test add(a, b) is OK")
    else:
        print("Test add(a, b) is Fail")
        
def test_sub():
    if calc.sub(4, 2) == 2:
        print("Test sub(a, b) is OK")
    else:
        print("Test sub(a, b) is Fail")
 
def test_mul():
    if calc.mul(2, 5) == 10:
        print("Test mul(a, b) is OK")
    else:
        print("Test mul(a, b) is Fail")
 
def test_div():
    if calc.div(8, 4) == 2:
        print("Test div(a, b) is OK")
    else:
        print("Test div(a, b) is Fail")       

test_add()
test_sub()
test_mul()
test_div()


Запустим test_calc.py.

python test_calc.py


В результате, в окне консоли, будет напечатано следующее:

Test add(a, b) is OK
Test sub(a, b) is OK
Test mul(a, b) is OK
Test div(a, b) is OK


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

Пример тестирования приложения с использованием unittest

Теперь посмотрим как можно было бы протестировать набор функций из calc.py с помощью unittest.

Для этого сделаем следующие действия:

1. Создадим файл с именем utest_calc.py

2. Добавим в него следующий код:

import unittest
import calc
 
class CalcTest(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()


3. Запустим файл utest_calc.py

python -m unittest utest_calc.py


Такой формат запуска предполагает вывод минимальной информации. В данном случае все тесты успешно завершились.

....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK


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

python -m unittest -v utest_calc.py


В этом случае результат будет таким:

test_add (test_calc_v2.CalcTest) ... ok
 test_div (test_calc_v2.CalcTest) ... ok
 test_mul (test_calc_v2.CalcTest) ... ok
 test_sub (test_calc_v2.CalcTest) ... ok
 ----------------------------------------------------------------------
 Ran 4 tests in 0.002s
 OK


На этом простом примере не видно всех преимуществ, которые дает unittest, по сравнению с вариантом без него. Для кого-то даже покажется лишним создание отдельных классов и запуск модулей с дополнительными ключами, но в более сложном случае, преимущества использования framework’а несомненны. В следующих статьях мы постараемся последовательно и подробно раскрыть вопросы написания автономных тестов с использованием unittest в Python.



Источник

Report Page