Автор статьи: Рустем Галиев
IBM Senior DevOps Engineer & Integration Architect. Официальный DevOps ментор и коуч в IBM
Привет, Хабр
В мире разработки программного об еспечения качество кода играет решающую роль. Недостаточно просто написать работающий код — важно удостовериться, что он будет работать корректно в самых разных сценариях использования. В этом контексте тестирование программного обеспечения становится неотъемлемой частью процесса разработки.
Одним из наиболее распространенных методов тестирования в Python являются модульные тесты, или Unit Tests. Эти тесты позволяют разработчикам проверить отдельные компоненты и функции своего кода на соответствие ожидаемому поведению. В результате Unit Tests становятся надежным инструментом для обеспечения качества программного обеспечения и минимизации возможных ошибок.
Данная статья призвана погрузить вас в мир Unit Tests в Python. Мы рассмотрим основные принципы написания тестов, научимся создавать простые и эффективные тесты для вашего кода, а также рассмотрим некоторые лучшие практики и советы по их использованию. После ознакомления с этой статьей вы сможете использовать Unit Tests для обеспечения качества вашего Python-кода и повысить уверенность в его надежности.
Юнит-тесты - это кусочек программного кода, который проверяет другие кусочки кода. Они являются фундаментальным инструментом в разработке программного обеспечения, так как позволяют программистам убедиться, что отдельные части их программ работают так, как ожидается.
Вот несколько ключевых аспектов юнит-тестов:
Цель: Цель юнит‑тестов — проверить каждую отдельную часть кода, обычно функции или методы, на корректность работы. Это помогает выявить ошибки на ранних стадиях разработки, когда они проще исправить.
Изоляция: Юнит-тесты обычно тестируются в изоляции от других частей программы. Это означает, что код, который тестируется, запускается самостоятельно без взаимодействия с другими компонентами. Для этого могут использоваться заглушки (mocks) или фиктивные объекты.
Автоматизация: Хорошо написанные юнит-тесты должны быть автоматизированы, то есть запускаться без участия человека. Это позволяет быстро проверять, не приводят ли внесенные изменения к появлению новых ошибок.
Контроль: Юнит-тесты должны быть предсказуемыми и надежными. Они должны проверять конкретные аспекты функции или метода и сообщать о проблемах, если они возникают.
Интеграция в процесс разработки: Юнит-тесты интегрируются в процесс разработки программного обеспечения и обычно запускаются автоматически во время сборки проекта или перед внесением изменений в основной код.
Хорошие юнит-тесты помогают обеспечить стабильность программного обеспечения, упрощают процесс отладки и рефакторинга, а также дают уверенность в работоспособности кода при его изменениях.
Написание базового модульного теста
Допустим, у нас есть простая функция с именем addTwo()
, которая принимает число и добавляет к нему 2. Вот как выглядит эта функция:
def addTwo(x):
return x + 2
Это простая функция. Давайте напишем модульный тест, чтобы убедиться, что функция работает правильно.
Первое, что нам нужно сделать, это импортировать модуль unittest и создать класс с тестами. После создания класса следующим шагом является создание методов, обычно называемых testSomething
, который описывает, что именно тестируется.
Хорошим советом будет дать своему тесту описательное имя, например, testValidationFunctionWithInvalidInput
, а не test32 или что-то подобное, таким образом, вы сможете быстрее определить, какой тест не прошел, и выполнить отладку.
import unittest
class SimpleUnitTest(unittest.TestCase):
def testWithInt(self):
self.assertEqual(4, addTwo(2))
Каждый модульный тест должен содержать по крайней мере один вызов метода assert().
Примечание: Обычно модульные тесты содержатся в отдельном файле от вашего фактического кода. Однако, мы собираемся писать тесты непосредственно в интерпретатор Python, т.к. будет полезно знать как это делается, в случае если нужно будет провести небольшое быстрое тестирование. Я внес некоторые изменения в код, чтобы он работал здесь в интерпретаторе.
Чтобы запустить модульный тест в блокноте Jupyter, вам придется добавить аргумент exit=False
. (В самостоятельном скрипте это не обязательно.)
if __name__ == '__main__':
unittest.main(argv=['ignored'], exit=False)
class SimpleUnitTest(unittest.TestCase):
def testWithInt(self):
self.assertEqual(4, addTwo(2))
def testWithFloat(self):
self.assertEqual(4.0, addTwo(2.0))
if __name__ == '__main__':
unittest.main(argv=['ignored'], exit=False)
Мы должны увидеть выполнение двух тестов и результаты, которые будут OK.
class SimpleUnitTest(unittest.TestCase):
def testWithInt(self):
self.assertEqual(4, addTwo(2))
def testWithFloat(self):
self.assertEqual(4.0, addTwo(2.0))
if __name__ == '__main__':
unittest.main(argv=['ignored'], exit=False)
Этот тест не является особенно полезным, но одна из вещей, которые можно сделать с помощью фреймворков, — это проверить, КАК ваш код завершается с ошибкой, и убедиться, что он завершается так, как вы хотите. Например, что произойдет, если выполнить следующий код:
def addTwo(x):
return x + 2
addTwo("2")
Этот код завершится с ошибкой, поскольку Python не может преобразовать строку в целое число. В реальной ситуации мы, возможно, не захотим, чтобы код останавливал выполнение с исключением из-за неправильного ввода, или мы можем захотеть предоставить более полезное сообщение об ошибке.
Фреймворк unittest предоставляет ряд функций assert, которые позволяют вам тестировать различные ситуации, включая:
assertEqual(a, b): проверяет, что a = b
assertNotEqual(a, b): проверяет, что a != b
assertTrue(a): проверяет, что a - истина
assertFalse(a): проверяет, что a - ложь
assertRaises(ErrorType): проверяет, что модульный тест завершается с определенным типом ошибки
Теперь давайте попробуем написать модульный тест, чтобы убедиться, что ошибка, возвращаемая при неправильном вводе, действительно является TypeError:
import unittest
def addTwo(x):
return x + 2
class SimpleExceptionTest(unittest.TestCase):
def testInvalidInput(self):
with self.assertRaises(TypeError):
addTwo("2")
if __name__ == '__main__':
unittest.main(argv=['ignored'], exit=False)
В приведенном выше примере мы видим, что даже если код завершается с ошибкой, модульный тест проходит, потому что код возвращает правильный тип ошибки.
Unit-тесты предназначены для вас, чтобы помочь вам убедиться, что ваш код делает то, что вы от него ожидаете, поэтому вот несколько мыслей о написании эффективных unit-тестов:
Один assert на один unit-тест: Хотя вы можете включить несколько проверок в один unit-тест, это может затруднить определение причины сбоя, поэтому ограничьте unit-тест до одного assert'а.
Делайте их короткими: Unit-тесты не должны быть длинными демонстрациями коварства кода. Они должны быть простыми, быстрыми и понятными. Если ваши тесты слишком сложные, иногда можно потратить часы на поиск ошибки, которая, на самом деле, не была в вашем коде, а в тесте.
Пишите unit-тесты по мере написания кода: Unit-тесты не должны быть последней мыслью. Вы должны писать их по мере написания кода, чтобы можно было рано и часто выявлять ошибки.
Ясно называйте ваши тесты: Как уже упоминалось ранее, вы должны ясно называть свои тесты, чтобы было очевидно, что именно не сработало.
Тестируйте не только успешные случаи: При написании unit-тестов креативно мыслите о том, что может пойти не так, и пишите тест для этого. Например, если вы заполняете dataframe данными, полученными из внешнего источника, что произойдет, если источник будет недоступен? Что произойдет, если источник изменит формат данных? Тщательное рассмотрение возможных проблем может предотвратить катастрофы позже.
Не забывайте о крайних случаях: Это на самом деле одни из самых важных unit-тестов. Подумайте о том, какие крайние случаи существуют, и убедитесь, что вы пишете для них unit-тесты.
И, наконец, не игнорируйте результаты ваших тестов. Если они не все проходят, вы еще не закончили. Не переходите к следующему этапу, пока НЕ ВСЕ ваши unit-тесты не пройдены.
В заключение приглашаю всех желающих тестировщиков на открытый урок 23 мая, на котором рассмотрим фреймворк для автоматизации тестирования Playwright. Научимся настраивать и запускать автотесты, а также разберём основные отличия от Selenium. Записаться можно по ссылке
Ktulhy
Дорогой автор! Будьте добры, изучите PEP-8, современный фреймфорк для тестирования pytest, который является стандартом де-факто в индустрии и прекращайте писать статьи с помощью языковых моделей. Спасибо!