Когда только начинаешь писать код, стремишься сделать его рабочим. Но со временем понимаешь: недостаточно просто написать этот код. Хороший код надежен, прост в сопровождении и готов разрастаться. Сделать его таким и призвано модульное тестирование, благодаря которому меняется подход разработчиков к программированию, они избавляются от бесчисленных багов и бесконечной отладки.
Расскажем, что такое модульное тестирование, в чем его важность и как начать.
Что такое «модульное тестирование»?
Это процесс тестирования небольших изолированных частей кода, обычно называемых модулями. Так обеспечивается их корректная работа. Модуль — отдельная функция, метод или даже класс. Цель такого тестирования — обеспечить ожидаемое поведение каждой части кода отдельно, до их взаимодействия с другими частями системы.
Модульным тестированием проверяется качество каждого фрагмента кода. Если работа каждой части по отдельности корректна, шансов на беспроблемную работу всей программы гораздо больше.
Зачем проводить модульное тестирование?
Но не будет ли тестирование лишней работой? Поначалу так и кажется, но у модульного тестирования много преимуществ:
- Раннее выявление багов: модульными тестами обнаруживаются и исправляются баги, причем в месте возникновения последних. Их выявить легче, поскольку тестируются небольшие части кода.
- Написание качественного кода: для каждой функции пишется тест, поэтому получается четкий модульный код. Каждый модуль становится акцентированнее и понятнее.
- Уверенное внесение изменений: при наличии модульных тестов уверенно выполняется рефакторинг или расширение кода. Из тестов узнается о поломках, поэтому случайное появление багов исключено.
- Экономия времени: хотя на написание тестов изначально требуется время, в дальнейшем это оборачивается значительной его экономией. Модульными тестами баги выявляются заранее, поэтому многочасовую отладку сложного кода выполнять не приходится.
Приступаем к модульному тестированию
Попрактикуемся в модульном тестировании на примере со встроенной Python библиотекой unittest
. В большинстве языков имеются аналогичные фреймворки модульного тестирования, например JUnit в Java или Mocha в JavaScript, так что концепция в целом одинакова.
Этап 1. Написание простой функции для тестирования
Допустим, имеется функция для вычисления площади прямоугольника:
def calculate_area(width, height):
if width < 0 or height < 0:
raise ValueError("Width and height must be non-negative.")
return width * height
В этой функции площадь прямоугольника получается умножением ширины width
на длину height
. В ней же проверяется, что оба значения неотрицательны, в противном случае выдается ошибка.
Этап 2. Написание модульных тестов для функции
Теперь создадим для этой функции модульные тесты:
import unittest
class TestCalculateArea(unittest.TestCase):
def test_area_calculation(self):
# Тестирование обычного случая
self.assertEqual(calculate_area(5, 10), 50)
def test_zero_area(self):
# Пограничный случай: ширина или длина равна нулю
self.assertEqual(calculate_area(0, 10), 0)
def test_negative_input(self):
# Случай ошибки: отрицательная ширина или длина
with self.assertRaises(ValueError):
calculate_area(-5, 10)
if __name__ == "__main__":
unittest.main()
Разберем каждый тест:
test_area_calculation
: здесь тестируется обычный случай. Если ширинаwidth
5, а длинаheight
10, результат равен 50.test_zero_area
: здесь тестируется случай, когда одно из значений равно нулю, например ширина 0, длина 10. Результат 0, так как площадь с нулевой шириной равна нулю.test_negative_input
: этим тестом проверяется, не выдается ли функциейValueError
при вводе отрицательного значения, например ширины –5.
Каждый тест пишется как отдельный метод, который начинается с test_
. Благодаря такому соглашению об именовании он распознается и запускается фреймворком unittest
автоматически.
Этап 3. Запуск тестов
Сохраняем этот код в файле, например test_calculate_area.py
, и запускаем тесты такой командой терминала:
python test_calculate_area.py
Если все получится, увидите это:
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
Если тест не выполнится, в выводе указывается, какой тест не выполнился и почему. Так что можно вернуться и исправить его.
Рекомендации по написанию эффективных модульных тестов
Поработав с модульными тестами, мы усвоили ряд рекомендаций для плавного и эффективного тестирования.
1. Тестируйте в каждом тесте что-то одно
Каждый тест фокусируется на конкретном поведении или пограничном случае. Так проще понять, чем занимается каждый тест, и быстро обнаружить проблемы, если что-то ломается.
2. Давайте тестам четкие названия
По четким, содержательным названиям становится очевидным, что именно проверяется в каждом тесте. С такими названиями тесты интуитивно понятны, им не требуются комментарии.
3. Тестируйте пограничные случаи
Тестируйте не только happy path — случаи, которые ожидаемо оказываются рабочими. Проверяйте также пограничные случаи: ноль, отрицательные числа или очень большие входные данные. Так подтверждается, что код рабочий во всех сценариях.
4. Обеспечьте независимость тестов
Каждый тест выполняется независимо, не полагаясь ни на какие другие тесты. Если один не выполнится, это не скажется на остальных.
5. Проводите тесты регулярно
Сделайте тестирование частью вашей обычной рутины. Благодаря частому выполнению тестов баги выявляются заранее и устраняются, не успевая превратиться в крупные проблемы.
Реальные преимущества модульного тестирования
Начните писать тесты регулярно, и вы заметите серьезные преимущества. Вот ситуации, где действительно проявляется польза модульного тестирования:
- Добавление нового функционала: если при этом в коде что-то ломается, модульными тестами это немедленно обнаруживается.
- Рефакторинг: если нужно реорганизовать или оптимизировать код, модульными тестами обеспечивается, что вся его работа по-прежнему обходится без неожиданностей.
- Совместная работа: при взаимодействии в команде модульные тесты — это как страховочные сети, благодаря которым упрощается совместная работа и исключается появление багов.
Модульное тестирование поначалу кажется лишней работой, но это одна из лучших инвестиций в код. Тестируя каждую часть кода отдельно, настраиваешься на успех вдолгую. С модульными тестами обретаешь уверенность в том, что код рабочий и проблемы в нем выявляются заранее. Поэтому вместо постоянной отладки концентрируешься на создании функционала.
Начинайте модульное тестирование с добавления небольших тестов к одной функции. Войдете во вкус — доведете до автоматизма.
Хорошего тестирования и написания долговечного кода!
Читайте также:
- Проектирование устойчивых API: постигаем искусство ограничения скорости
- Улучшите производительность с помощью веб-воркеров
- Тестируя нетестируемое — битва с легаси-кодом
Читайте нас в Telegram, VK и Дзен
Перевод статьи Ranjan Monisha: Introduction to Unit Testing: Writing Code That Lasts