Appearance
Тестирование в Python без использования библиотек
Тестирование — неотъемлемая часть разработки программного обеспечения. В процессе создания приложений важно убедиться, что код работает так, как задумано, и не имеет скрытых ошибок.
В Python существует множество библиотек для тестирования, таких как unittest
, pytest
и другие, которые упрощают написание тестов. Однако иногда полезно понять, как тестирование работает "под капотом", и попробовать написать тесты без использования сторонних библиотек.
Основная идея тестирования
Тестирование основывается на проверке того, что функция или метод возвращает ожидаемые результаты при определённых входных данных. Основной принцип прост:
- Задаём входные данные для функции.
- Определяем ожидаемый результат.
- Сравниваем результат выполнения функции с ожидаемым.
- Если результат совпадает с ожиданиями — тест пройден, иначе — тест провален.
Юнит Тестирование
Юнит тестирование — это метод тестирования программного обеспечения, где отдельные компоненты (юниты) исходного кода, такие как функции или методы, тестируются независимо, чтобы убедиться, что они работают должным образом. Цель юнит тестирования — изолировать каждый компонент и проверить, соответствует ли его поведение ожидаемому.
Простая проверка через assert
Python предоставляет встроенную конструкцию для проверки условий — assert
(с анг. перевод "утверждение"). Она используется для проверки истинности выражения. Если выражение ложно, выполнение программы завершается с ошибкой AssertionError
, сигнализирующей о провале теста.
Пример тестирования с использованием assert
Для начала создадим простую функцию, которую будем тестировать:
python
def add(a, b):
return a + b
def subtract(a, b):
return a - b
Теперь напишем тесты для этой функции:
python
def test_add():
# Тест 1: Проверяем сложение положительных чисел
assert add(2, 3) == 5, "Ошибка: 2 + 3 должно быть равно 5"
# Тест 2: Проверяем сложение отрицательных чисел
assert add(-1, -1) == -2, "Ошибка: -1 + -1 должно быть равно -2"
# Тест 3: Проверяем сложение положительного и отрицательного числа
assert add(5, -3) == 2, "Ошибка: 5 + (-3) должно быть равно 2"
# Тест 4: Проверяем сложение с нулём
assert add(0, 7) == 7, "Ошибка: 0 + 7 должно быть равно 7"
# Запускаем тест
test_add()
print("Все тесты пройдены успешно!")
Если все утверждения в тесте истинны, программа завершится без ошибок. Если хотя бы одно утверждение ложно, выполнение остановится с сообщением об ошибке.
Автоматизация нескольких тестов
Если тестов становится много, можно организовать их в виде списка или словаря, чтобы избежать дублирования кода. Например:
python
def test_add_with_cases():
# Список тест-кейсов в формате (a, b, expected_result)
test_cases = [
(2, 3, 5),
(-1, -1, -2),
(5, -3, 2),
(0, 7, 7),
(10, 0, 10),
]
for a, b, expected in test_cases:
result = add(a, b)
assert result == expected, f"Ошибка: {a} + {b} должно быть равно {expected}, а получилось {result}"
print("Все тесты для add() пройдены успешно!")
# Запускаем тест
test_add_with_cases()
Теперь мы можем легко добавлять новые тест-кейсы, просто добавляя их в список.
Тестирование исключений
Иногда функции должны не только возвращать значения, но и выбрасывать исключения в определённых ситуациях. Например, напишем функцию, которая делит одно число на другое, и протестируем, что она выбрасывает исключение при делении на ноль:
python
def divide(a, b):
if b == 0:
raise ValueError("Деление на ноль запрещено")
return a / b
def test_divide():
# Тест 1: Проверяем обычное деление
assert divide(10, 2) == 5, "Ошибка: 10 / 2 должно быть равно 5"
# Тест 2: Проверяем деление на 0
try:
divide(10, 0)
except ValueError as e:
assert str(e) == "Деление на ноль запрещено", f"Неверное сообщение об ошибке: {e}"
else:
assert False, "Ошибка: Ожидалось исключение ValueError при делении на 0"
print("Все тесты для divide() пройдены успешно!")
# Запускаем тест
test_divide()
Здесь мы используем конструкцию try-except-else
для проверки выбрасываемого исключения.
Организация тестов
Если в вашем проекте несколько функций, и вы хотите протестировать их все, можно организовать тесты в виде отдельных функций и запускать их последовательно. Например:
python
def run_tests():
test_add() # Тестируем функцию add
test_divide() # Тестируем функцию divide
print("Все тесты пройдены успешно!")
# Запускаем все тесты
run_tests()
Улучшение вывода результатов
Для удобства можно добавить более подробный вывод результатов тестирования. Например:
python
def run_test(name, func):
try:
func()
print(f"[PASS] {name}")
except AssertionError as e:
print(f"[FAIL] {name}: {e}")
def run_all_tests():
run_test("test_add", test_add)
run_test("test_divide", test_divide)
run_all_tests()
Теперь для каждого теста будет выводиться, прошёл он или провалился.
Более продвинутая организация
Для более сложных проектов можно использовать более структурированный подход. Например, можно создать класс для группировки тестов:
python
class TestCalculator:
def test_add_with_cases(self):
# Список тест-кейсов в формате (a, b, expected_result)
test_cases = [
(2, 3, 5),
(-1, -1, -2),
(5, -3, 2),
(0, 7, 7),
(10, 0, 10),
]
for a, b, expected in test_cases:
result = add(a, b)
assert result == expected, f"Ошибка: {a} + {b} должно быть равно {expected}, а получилось {result}"
print("Все тесты для add() пройдены успешно!")
def test_divide(self):
# Тест 1: Проверяем обычное деление
assert divide(10, 2) == 5, "Ошибка: 10 / 2 должно быть равно 5"
# Тест 2: Проверяем деление на 0
try:
divide(10, 0)
except ValueError as e:
assert str(e) == "Деление на ноль запрещено", f"Неверное сообщение об ошибке: {e}"
else:
assert False, "Ошибка: Ожидалось исключение ValueError при делении на 0"
print("Все тесты для divide() пройдены успешно!")
# Запуск тестов (более структурированный способ)
test_suite = TestCalculator()
for attr in dir(test_suite):
if attr.startswith('test_'):
try:
getattr(test_suite, attr)()
except AssertionError as e:
print(f"Тест '{attr}' провален: {e}")
break # Можно продолжить тестирование или прервать
else:
print("Все тесты пройдены успешно!")
Этот подход позволяет лучше организовать тесты и улучшает читаемость кода.
Ограничения подхода без библиотек
Хотя тестирование без библиотек позволяет понять базовые принципы, у такого подхода есть ограничения:
- Отсутствие удобных инструментов: Библиотеки, такие как
unittest
илиpytest
, предоставляют множество функций для проверки различных условий, упрощают организацию тестов и выводят подробные отчёты. - Нет поддержки фикстур: С тестовыми библиотеками можно легко создавать подготовительные данные (фикстуры) для каждого теста.
- Нет параллелизма: Современные библиотеки позволяют запускать тесты одновременно, что экономит время.
- Нет интеграции с инструментами разработки: Библиотеки чаще всего поддерживают интеграцию с
IDE
иCI/CD
системами.
Заключение
Тестирование — неотъемлемая часть разработки качественного программного обеспечения. Тестирование без использования библиотек — это полезное упражнение, которое помогает понять основы тестирования и принципы работы с проверками. Если у вас небольшой проект или вы хотите разобраться в базовых принципах, тестирование с использованием assert
и простых функций — отличный старт.
Упражнения
Test_Absolute_Value: Напишите функцию
calculate_absolute_value(number: int) -> int
, которая вычисляет абсолютное значение числа. Создайте ручные тесты для положительных, отрицательных и нулевых входных данных.Test_Maximum_of_Two: Напишите функцию
find_maximum(a: int, b: int) -> int
, которая возвращает максимальное из двух чисел. Создайте ручные тесты для случаев, когдаa > b
,a < b
иa == b
.Test_String_Reversal: Напишите функцию
reverse_string(input_string: str) -> str
, которая переворачивает строку. Создайте ручные тесты для пустых строк, строк с одним символом и строк с несколькими символами.Test_List_Sum: Напишите функцию
calculate_list_sum(numbers: list[int]) -> int
, которая вычисляет сумму элементов списка целых чисел. Создайте ручные тесты для пустых списков, списков с положительными числами, списков с отрицательными числами и списков с и положительными, и отрицательными числами.Test_Leap_Year: Напишите функцию
is_leap_year(year: int) -> bool
, которая определяет, является ли год високосным. Создайте ручные тесты для обычных годов и високосных годов (например, 2000, 2004, 1900, 2003, 2100).Test_Factorial: Напишите функцию
calculate_factorial(n: int) -> int
, которая вычисляет факториал числа. Создайте ручные тесты дляn = 0
,n = 1
,n = 5
, иn = 10
. Обратите внимание на граничные условия и обработку ошибок.