Skip to content

Skip - Пропуск тестов

Skip и Expected Failure

unittest предоставляет возможность пропускать тесты или помечать их как ожидаемо проваленные.

  • @unittest.skip(reason): Позволяет пропустить выполнение теста.
  • @unittest.skipIf(condition, reason): Позволяет пропустить выполнение теста в зависимости от условия.
  • @unittest.skipUnless(condition, reason): Позволяет выполнить тест только если условие истинно.
  • @unittest.expectedFailure: Помечает тест как ожидаемо проваленный. Тест будет выполнен, но результат провала не приведет к общему провалу тестового набора.
python
import unittest

class MyClass:
    def divide(self, a, b):
        if b == 0:
            raise ZeroDivisionError("Division by zero")
        return a / b

class TestMyClass(unittest.TestCase):
    def setUp(self):
        self.my_object = MyClass()

    @unittest.skip("Demonstrating skipping tests")
    def test_add(self):
        self.assertEqual(self.my_object.divide(2,2), 1)

    @unittest.skipIf(1 == 1, "Condition is met, skip")
    def test_subtract(self):
         self.assertEqual(self.my_object.divide(2,2), 1)


    @unittest.skipUnless(1 == 2, "Condition is not met, skip")
    def test_multiply(self):
         self.assertEqual(self.my_object.divide(2,2), 1)

    @unittest.expectedFailure
    def test_divide_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            self.my_object.divide(1, 0)

    def test_divide(self):
        self.assertEqual(self.my_object.divide(10, 2), 5)

if __name__ == '__main__':
    unittest.main()

Mocking:

При тестировании модулей часто возникает необходимость изолировать тестируемый код от зависимостей, например, внешних API, баз данных или других модулей. Mocking позволяет заменить эти зависимости "заглушками" (mocks), которые имитируют поведение реальных объектов. Python предоставляет библиотеку unittest.mock для создания моков.

python
import unittest
from unittest.mock import patch
import requests

class DataFetcher:
    """
    A class that fetches data from an external API.
    """
    def get_data(self, url: str) -> dict:
        """
        Fetches data from the given URL and returns it as a dictionary.
        """
        response = requests.get(url)
        response.raise_for_status() # Raises an exception for bad status codes
        return response.json()

class TestDataFetcher(unittest.TestCase):
    """
    Test case for the DataFetcher class.
    """

    def setUp(self) -> None:
        """
        Sets up the test fixture.
        """
        self.data_fetcher = DataFetcher()

    def tearDown(self) -> None:
        """
        Tears down the test fixture.
        """
        self.data_fetcher = None


    @patch('requests.get')
    def test_get_data_success(self, mock_get) -> None:
        """
        Tests the get_data method with a successful response.
        """
        mock_response = unittest.mock.Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"key": "value"}
        mock_get.return_value = mock_response

        url = "http://example.com/api/data"
        data = self.data_fetcher.get_data(url)

        mock_get.assert_called_once_with(url) # checks that `requests.get` was called with specified url.
        self.assertEqual(data, {"key": "value"})

    @patch('requests.get')
    def test_get_data_error(self, mock_get) -> None:
        """
        Tests the get_data method with an error response.
        """
        mock_response = unittest.mock.Mock()
        mock_response.status_code = 404
        mock_get.return_value = mock_response
        mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Not Found") # emulate error.

        url = "http://example.com/api/data"
        with self.assertRaises(requests.exceptions.HTTPError):
            self.data_fetcher.get_data(url)

        mock_get.assert_called_once_with(url)


if __name__ == '__main__':
    unittest.main()

В этом примере мы используем декоратор @patch('requests.get') для замены функции requests.get моком. В методе test_get_data_success мы создаем мок для объекта ответа (mock_response) и устанавливаем его атрибуты status_code и json. Затем мы вызываем метод get_data и проверяем, что функция requests.get была вызвана с правильным URL и что возвращенные данные соответствуют ожидаемым.

В методе test_get_data_error мы имитируем ошибочный ответ сервера и проверяем, что при этом выбрасывается исключение requests.exceptions.HTTPError. Важно использовать mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Not Found") для имитации выброса исключения при вызове response.raise_for_status(). Также проверяется, что mock_get вызывается с ожидаемым аргументом.

Test Discovery:

Вместо того, чтобы явно добавлять тесты в TestSuite, можно использовать автоматическое обнаружение тестов. unittest предоставляет механизм для поиска и запуска всех тестов в указанной директории или модуле.

python
#file: my_tests.py
import unittest

class MyClass:
    def add(self, a, b):
        return a + b

class TestMyClass(unittest.TestCase):
    def test_add_positive(self):
        my_object = MyClass()
        self.assertEqual(my_object.add(2, 3), 5)

    def test_add_negative(self):
        my_object = MyClass()
        self.assertEqual(my_object.add(-2, -3), -5)

if __name__ == '__main__':
    unittest.main()

Для запуска тестов, используя обнаружение тестов, выполните в терминале команду находясь в директории с файлом my_tests.py:

bash
python -m unittest discover .

Эта команда автоматически найдет и запустит все тесты в текущей директории (.). Вы можете указать конкретную директорию или шаблон имени файлов для поиска тестов.

Упражнения:

  1. Custom Assertions: Создайте класс CustomAssertions, который наследуется от unittest.TestCase, и добавьте в него методы assertGreater(a, b) и assertLess(a, b), которые проверяют, что a > b и a < b соответственно. Используйте эти методы в тестах для проверки сравнений чисел.
  2. Mocking External Service: Создайте класс PaymentProcessor, который взаимодействует с внешним платежным сервисом (замокируйте этот сервис). Напишите тесты, которые проверяют успешную обработку платежа, отказ платежа (например, из-за недостатка средств) и обработку ошибок соединения с сервисом.
  3. Test Suite Organization: Создайте несколько классов тестовых случаев для разных модулей вашего проекта. Организуйте их в TestSuite, используя различные способы добавления тестов (например, addTest, addTests, makeSuite, TestLoader). Запустите все тесты с помощью TextTestRunner.
  4. HTML Test Runner: Используйте стороннюю библиотеку, такую как HtmlTestRunner, для генерации HTML-отчетов о результатах тестирования. Установите библиотеку (pip install html-testRunner) и напишите скрипт, который запускает тесты и создает HTML-отчет.
  5. Asyncio Testing: Если вы используете асинхронный код, напишите тесты, которые используют библиотеку asyncio и unittest для тестирования асинхронных функций. Убедитесь, что вы корректно обрабатываете асинхронные операции и используете asyncio.run или asyncio.get_event_loop().run_until_complete() для запуска тестов.

Contacts: teffal@mail.ru