Добро пожаловать! Сегодня я расскажу о тестировании на основе свойств и метаморфическом тестировании.

Я слышал, как специалисты по анализу данных недовольны тестированием по множеству причин. Они часто отвергают тестирование как неважное, потому что они написали и понимают код, он работает в их модели, они уже просмотрели результат или на написание функциональных тестов уходит слишком много времени. Все это я сказал во время работы программистом. Однако преимущества тестирования намного перевешивают дополнительное время. Это упрощает повторное использование вашего кода, давая вам уверенность в правильности ваших функций путем простого повторного запуска тестов.

Кроме того, он заставляет вас понимать свой код и его крайние случаи или способы использования. Точно так же, если вам сложно писать тесты, это свидетельствует о том, что ваш код не был должным образом абстрагирован и вряд ли можно будет использовать повторно. Наконец, другие могут использовать ваши тесты в качестве примеров того, как работает код, что упростит другим использование вашего кода или его уверенное изменение. Если вы не уверены, что тестирование позволяет тратить ваше время с пользой, я бы порекомендовал эту статью.

Особенно важно тестирование кода для моделей. Если мы не уверены, что код нашей модели работает так, как мы предполагаем, сложно смотреть на выходные данные модели и быть уверенными в своих прогнозах. Три критических элемента, которые я обычно тестирую, - это инвариантные свойства функции, примеры использования и крайние варианты.

Тестирование простой функции

Давайте рассмотрим простую проблему и выведем принципы тестирования, которые могут помочь в тестировании моделей. Рассмотрим функцию сложения: f (x, y) = x + y.

Модульные тесты, при которых вы тестируете наименьшую единицу тестируемой функции или приложения, полезны для выполнения проверок работоспособности и документирования API. К сожалению, они не легко поддаются моделям машинного обучения по нескольким причинам. Во-первых, они рассказывают вам только о случаях, о которых вы уже думали, и не заставляют думать о крайних случаях. Более того, количество тестов, которые вы можете выполнить, зависит от количества тестов, которые вы пишете. Во-вторых, неясно, насколько жестко привязать тестовые примеры к вашей модели. Если вы соедините их слишком тесно, их станет утомительно изменять, когда вы обновляете свою модель, не изменяя качественно ее прогнозы. В качестве альтернативы, если вы соедините их слишком слабо, производительность может снизиться незаметно. Модульные тесты полезны, но они наиболее ценны, когда дополняются другими подходами.

В дополнение к модульному тестированию я обычно подписываюсь на тестирование на основе свойств, которое проверяет гипотезы о функции. В рамках тестирования на основе свойств первое, что нужно проверить, - это инвариантные свойства функции. Есть как минимум четыре свойства сложения, требующие тестирования:

  • x + y = y + x
  • Идентичность: 0 + y = y
  • Ассоциативность: (a + b) + c = a + (b + c)
  • Инвертирует: -a + a = 0

Затем я напишу тесты, чтобы охватить любые крайние случаи. Для простого добавления я бы протестировал следующее, хотя оно может быть не исчерпывающим:

  • Что функция дает правильный результат в конкретных примерах случаев, когда мы знаем решение.
  • Функция улавливает и обрабатывает несовместимые значения соответствующим образом.

Этот пример тривиален, поскольку мы обернули встроенный оператор Python. Однако элементы, которые мы тестируем в простом случае, отражают то, что мы проверяем в сложных случаях. Мы хотим убедиться, что все инвариантные свойства функции сохраняются, что любые граничные случаи проверяются и решаются, а затем модульное тестирование нескольких известных случаев.

Тестирование более сложного алгоритма

Помня об этих идеях, давайте применим их к сложной задаче, задаче кратчайшего пути в теории графов:

  • Для ориентированного графа G, состоящего из вершин V и ребер E, можем ли мы найти кратчайший путь между двумя вершинами V1 и V2?

Кроме того, давайте добавим к проблеме следующее ограничение:

  • Вес ребер должен быть положительным целым числом.

Теперь предположим, что у нас есть алгоритм кратчайшего пути и необходимо проверить его работоспособность. Во-первых, давайте скомпилируем набор инвариантных свойств для проверки:

  1. Для трех вершин A, B и C кратчайший путь от A до C должен быть меньше или равен кратчайшему пути от A до B плюс кратчайший путь от B до C (неравенство треугольника):
  • min_path (A - ›C)‹ = min_path (A - ›B) + min_path (B -› C)

2. Кратчайший путь от вершины V к самой себе должен быть равен 0 (идентичность):

  • min_path (A, A) == 0

3. Если min_path (A, B) больше нуля, A не равно B. И наоборот, если min_path (A, B) равен нулю, A == B

  • min_path (A, B) ›0 ==› A! = B
  • min_path (A, B) == 0 == ›A == B

4. Если A и B различны, путь с минимальной стоимостью должен быть больше или равен наименьшему весу ребер и меньше или равен сумме всех весов ребер. Это свойство следует из условия, что веса ребер должны быть положительными целыми числами.

  • min_path (A, B) ›= min (edge_weights), если A! = B
  • min_path (A, B) ‹= sum (edge_weights)

Свойства 1, 2 и 3 определяют квазиметрику, которая является метрикой без аксиомы симметрии. Однако их недостаточно для тестирования алгоритма кратчайшего пути, поэтому давайте скомпилируем граничные случаи в дополнение к свойствам. Например:

  • Циклы, которые могут привести к бесконечному зацикливанию алгоритма
  • Граф без пути из A в B
  • Большие входные данные - более медленные алгоритмы не смогут их обработать достаточно быстро.

Затем, если бы мы протестировали их в дополнение к свойствам и некоторым конкретным примерам, я был бы достаточно уверен, что алгоритм кратчайшего пути работает так, как задумано.

Метаморфические испытания

Иногда, работая со сложными алгоритмами, вы сталкиваетесь со свойством, которое трудно протестировать непосредственно на одном графике. В этом случае вы можете убедиться, что связь выполняется между парами входных функций. Этот метод тестирования называется метаморфическим тестированием, и полезно думать о метаморфическом тестировании как о «квадрате тестирования на основе свойств».

В тестировании на основе свойств вы указываете инвариантное отношение R между `x` и y = f (x). При метаморфическом тестировании вы определяете инвариантное отношение R между двумя парами выходов y1 и y2, учитывая, что их соответствующие входы x1 и x2 подчиняются другому отношению S. Обычно это работает так: вы преобразуете x1 в x2 и проверяете, что выходы меняются, как и ожидалось.

Прекрасный пример этого - тригонометрия. Предполагая, что у нас есть синусоидальная функция, мы не обязательно знаем значение f (x) = sin (x) для произвольного входа x. Однако мы знаем, что sin (x) = sin (π - x), поэтому мы можем написать тест на равенство двух входов x и (π - x).

Какой бы метод вы ни использовали, тестирование кода модели имеет решающее значение для обеспечения производительности модели.

Тестировать,

Ray & Kensho R&D