Skip to content

Лабораторная работа: Отработка навыков работы с 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. Использование ассертов

Ассерты — это методы, которые проверяют выполнение определенных условий. Если условие не выполняется, тест считается проваленным.

Основные ассерты

  1. assertEqual(a, b)
    Проверяет, что a == b. python self.assertEqual(2 + 2, 4)

  2. assertTrue(x)
    Проверяет, что x истинно. python self.assertTrue(10 > 5)

  3. assertFalse(x)
    Проверяет, что x ложно. python self.assertFalse(10 < 5)

  4. assertRaises(Исключение, функция, *аргументы)
    Проверяет, что функция вызывает указанное исключение. python with self.assertRaises(ZeroDivisionError): 1 / 0

  5. assertIn(a, b)
    Проверяет, что a содержится в b. python self.assertIn(3, [1, 2, 3])

  6. assertIsNone(x)
    Проверяет, что x равно None. python self.assertIsNone(None)

  7. 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

Мокирование позволяет заменять реальные объекты на "заглушки" (моки) для изоляции тестируемого кода от внешних зависимостей.

Основные компоненты

  1. Mock
    Заменяет объект на заглушку. ```python from unittest.mock import Mock

mock = Mock() mock.method.return_value = 42 assert mock.method() == 42 ```

  1. patch
    Временно заменяет объект на мок в контексте теста. ```python from unittest.mock import patch

def my_function(): return 42

with patch("main.my_function", return_value=100): assert my_function() == 100 ```

  1. 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()