Haskell - assert that a function was called

Is it possible to check that a function is being called in Haskell HSpec?

Assuming I have two functions foo and bar that will transform my data.

foo :: Stuff -> Stuff
bar :: Stuff -> Stuff

      

And I have a function that applies either foo or bar to Stuff depending on whether it received 'f' or 'b' as its second argument and returns the result of the applied function.

apply :: Stuff -> Char -> Stuff

      

And in my tests, I've tested each of the foo and bar functions comprehensively so that I don't want to test the effect in apply .

Is it possible to check that the function foo or bar was called? depending on what argument is passed to be applied?

+3


source to share


2 answers


"I think more of TDD like OOP. Is that possible in Haskell?"

The best question is, "Is this the case in Haskell?" -)

[I understand that this is not the question you actually asked. Feel free to ignore this answer.]

In OO language, we create objects that talk to other objects to get our work done. To test such an object, we create a bunch of fake objects, bind the real object to the fake ones, run the method (s) we want to test, and assert that it calls the fake methods with expected inputs, etc.



In Haskell, we write functions. The only thing a pure function does is take some input and produce some output. So, to test that you just need to start this thing by giving it known inputs and checking that it returns known outputs. What other functions it calls while doing this is irrelevant; all we care about is the correct answer.

In particular, the reason we don't usually do this in OOP is that calling some arbitrary method can cause "real work" to happen - reading or writing files on disk, opening network connections, talking to databases and other servers, etc. If you're just testing one piece of your code, you don't want the test to depend on whether any database on the real network is running a server somewhere; you just want to test one small part of your code.

With Haskell, we separate everything that can affect the real world from everything that only does data transformations. Testing stuff that just converts data to memory is deliciously trivial! (Testing the parts of your code that interact with the real world is still hard, but hopefully those parts are very small right now.)

The choice of the Haskell test style seems to be property based. For example, if you have a function to solve an equation, you write a QuickCheck property that randomly generates 100 equations, and for each one checks if the number returned actually solves the original equation or not. This is a tiny piece of code that automatically checks everything you ever wanted to know! (But not really: you need to make sure that the "random" chosen equations actually test all the code paths you're interested in.)

+3


source


(Not exactly Haskell, but close.)

fooP = point . foo
-- testable property: forall s. foo s = runIdenity $ fooP s

barP = point . bar
-- similar testable property

fooAndWitness :: Stuff -> Writer String Stuff
fooAndWitness = fooM >> tell "foo"
-- testable property forall s. (foo s, "foo") = runWriter $ fooAndWitness s

barAndWitness :: Stuff -> Writer String Stuff
barAndWitness = barM >> tell "bar"
-- similar testable property

applyOpen :: Pointed p => (Stuff -> p Stuff) -> (Stuff -> p Stuff) -> Stuff -> Char -> p Stuff
applyOpen onF _   x 'f' = onF x
applyOpen _   onB x 'b' = onB x
applyOpen _   _   x _   = point x
-- semi-testable property (must fix p):
-- forall f b s c. let a = applyOn f b s c in a `elem` [f s, b s, point s]
-- In particular, if we choose p carefully we can be, at least stochastically,
-- sure that either f, b, or neither were called by having p = Const [Int], and running several tests
-- where two random numbers are chosen, `f _ = Const $ [rand1]`, and `b _ = Const $ [rand2]`
-- and verifying we get one of those numbers, which could not have been known when applyOpen was written.

applyM = applyOpen fooM barM
-- similar testable property, although but be loose the "rigged" tests for variable f/b, so
-- some of our correctness may have to follow from the definition.

apply = (runIdentity .) . applyM
-- similar testable property and caveat

      



Pointed

is a type class that fits between Functor and Applicative and provides point

with the same semantics as pure

or return

. Only the law follows from the parametricity:(. point) . fmap = (point .)

0


source







All Articles