Jak właściwie porównać podwójne wartości równości w teście jednostkowym?

20

Niedawno zaprojektowałem moduł szeregów czasowych, w którym moje szeregi czasowe są zasadniczo SortedDictionnary<DateTime, double>.

Teraz chciałbym utworzyć testy jednostkowe, aby upewnić się, że ten moduł zawsze działa i daje oczekiwany wynik.

Częstą operacją jest obliczanie wydajności między punktami w szeregu czasowym.

Więc tworzę szereg czasowy, powiedzmy {1.0, 2.0, 4.0} (w niektórych terminach), i spodziewam się, że wynik wyniesie {100%, 100%}.

Chodzi o to, że jeśli ręcznie utworzę szereg czasowy z wartościami {1.0, 1.0} i sprawdzę równość (porównując każdy punkt), test nie przejdzie, ponieważ zawsze będą niedokładności podczas pracy z reprezentacjami binarnymi rzeczywistych liczby.

Dlatego postanowiłem utworzyć następującą funkcję:

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

Czy istnieje inny wspólny sposób radzenia sobie z takim przypadkiem?

SRKX
źródło

Odpowiedzi:

10

Mogę wymyślić dwa inne sposoby rozwiązania tego problemu:

Możesz użyć Is.InRange:

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

Możesz użyć Math.Round:

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

Myślę, że oba sposoby są bardziej wyraziste niż funkcja dedykowana, ponieważ czytelnik może dokładnie zobaczyć, co się dzieje z twoim numerem, zanim zostanie porównany z oczekiwaną wartością.

dasblinkenlight
źródło
2
Tylko uwaga, że ​​ta odpowiedź jest specyficzna dla NUnit i pokazuje model asercji oparty na „ograniczeniach”. Klasyczny model asercji wyglądałby następująco: Assert.AreEqual (oczekiwany, rzeczywisty, tolerancja);
RichardM
1
@RichardM: Opublikuj to jako odpowiedź, a ja zaznaczę, że zaakceptuję.
SRKX
Odpowiedź @dasblinkenlight jest poprawna, wystarczy dodać trochę szczegółów (ponieważ może to nie być jasne - klasyczny model asercji to także NUnit). Inne struktury testowe (nie MSTest) prawdopodobnie mają własny model aser, który radzi sobie z wartościami zmiennoprzecinkowymi.
RichardM
1
Assert.That(result, Is.InRange(expected-tolerance, expected+tolerance));zawiedzie, jeśli tolerance/abs(expected) < 1E-16.
quant_dev
@quant_dev Masz absolutną rację. Ponieważ OP mówi o obliczaniu zwrotów jako procentów, założyłem, że abs(expected)będą to cyfry od jednej do podwójnej cyfry. Przyjąłem również tolerancję w okolicach 1E-9. Przy tych założeniach to rzeczywiście uproszczone podejście może ci dobrze służyć (używam Is.InRangew moich testach).
dasblinkenlight
3

To zależy od tego, co zrobisz z liczbami. Jeśli testujesz metodę, która ma np. Wybrać odpowiednią wartość z zestawu danych wejściowych na podstawie niektórych kryteriów, powinieneś przetestować pod kątem ścisłej równości. Jeśli wykonujesz obliczenia zmiennoprzecinkowe, zwykle będziesz musiał przetestować z niezerową tolerancją. To, jak duża jest tolerancja, zależy od obliczeń, ale przy podwójnej precyzji dobrym punktem wyjścia jest wybór tolerancji względnej 1E-14 dla prostych obliczeń i 1E-8 (tolerancja) dla bardziej skomplikowanych. YMMV oczywiście i musisz dodać małą absolutną tolerancję, jeśli oczekiwany wynik to 0.

quant_dev
źródło