Лабораторная работа: Отработка навыков работы с unittest и TDD
Цель:
Научиться использовать модуль unittest для написания тестов в соответствии с концепцией Test-Driven Development (TDD).
Задачи:
1. Понять основные принципы TDD.
2. Написать тесты до реализации кода.
3. Использовать возможности unittest: тестовые случаи, ассерты, мокирование и параметризацию.
Теоретическая часть
2. Основные возможности unittest
- Создание тестовых классов, наследующихся от
unittest.TestCase. - Использование ассертов (
assertEqual,assertTrue,assertRaisesи др.). - Мокирование с помощью
unittest.mock. - Параметризация тестов с помощью
subTest.
Подробная теория по работе с unittest
В этом разделе мы подробно рассмотрим ключевые аспекты работы с модулем unittest в Python:
1. Создание тестовых классов, наследующихся от unittest.TestCase.
2. Использование ассертов (assertEqual, assertTrue, assertRaises и др.).
3. Мокирование с помощью unittest.mock.
4. Параметризация тестов с помощью subTest.
1. Создание тестовых классов, наследующихся от unittest.TestCase
Основы
unittest — это встроенный модуль Python для написания и запуска тестов. Основная идея заключается в создании тестовых классов, которые наследуются от unittest.TestCase. Каждый метод этого класса, имя которого начинается с test_, считается тестом.
Пример
import unittest
class TestMathOperations(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
def test_subtraction(self):
self.assertEqual(5 - 3, 2)
if __name__ == "__main__":
unittest.main()
Особенности
- Каждый тест выполняется в изолированном окружении.
- Методы
setUpиtearDownпозволяют настроить предварительные условия и очистку после каждого теста. - Методы
setUpClassиtearDownClassвыполняются один раз для всего класса.
2. Использование ассертов
Ассерты — это методы, которые проверяют выполнение определенных условий. Если условие не выполняется, тест считается проваленным.
Основные ассерты
-
assertEqual(a, b)
Проверяет, чтоa == b.python self.assertEqual(2 + 2, 4) -
assertTrue(x)
Проверяет, чтоxистинно.python self.assertTrue(10 > 5) -
assertFalse(x)
Проверяет, чтоxложно.python self.assertFalse(10 < 5) -
assertRaises(Исключение, функция, *аргументы)
Проверяет, что функция вызывает указанное исключение.python with self.assertRaises(ZeroDivisionError): 1 / 0 -
assertIn(a, b)
Проверяет, чтоaсодержится вb.python self.assertIn(3, [1, 2, 3]) -
assertIsNone(x)
Проверяет, чтоxравноNone.python self.assertIsNone(None) -
assertGreater(a, b)
Проверяет, чтоa > b.python self.assertGreater(10, 5)
Пример использования
class TestAsserts(unittest.TestCase):
def test_asserts(self):
self.assertEqual(2 * 3, 6)
self.assertTrue(10 > 5)
self.assertFalse(10 < 5)
self.assertIn(3, [1, 2, 3])
with self.assertRaises(ValueError):
int("не число")
3. Мокирование с помощью unittest.mock
Мокирование позволяет заменять реальные объекты на "заглушки" (моки) для изоляции тестируемого кода от внешних зависимостей.
Основные компоненты
Mock
Заменяет объект на заглушку. ```python from unittest.mock import Mock
mock = Mock() mock.method.return_value = 42 assert mock.method() == 42 ```
patch
Временно заменяет объект на мок в контексте теста. ```python from unittest.mock import patch
def my_function(): return 42
with patch("main.my_function", return_value=100): assert my_function() == 100 ```
MagicMock
РасширениеMock, которое автоматически создает магические методы (например,__len__,__iter__).
Пример
from unittest.mock import Mock, patch
class WeatherService:
def __init__(self, api):
self.api = api
def get_temperature(self, city):
return self.api.fetch_weather(city)["temperature"]
class TestWeatherService(unittest.TestCase):
def test_get_temperature(self):
mock_api = Mock()
mock_api.fetch_weather.return_value = {"temperature": 25.0}
service = WeatherService(mock_api)
self.assertEqual(service.get_temperature("Moscow"), 25.0)
4. Параметризация тестов с помощью subTest
Параметризация позволяет запускать один тест с разными наборами входных данных. В unittest для этого используется метод subTest.
Пример
class TestFactorial(unittest.TestCase):
def test_factorial(self):
test_cases = [
(0, 1),
(1, 1),
(2, 2),
(3, 6),
(4, 24),
(5, 120),
]
for n, expected in test_cases:
with self.subTest(n=n):
self.assertEqual(factorial(n), expected)
Пример: Калькулятор (TDD-подход)
Шаг 1: Написание теста
Создайте файл test_calculator.py:
import unittest
class TestCalculator(unittest.TestCase):
def test_add(self):
from calculator import add
self.assertEqual(add(2, 3), 5)
if __name__ == "__main__":
unittest.main()
Шаг 2: Запустите тест (он упадет)
python -m unittest test_calculator.py
Ошибка: модуль calculator не существует.
Шаг 3: Напишите минимальную реализацию
Создайте calculator.py:
def add(a, b):
return a + b
Шаг 4: Запустите тест снова
Практические задания
1
Напишите программу на Python, которая вычисляет сумму всех чисел от 1 до N, где N — целое число, введенное пользователем. В коде допущена ошибка, найдите и исправьте её.
def calculate_sum(n):
total = 0
for i in range(n):
total += i
return total
n = int(input("Введите число N: "))
print("Сумма чисел от 1 до N:", calculate_sum(n))
2
Напишите программу на Python, которая реализует стек (структуру данных) с использованием списка. В коде допущены несколько ошибок, найдите и исправьте их.
class Stack:
def __init__(self):
self.stack = []
def push(self, item):
self.stack.append(item)
def pop(self):
if self.is_empty():
return None
return self.stack.pop()
def is_empty(self):
return len(self.stack) == 0
def peek(self):
if self.is_empty():
return None
return self.stack[-1]
def size(self):
return len(self.stack)
# Пример использования
stack = Stack()
stack.push(10)
stack.push(20)
stack.push(30)
print("Верхний элемент стека:", stack.peek())
print("Размер стека:", stack.size())
print("Извлеченный элемент:", stack.pop())
print("Извлеченный элемент:", stack.pop())
print("Извлеченный элемент:", stack.pop())
print("Извлеченный элемент:", stack.pop())
Задание 3: Проверка палиндрома (TDD)
Требования:
Функция is_palindrome(s: str) -> bool должна возвращать True, если строка является палиндромом.
Шаги:
1. Напишите тесты для случаев:
- Палиндром без пробелов (например, "radar").
- Не палиндром ("hello").
- Палиндром с пробелами ("A man a plan a canal Panama").
2. Запустите тесты (они упадут).
3. Реализуйте функцию.
4. Проверьте, что тесты проходят.
Пример теста
import unittest
class TestPalindrome(unittest.TestCase):
def test_is_palindrome(self):
from palindrome import is_palindrome
self.assertTrue(is_palindrome("radar"))
self.assertFalse(is_palindrome("hello"))
self.assertTrue(is_palindrome("A man a plan a canal Panama"))
if __name__ == "__main__":
unittest.main()
Задание 4: Подсчет слов в строке
Требования:
Функция count_words(s: str) -> int возвращает количество слов в строке. Слово — последовательность символов без пробелов.
Шаги:
1. Напишите тесты для:
- Пустой строки.
- Строки с одним словом.
- Строки с несколькими словами и пробелами.
2. Реализуйте функцию после написания тестов.
Пример теста
import unittest
class TestCountWords(unittest.TestCase):
def test_count_words(self):
from word_counter import count_words
self.assertEqual(count_words(""), 0)
self.assertEqual(count_words("hello"), 1)
self.assertEqual(count_words("hello world"), 2)
if __name__ == "__main__":
unittest.main()
Задание 5: Валидация email
Требования:
Функция is_valid_email(email: str) -> bool проверяет:
- Наличие символа @.
- Наличие домена (часть после @ содержит точку).
Примеры валидных email:
user@example.com, john.doe@domain.co
Примеры невалидных email:
user@, john.doe@domain
Шаги:
1. Создайте тесты для различных случаев.
2. Реализуйте функцию.
Пример теста
import unittest
class TestEmailValidation(unittest.TestCase):
def test_is_valid_email(self):
from email_validator import is_valid_email
self.assertTrue(is_valid_email("user@example.com"))
self.assertTrue(is_valid_email("john.doe@domain.co"))
self.assertFalse(is_valid_email("user@"))
self.assertFalse(is_valid_email("john.doe@domain"))
if __name__ == "__main__":
unittest.main()
Задача 6: Проверка возраста
Функция is_adult(age: int) -> bool должна возвращать True, если возраст ≥ 18.
Тесты:
- Проверьте граничные значения (17, 18, 19).
Пример теста
import unittest
class TestIsAdult(unittest.TestCase):
def test_is_adult(self):
from age_checker import is_adult
self.assertFalse(is_adult(17))
self.assertTrue(is_adult(18))
self.assertTrue(is_adult(19))
if __name__ == "__main__":
unittest.main()