16

in object oriented programming i have objects and state. so i can mock all dependencies of an object and test the object. but functional programming (especially the pure) is about composing functions

it's easy to test function that doesn't depend on other functions. we just pass parameter and check the result. but what about function that takes another functions and returns functions?

let's say i have the code g = h1 ∘ h2 ∘ h3 ∘ h4. should i test just function g? but that's integration/functional testing. it's impossible to test all branches with only integration tests. what about unit testing? and it's getting more complicated when a function takes more parameters.

should i create custom functions and use them as mocks? wouldn't it be to expensive and error prone?

and what about monads? for example how to test console output or disk operations in haskell?

6
  • 4
    What makes you think custom functions as mocks are any more expensive or error-prone than OO objects? Commented Feb 18, 2015 at 21:24
  • i don't. that's why i'm asking Commented Feb 18, 2015 at 21:29
  • 2
    Why would you want to mock a pure function? Commented Feb 18, 2015 at 21:59
  • Testing a higher-order function can be more challenging than testing a first-order one. However, it is not so different from testing a function taking objects and returning objects. If anything, it looks a bit easier, because objects have state, while functions have not. For instance, how would you test an array-sorting function taking a comparator object? How would this be different from testing a sortBy alternative? Commented Feb 18, 2015 at 22:24
  • 1
    @dfeuer You can, and I'm always puzzled as to what on Earth that actually does... Commented Feb 19, 2015 at 15:52

3 Answers 3

7

I too have been thinking about testing in functional code. I don't have all the answers, but I'll write a little bit here.

Functional programs are put together differently, and that demands different testing approaches.

If you take even the most cursory look at Haskell testing, you will inevitably come across QuickCheck and SmallCheck, two very well-known Haskell testing libraries. These both do "property-based testing".

In an OO language you would laboriously write individual tests to set up half a dozen mock objects, call a method or two, and verify that the expected external methods were called with the right data and / or the method ultimately returned the right answer. That's quite a bit of work. You probably only do this with one or two test cases.

QuickCheck is something else. You might write a property that says something like "if I sort this list, the output should have the same number of elements as the input". This is a one-liner. The QuickCheck library will then automatically build hundreds of randomly-generated lists, and check that the specified condition holds for every single one of them. And if it doesn't, it'll spit out the exact input on which the test failed.

(Both QuickCheck and SmallCheck do roughly the same thing. QuickCheck generates random tests, whereas SmallCheck systematically tries all combinations up to a specified size limit.)

You say you're worried about the combinatorial explosion of possible flow control paths to test, but with tools like this generating the test cases for you dynamically, manually writing enough tests isn't a problem. The only problem is coming up with enough data to test all flow paths.

Haskell can help with that too. I read a paper about a library [I don't know if it ever got released] which actually uses Haskell's lazy evaluation to detect what the code under test is doing with the input data. As in, it can detect whether the function you're testing looks at the contents of a list, or only the size of that list. It can detect which fields of that Customer record are being touched. And so forth. In this way, it automatically generates data, but doesn't waste hours generating different random variations of parts of the data that aren't even relevant for this particular code. (E.g., if you're sorting Customers by ID, it doesn't matter what's in the Name field.)

As for testing functions that take or produce functions... yeah, I don't have an answer to that.

Sign up to request clarification or add additional context in comments.

Comments

5

In your example, you can test h1, h2, h3 and h4 separately, no problem, because they don't actually depend on each other. There's nothing stopping you testing g either. But is g a 'unit'? Well a very good definition of a unit test is given by Michael Feathers in his famous unit-testing book, Working Effectively With Legacy Code. He says unit tests are fast and reliable to run in the commit phase of your build pipeline and fast enough for developers to run. So g is a 'unit' by this measure. The other excellent perspective on unit testing is from Hexagonal Architecture, see TDD Where Did It All Go Wrong? They say that you want to test your application's API via the 'ports' it uses to interface to the outside world. Your g is a unit by this definition also. But what do they mean by a 'port' and can we relate this to Haskell? Well a typical port might be the database connection that the application uses to store things in a database. In Hexagonal, you would want to test that interface, likely by a mock. In Haskell terms, the core of the application is pure code and the ports are IO. The point is, you want to introduce your 'seams' (such as mocks) at the IO interface. So you probably don't want to worry about splitting g up.

But how do you introduce 'seams' for testing in Haskell? After all, there is no dependency injection framework (and nor should there be). Well the go-to answer to this is, as always in Haskell, to use functions and parameterisation. For example, suppose you have a function foo that's defined in terms of a function bar. You want to vary bar so it is a test double in your test and the regular value the rest of the time. Just make bar a parameter like this:

Module Foo
 foo bar = ... bar ...

Module Test
 foo = Foo.foo testBar

Module Real
  foo = Foo.foo realBar

You don't need to do it exactly like that but the point is that parameterisation gets you further than you'd think.

Alright, but what about testing IO in Haskell? How do we 'mock out' those IO actions? One way is to do it like you would in JavaScript: create data structures full of IO actions (they call them 'objects' ;-) ) and pass them around. Another way is to not use the IO type directly but instead access it through one of two monadic types - the real and the test type that are both instances of the same type class that defines the actions you want to swap out. Or you can make a Free Monad (using the free or operational packages) and write two interpreters - the test one and the real one.

In summary, testing pure code is so easy that practically anything you try will work. Testing IO code is harder, which is why we isolate it as much as possible.

4 Comments

regarding testing g. my concerns are: if each h has some branches then g has to many possible execution flows to test them all. that's the problem with integration tests - to many possibilities
Well in your example, you're already testing h1, h2, h3 and h4, so testing g is really just testing you composed them right. You can do that with just a few sample values. You mentioned branches. In Haskell, types have branches, not just functions. The branching structure of a function mirrors the cases of the data it operates on. This makes it much easier to know what values to test. And as Mathematical Orchid says, Quickcheck and Smallcheck can generate lots of those values, giving you far better coverage than you would have otherwise.
I have written up a walkthrough of how you can achieve your second suggestion for testing IO code using a real world example: blog.pusher.com/unit-testing-io-in-haskell
@piotrek - lately, I have had no qualms about mocking the dependencies (also pure functions) to avoid the combinatorial explosion of possibilities - these dependencies not being arguments to the function under test (sut) but simple plain old functions called within the sut i.e. the sut not being a higher order function.
0

Concerning functions that takes other functions, a possibility would be to pass those as parameters (callbacks), but it could blow up the number of parameters, and you can't (at least in PHP) typehint them as distinct types (only callable in PHP). So I suggest to stick to OOP, with dependency injection. Then you can mock your dependencies with ease.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.