Haskell Feature Flag Demo (Part 4)
This entry of the Haskell Feature Flag Demo series presents tests for the demo using simple types as well as the demo using tagged feature flag configuration.
Spec
The Spec module defines the main function for the test,
using the tasty
test framework. It is straightforward except that it sets the
TASTY_NUM_THREADS environment variable to ensure that the
tests are executed sequentially. This is essential because the tested
code uses global storage. Tests use different feature flag
configuration, and tests running in parallel would conflict.
main :: IO ()
main = do
    setEnv "TASTY_NUM_THREADS" "1"  -- run only one test at a time!
    defaultMain $ testGroup "test"
      [ Demo1.IO.Test.tests
      , Demo2.IO.Test.tests
      ]Demo1.IO.Test
This module implements tests for the foo and
bar pure functions as well as the run function
of the demo
using simple types.
Function foo is tested using unit tests, passing
different feature flag configuration for each test.
testFoo :: TestTree
testFoo = testGroup "foo"
    [ testCase "-ffSomeBug" $ 13 @=? Demo1.IO.foo False
    , testCase "+ffSomeBug" $ 42 @=? Demo1.IO.foo True
    ]Function bar is tested using property testing. The
property asserts that the refactored implementation is equivalent to the
original implementation.
testBar :: TestTree
testBar = testProperty "bar" prop_equiv
  where
    prop_equiv :: Int -> Bool
    prop_equiv n = Demo1.IO.bar False n == Demo1.IO.bar True nFunction run is tested using unit tests, passing
different combinations of feature flag configurations for each test.
testRun :: TestTree
testRun = testGroup "run"
    [ testCase "-ffSomeBug -ffSomeBusinessLogic" $
        (26 @=?) =<< Demo1.IO.run (ffMap False False)
    , testCase "-ffSomeBug +ffSomeBusinessLogic" $
        (26 @=?) =<< Demo1.IO.run (ffMap False True)
    , testCase "+ffSomeBug -ffSomeBusinessLogic" $
        (84 @=?) =<< Demo1.IO.run (ffMap True False)
    , testCase "+ffSomeBug +ffSomeBusinessLogic" $
        (84 @=?) =<< Demo1.IO.run (ffMap True True)
    ]
  where
    ffMap :: Bool -> Bool -> FF.FeatureFlagMap
    ffMap ffSomeBug ffSomeBusinessLogic = Map.fromList
      [ (FF.Fix_202407_SomeBug_42, ffSomeBug)
      , (FF.Ref_202407_SomeBusinessLogic, ffSomeBusinessLogic)
      ]Demo2.IO.Test
This module implements tests for the foo and
bar pure functions as well as the run function
of the demo
using tagged feature flag configuration.
Function foo is tested using unit tests, passing
different feature flag configuration for each test. Helper function
ffSomeBug constructs a feature flag configuration of the
correct type. The default case is tested as well.
testFoo :: TestTree
testFoo = testGroup "foo"
    [ testCase "-ffSomeBug" $ 13 @=? Demo2.IO.foo (ffSomeBug (Just False))
    , testCase "+ffSomeBug" $ 42 @=? Demo2.IO.foo (ffSomeBug (Just True))
    , testCase "!ffSomeBug" $ 42 @=? Demo2.IO.foo (ffSomeBug Nothing)
    ]
  where
    ffSomeBug :: Maybe Bool -> FF.FeatureFlagConfig FF.Fix_202407_SomeBug_42
    ffSomeBug = FF.FeatureFlagConfigFunction bar is tested using property testing. The
property asserts that the refactored implementation is equivalent to the
original implementation. Note that the ffSomeBusinessLogic
helper function hard-codes the Just constructor because the
case of no configuration is irrelevant to this property.
testBar :: TestTree
testBar = testProperty "bar" prop_equiv
  where
    prop_equiv :: Int -> Bool
    prop_equiv n =
      Demo2.IO.bar (ffSomeBusinessLogic False) n
        == Demo2.IO.bar (ffSomeBusinessLogic True) n
    ffSomeBusinessLogic
      :: Bool
      -> FF.FeatureFlagConfig FF.Ref_202407_SomeBusinessLogic
    ffSomeBusinessLogic = FF.FeatureFlagConfig . JustFunction run is tested using unit tests, passing
different combinations of feature flag configurations for each test.
testRun :: TestTree
testRun = testGroup "run"
    [ testCase "-ffSomeBug -ffSomeBusinessLogic" $
        (26 @=?) =<< Demo2.IO.run (ffMap False False)
    , testCase "-ffSomeBug +ffSomeBusinessLogic" $
        (26 @=?) =<< Demo2.IO.run (ffMap False True)
    , testCase "+ffSomeBug -ffSomeBusinessLogic" $
        (84 @=?) =<< Demo2.IO.run (ffMap True False)
    , testCase "+ffSomeBug +ffSomeBusinessLogic" $
        (84 @=?) =<< Demo2.IO.run (ffMap True True)
    ]
  where
    ffMap :: Bool -> Bool -> FF.FeatureFlagMap
    ffMap ffSomeBug ffSomeBusinessLogic = DMap.fromList
      [ FF.Fix_202407_SomeBug_42 ==> ffSomeBug
      , FF.Ref_202407_SomeBusinessLogic ==> ffSomeBusinessLogic
      ]Code
The full source is available on GitHub.
These tests uses GHC 9.6.5 and can be run using the following command.
$ cabal testAlternatively, if you use Stack, run the demo using the following command.
$ stack test