Типичный модульный тест Python утверждает, что некоторая функция возвращает предварительно вычисленное выходное значение, например:
def average(a, b):
return (a + b) / 2
def test_average():
assert average(2, 4) == 3
Все проверочные тесты следуют той же базовой формуле: ввод входных данных, предоставление ожидаемых выходов, вызов кодабыть проверенным, сравните.Я часто пишу такие тесты, и я думаю, что их ценность во многих случаях очевидна.
Но меня не устраивает то, что рамки тестов неоднозначности оставляют без внимания то, насколько точно установлен результат (3
в примере)предназначен для вычисления.В тесте часто требуется, чтобы различные параметры демонстрировали поведение в крайнем случае, например:
def test_average(a, b):
params = [
[1, 3, 2],
[9000.5, 9000.5, 9000.5],
[100, -100, 0],
[-1, 3, 1],
[0, 0, 0]
]
for a, b, result in params:
assert average(a, b) == result
Но если у вас есть длинный список параметров или трудно вычисляемый результат, у вас естьчтобы выбрать один из двух вариантов:
- Запустите проверяемый код один раз вручную, например, из IPython, и сохраните его возвращаемое значение в кодовой базе в качестве проверочного утверждения
- Достаточно переопределитев тестируемом коде ожидаемые результаты вычисляются в самом тестовом примере
Большинство разработчиков используют комбинацию обоих подходов.Но ни то, ни другое не дает вам достаточных доказательств того, что код правильный , только то, что он соответствует для всех версий кода.Выполнение ваших тестов на самом деле просто проверяет, что код ведет себя так же, как и при создании тестов.
Это заставило меня задуматься, существуют ли методы тестирования, где запись / сравнение поведения во время ревизии между ревизиями (для тестируемого программного обеспечения)делается явнымНапример, вы можете записать JSON-коллекцию возвращаемых значений «demo case» на диск и в систему управления версиями, чтобы тестовые примеры могли затем утверждать свойства о согласованности этих результатов в разных версиях:
def demo_average():
arg_a_vals = [0, -4.2, 1/12] + range(-100, 1000, 5)
arg_b_vals = [0, -10**200, math.pi] + range(2**20, 2**20 + 17)
return [
a, b, average(a, b)
for a in arg_a_vals
for b in arg_b_vals
]
def save_demo(results):
with open(f"demo_v{__version__}.json", "w") as f:
json.dump(results, f)
def load_demo(version):
with open(f"demo_v{version}.json") as f:
return json.load(f)
def test_average():
result = demo_average()
save_demo(result)
saved_result = load_demo(GOOD_PRIOR_VERSION)
assert_lists_are_equal(result, saved_result)
Этот примерпреднамеренно просто, но save_demo
, load_demo
и test_average
можно обобщить, поэтому единственный код, который вам нужно повторять в каждом тестовом примере, это какой-то вариант demo_average
- перечисление возможных входных значений, выходные данные которых должны сравниватьсячерез ревизии.Обратите внимание, что код не включает в себя любые ожидаемые выходные значения в виде литералов, в отличие от обычного теста утверждения.
Вы также можете избежать фиксации "демонстрационных" результатов (которые могут быть очень большими).если тестовая среда знает о системе управления версиями и может запускать демонстрации на нескольких клонированных версиях, чтобы сгенерировать объект results
выше на лету.
Существуют ли какие-либо тестовые платформы или проекты Python, которые работаюткак это? То есть, когда программное обеспечение тестируется путем проверки того, что код возвращает согласованные результаты при пересмотрах кода , а не конкретный ожидаемый результат, заявленный в тестовом примере.[Редактировать: Дирк Херманн отмечает в комментариях, что это иногда называют тестирование .]
Обратите внимание, что я изучил tox и яЯ не уверен, что это адрес этого варианта использования.Он поддерживает сравнение между версиями, но в документах в основном рассматривается обеспечение того, чтобы ваши собственные существующие тестовые случаи проходили под несколькими версиями зависимостей вашего проекта, что я не думаю, применимо.
Несколько человек также упомянули гипотезы .Гипотеза интересна и также работает совсем не так, как обычные тесты - вкратце, вы пишете тесты как сильно параметризованные / обобщенные утверждения, а Гипотеза делает умные вещи, чтобы исследовать пространство параметров для сбоев.Но, читая документы, я не вижу, как это применимо к моему делу.Моя цель - написать тесты, которые утверждают согласованность поведения между версиями, а не проверять какие-либо свойства, которые могут быть утверждены для отдельной версии кода в отдельности.