How do I compare Double values in Hunit?

88 views Asked by At

I would like to understand how I can use HUnit to compare Real values, specifically Double. I say errors, as far as I understand HUnit has no standard methods for comparing this type, so I stumbled upon this question:

Testing haskell equality on Doubles with HUnit?

From it I learned that there is Data.AEq, which gives the right comparator -== (the right one as far as I understand it)

But the presence of the comparator does not tell me anything about how to compare with it in HUnit, for example there is such a test:

test1 :: Test
test1 = TestCase (assertEqual "Test linearApproximation" expectedResult (linearApproximation (1, 10) 0.95 points))
  where
    points = [(1, 2), (2, 3)]
    expectedResult = Linear [Just 1.0,Just 1.95,Just 2.9,Just 3.8499999999999996,Just 4.8,Just 5.75,Just 6.699999999999999,Just 7.6499999999999995,Just 8.6,Just 9.549999999999999]
     [Just 2.0,Just 2.95,Just 3.9,Just 4.85,Just 5.8,Just 6.75,Just 7.699999999999999,Just 8.649999999999999,Just 9.6,Just 10.549999999999999]

I think it's clear to everyone why I don't like this test, I wish I hadn't written such an exact expectedResult, but I don't understand how I can apply -==

Maybe there is another way?

1

There are 1 answers

0
Stéphane Laurent On

Here is the way I use. In the tests/ folder I create this module:

module Approx where

-- round x to n digits
approx :: Int -> Double -> Double
approx n x = fromInteger (round $ x * (10^n)) / (10.0^^n)

-- check whether rounded x1 and rounded x2 are equal
approxEqual :: Int -> Double -> Double -> Bool
approxEqual n x1 x2 = approx n x1 == approx n x2

I could do instead, shorter: abs (x1 - x2) < 10.0^^(-n), but I have this approx function for historical reasons.

Then in the Main module, I have for example:

module Main where
import           Approx
import           Math.NevilleTheta -- this is a module from my package
import           Test.Tasty       (defaultMain, testGroup)
import           Test.Tasty.HUnit (assertBool, testCase)

main :: IO ()
main = defaultMain $
  testGroup "Tests"
  [ testCase "a theta_c value" $ do
      let expected = 0.902705416117337 
          obtained = theta_c 1.0 2.5
      assertBool "" (approxEqual 10 expected obtained)

  ]

And in the cabal file:

test-suite unit-tests
  type:                 exitcode-stdio-1.0
  main-is:              Main.hs
  hs-source-dirs:       tests/
  other-modules:        Approx
  Build-Depends:        base >= 4.7 && < 5
                      , tasty
                      , tasty-hunit
                      , jacobi-elliptic
  Default-Language:     Haskell2010

where jacobi-elliptic is my package.

Alternatively, you could do

assertEqual "" (approx 10 expected) (approx 10 obtained)

This has one advantage: if the equality does not hold, the two different values are printed with the failure notification.


EDIT

I finally wrote a custom "assert" function.

module Approx where
import           Test.Tasty.HUnit (Assertion, assertEqual)

-- round x to n digits
approx :: Int -> Double -> Double
approx n x = fromInteger (round $ x * (10^n)) / (10.0^^n)

-- assert approximate equality
assertApproxEqual :: String -> Int -> Double -> Double -> Assertion
assertApproxEqual prefix n x1 x2 = 
  assertEqual prefix (approx n x1) (approx n x2)

This stuff can be generalized to Num a instead of Double.