Модульные тесты могут становиться нестабильными по ряду причин, особенно когда они зависят от динамически изменяемых данных, например, от текущего времени. Функция is_business_hour, основанная на datetime.now, будет возвращать разные результаты в зависимости от времени выполнения теста, что делает её трудной для тестирования.
Проблема с нестабильностью тестов
В данном случае, если тест выполняется в пределах рабочего времени например,между9:00и17:59например, между 9:00 и 17:59например,между9:00и17:59, тест пройдет, но если время выполнения изменится на 18:00, тест будет провален. Это создает проблемы, так как один и тот же тест может давать разные результаты в зависимости от времени.
Тестируемая версия функции
Чтобы сделать функцию is_business_hour более тестируемой, стоит инкапсулировать логику получения текущего времени, позволяя тестам легко подменять её. Одним из подходов является передача времени в качестве параметра. Например:
from datetime import datetime def is_business_hournow=Nonenow=Nonenow=None: if now is None: now = datetime.now
return 9 <= now.hour < 18
Теперь функция принимает значение времени и может использовать текущее время по умолчанию, если параметр не передан.
Тестирование функции
Теперь можно написать модульные тесты, передавая разные значения для now:
Инъекция времени: Как показано выше, можно передавать время в функцию в качестве параметра, что позволяет легко тестировать разные временные рамки, без зависимости от реального времени.
Фикстуры: В тестовых библиотеках, таких как pytest, можно использовать фикстуры, чтобы задавать общее состояние или предустанавливать данные для тестов, включая текущее время.
Мокирование: Для тестов можно использовать библиотеки вроде unittest.mock для подмены datetime.now на статическое значение, что позволяет создать контрольный тест с фиксированным временем.
Пример с мокированием:
from unittest.mock import patch class TestBusinessHourWithMockunittest.TestCaseunittest.TestCaseunittest.TestCase: @patch′datetime.datetime′'datetime.datetime'′datetime.datetime′
def test_business_hourself,mockdatetimeself, mock_datetimeself,mockdatetime: # Настраиваем мок для возвращения фиксированного времени mock_datetime.now.return_value = datetime2023,10,10,10,0,02023, 10, 10, 10, 0, 02023,10,10,10,0,0
self.assertTrueisbusinesshour()is_business_hour()isbusinesshour() @patch′datetime.datetime′'datetime.datetime'′datetime.datetime′
def test_outside_business_hourself,mockdatetimeself, mock_datetimeself,mockdatetime: mock_datetime.now.return_value = datetime2023,10,10,19,0,02023, 10, 10, 19, 0, 02023,10,10,19,0,0
self.assertFalseisbusinesshour()is_business_hour()isbusinesshour() if __name__ == '__main__': unittest.main
Таким образом, используя данные подходы, вы можете создавать стабильные и предсказуемые тесты для функций, которые зависят от текущего времени.
Модульные тесты могут становиться нестабильными по ряду причин, особенно когда они зависят от динамически изменяемых данных, например, от текущего времени. Функция is_business_hour, основанная на datetime.now, будет возвращать разные результаты в зависимости от времени выполнения теста, что делает её трудной для тестирования.
Проблема с нестабильностью тестовВ данном случае, если тест выполняется в пределах рабочего времени например,между9:00и17:59например, между 9:00 и 17:59например,между9:00и17:59, тест пройдет, но если время выполнения изменится на 18:00, тест будет провален. Это создает проблемы, так как один и тот же тест может давать разные результаты в зависимости от времени.
Тестируемая версия функцииЧтобы сделать функцию is_business_hour более тестируемой, стоит инкапсулировать логику получения текущего времени, позволяя тестам легко подменять её. Одним из подходов является передача времени в качестве параметра. Например:
from datetime import datetimedef is_business_hournow=Nonenow=Nonenow=None:
if now is None:
now = datetime.now return 9 <= now.hour < 18
Теперь функция принимает значение времени и может использовать текущее время по умолчанию, если параметр не передан.
Тестирование функцииТеперь можно написать модульные тесты, передавая разные значения для now:
import unittestfrom datetime import datetime
class TestBusinessHourunittest.TestCaseunittest.TestCaseunittest.TestCase:
def test_business_hour_morningselfselfself:
self.assertTrueisbusinesshour(datetime(2023,10,10,10,0,0))is_business_hour(datetime(2023, 10, 10, 10, 0, 0))isb usinessh our(datetime(2023,10,10,10,0,0))
def test_business_hour_eveningselfselfself:
self.assertFalseisbusinesshour(datetime(2023,10,10,19,0,0))is_business_hour(datetime(2023, 10, 10, 19, 0, 0))isb usinessh our(datetime(2023,10,10,19,0,0))
def test_business_hour_boundary_startselfselfself:
self.assertTrueisbusinesshour(datetime(2023,10,10,9,0,0))is_business_hour(datetime(2023, 10, 10, 9, 0, 0))isb usinessh our(datetime(2023,10,10,9,0,0))
def test_business_hour_boundary_endselfselfself:
self.assertFalseisbusinesshour(datetime(2023,10,10,18,0,0))is_business_hour(datetime(2023, 10, 10, 18, 0, 0))isb usinessh our(datetime(2023,10,10,18,0,0))
if __name__ == '__main__':
unittest.mainПриёмы для интеграционного тестирования
Инъекция времени: Как показано выше, можно передавать время в функцию в качестве параметра, что позволяет легко тестировать разные временные рамки, без зависимости от реального времени.
Фикстуры: В тестовых библиотеках, таких как pytest, можно использовать фикстуры, чтобы задавать общее состояние или предустанавливать данные для тестов, включая текущее время.
Мокирование: Для тестов можно использовать библиотеки вроде unittest.mock для подмены datetime.now на статическое значение, что позволяет создать контрольный тест с фиксированным временем.
Пример с мокированием:
from unittest.mock import patchclass TestBusinessHourWithMockunittest.TestCaseunittest.TestCaseunittest.TestCase:
@patch′datetime.datetime′'datetime.datetime'′datetime.datetime′ def test_business_hourself,mockdatetimeself, mock_datetimeself,mockd atetime:
# Настраиваем мок для возвращения фиксированного времени
mock_datetime.now.return_value = datetime2023,10,10,10,0,02023, 10, 10, 10, 0, 02023,10,10,10,0,0 self.assertTrueisbusinesshour()is_business_hour()isb usinessh our()
@patch′datetime.datetime′'datetime.datetime'′datetime.datetime′ def test_outside_business_hourself,mockdatetimeself, mock_datetimeself,mockd atetime:
mock_datetime.now.return_value = datetime2023,10,10,19,0,02023, 10, 10, 19, 0, 02023,10,10,19,0,0 self.assertFalseisbusinesshour()is_business_hour()isb usinessh our()
if __name__ == '__main__':
unittest.main
Таким образом, используя данные подходы, вы можете создавать стабильные и предсказуемые тесты для функций, которые зависят от текущего времени.