Unittest (иногда) терпит неудачу из-за неточности с плавающей запятой
У меня есть класс Vector, который представляет точку в 3-мерном пространстве. Этот вектор имеет метод normalize(self, length = 1)
, который масштабирует вектор вниз/вверх до length == vec.normalize(length).length
.
Отключить для этого метода иногда не удается из-за неточности чисел с плавающей запятой. Мой вопрос: как я могу убедиться, что этот тест не сработает, если методы реализованы правильно? Возможно ли это сделать без тестирования для приблизительного значения?
Дополнительная информация:
def testNormalize(self):
vec = Vector(random.random(), random.random(), random.random())
self.assertEqual(vec.normalize(5).length, 5)
Этот иногда приводит к AssertionError: 4.999999999999999 != 5
или AssertionError: 5.000000000000001 != 5
.
Примечание. Я знаю, что проблема с плавающей запятой может быть в свойстве Vector.length
или в Vector.normalize()
.
Ответы
Ответ 1
1) Как я могу убедиться, что тест работает?
Используйте assertAlmostEqual
, assertNotAlmostEqual
.
Из официальной документации :
assertAlmostEqual(first, second, places=7, msg=None, delta=None)
Проверить, что первая и вторая приблизительно равны, вычисляя разность, округляя до заданного числа десятичных знаков (по умолчанию 7) и сравнивая с нулем.
2) Можно ли это сделать без проверки приблизительного значения?
В качестве альтернативы нет.
Проблема с плавающей запятой не может быть обойдена, поэтому вам нужно либо "округлить" результат, заданный vec.normalize
, либо принять почти равный результат (каждый из них является приближением).
Ответ 2
Используя значение с плавающей запятой, вы принимаете небольшую возможную неточность. Поэтому ваши тесты должны проверить, попадает ли вычисленное значение в допустимый диапазон, например:
theoreticalValue - epsilon < normalizedValue < theoreticalValue + epsilon
где epsilon
- очень маленькое значение, которое вы определяете как приемлемое для вариации из-за неточности с плавающей запятой.
Ответ 3
В общем, вы не должны утверждать равенство для float. Вместо этого убедитесь, что результат находится в определенных границах, например:
self.assertTrue(abs(vec.normalize(5).length - 5) < 0.001)
Ответ 4
Я полагаю, что одна из возможностей заключается в применении функции для проверки случаев, для которых все входы, результаты всех промежуточных вычислений и вывод точно представлены через float
.
Чтобы проиллюстрировать:
In [2]: import math
In [4]: def norm(x, y):
...: return math.sqrt(x*x + y*y)
...:
In [6]: norm(3, 4) == 5
Out[6]: True
Не уверен, насколько это практично...