Как правильно сравнить двойные значения на равенство в модульном тесте?

20

Недавно я разработал модуль временных рядов, где мои временные ряды по сути являются SortedDictionnary<DateTime, double>.

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

Обычной операцией является вычисление производительности между точками во временном ряду.

Поэтому я делаю временные ряды с, скажем, {1.0, 2.0, 4.0} (в некоторые даты), и я ожидаю, что результат будет {100%, 100%}.

Дело в том, что если я вручную создаю временной ряд со значениями {1.0, 1.0} и проверяю равенство (сравнивая каждую точку), тест не будет пройден, так как всегда будут неточности при работе с двоичными представлениями реального номера.

Поэтому я решил создать следующую функцию:

private static bool isCloseEnough(double expected, double actual, double tolerance=0.002)
{
    return squaredDifference(expected, actual) < Math.Pow(tolerance,2);
}

Есть ли другой распространенный способ справиться с таким делом?

SRKX
источник

Ответы:

10

Я могу придумать два других способа решения этой проблемы:

Вы можете использовать Is.InRange:

Assert.That(result, Is.InRange(expected-tolerance, expected+tolerance));

Вы можете использовать Math.Round:

Assert.That(Math.Round(result, sigDigits), Is.EqualTo(expected));

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

dasblinkenlight
источник
2
Просто обратите внимание, что этот ответ специфичен для NUnit и демонстрирует модель утверждений «на основе сравнения». Классическая модель утверждений будет выглядеть так: Assert.AreEqual (ожидаемый, фактический, допуск);
RichardM
1
@RichardM: Опубликуйте это как ответ, и я выберу его, примите его.
SRKX
Ответ @dasblinkenlight правильный, просто добавив некоторые детали (так как это может быть непонятно - классическая модель утверждений также NUnit). Другие тестовые среды (не MSTest), вероятно, имеют свою собственную модель утверждения для работы со значениями с плавающей запятой.
RichardM
1
Assert.That(result, Is.InRange(expected-tolerance, expected+tolerance));потерпит неудачу, если tolerance/abs(expected) < 1E-16.
quant_dev
@quant_dev Вы абсолютно правы. Поскольку OP говорит о расчете доходности в процентах, я предположил, что abs(expected)это будет однозначный или двузначный знак. Я также предположил допуск в районе 1E-9. При этих допущениях этот, по общему признанию, упрощенный подход мог бы служить вам достаточно хорошо (я использую Is.InRangeв своих тестах).
dasblinkenlight
3

Это зависит от того, что вы делаете с числами. Если вы тестируете метод, который должен, например, выбирать подходящее значение из входного набора на основе некоторых критериев, то вам следует проверить на строгое равенство. Если вы выполняете вычисления с плавающей запятой, обычно вам нужно тестировать с ненулевым допуском. Насколько велик допуск, зависит от расчетов, но с двойной точностью хорошей отправной точкой является выбор относительного допуска 1E-14 для простых вычислений и 1E-8 (допуск) для более сложных. Конечно, YMMV, и вам нужно добавить небольшую абсолютную погрешность, если ожидаемый результат равен 0.

quant_dev
источник