Functional Testing

in object oriented programming, I have objects and state. so I can mock all the dependencies of the object and test the object. but functional programming (especially pure) refers to composing functions

it is easy to check a function that is independent of other functions. we just pass the parameter and check the result. but what about a function that performs other functions and returns functions?

let's say I have the code g = h1 โˆ˜ h2 โˆ˜ h3 โˆ˜ h4 . should i check only function g ? but this is integration / functional testing. it is impossible to test all branches with integration tests only. how about unit testing? and this gets complicated when a function takes more parameters.

Should I create custom functions and use them as mocks? would it be costly and error prone?

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

+7
unit-testing functional-programming haskell testing
source share
2 answers

In your example, you can test h1, h2, h3 and h4 separately, without problems, because they are practically independent of each other. There is nothing stopping you from testing g. But is there a "unit"? Well, a very good definition of unit test was given by Michael Perse in his famous block-testing book, Effective Work with Obsolete Code. He says unit tests are quick and reliable to run at the stage of fixing your assembly pipeline, and fast enough to run developers. Thus g is the โ€œunitโ€ of this measure. Another great prospect of unit testing is from hexagonal architecture, see TDD, where is all this wrong? They say that you want to test your application API through the "ports" that it uses to interact with the outside world. Your g is also a unit by this definition. But what do they mean by โ€œportโ€, and can we relate this to Haskell? Well, a typical port might be a database connection, which the application uses to store things in the database. In Hexagonal, you would like to test this interface, most likely because of the layout. In Haskell terms, the core of the application is pure code, and the ports are IO. The fact is that you want to present your "seams" (such as mocks) in the I / O interface. Therefore, you probably do not want to worry about splitting g.

But how do you enter โ€œseamsโ€ for testing in Haskell? After all, there is no framework for dependency injection (and should not be). Well, to answer this, as always in Haskell, use functions and parameterization. For example, suppose you have a function foo that is defined in terms of a function panel. You want to change the bar, so this is a test double test and a regular value the rest of the time. Just enter bar as a parameter:

 Module Foo foo bar = ... bar ... Module Test foo = Foo.foo testBar Module Real foo = Foo.foo realBar 

You donโ€™t need to do it this way, but the fact is that parameterization gives you more than you think.

Ok, but what about testing IO in Haskell? How do we โ€œmockโ€ these IO actions? One way to do this, as in JavaScript: create data structures filled with IO actions (they are called "objects" ;-)) and pass them. Another way is not to use the IO type directly, but instead access it through one of the two monadic types - the real one and the test type, which are instances of the same type that determine the actions you want to change. Or you can make Free Monad (using free or operational packages) and write two translators - test and real.

Thus, testing clean code is so simple that pretty much anything you try will work. Testing I / O code is more complicated, so we isolate it as much as possible.

+3
source share

I also thought about testing in functional code. I do not have all the answers, but I will write a little here.

Functional programs are combined in different ways and require different approaches to testing.

If you take even the most superficial look at Haskell testing, you will inevitably come across QuickCheck and SmallCheck, two very famous Haskell testing libraries. They both perform property-based testing.

In OO, you must carefully write individual tests to set up half a dozen mock objects, call a method or two, and make sure the expected external methods are called with the correct data and / or the method ultimately returns the right answer. This is quite a bit of work. You are probably doing this with only one or two test cases.

QuickCheck is something else. You can 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 single line. Then, the QuickCheck library will automatically create hundreds of randomly generated lists and verify that the specified condition is met for each of them. And if this does not happen, he will spit out the exact input on which the test failed.

(Both QuickCheck and SmallCheck do roughly the same thing. QuickCheck generates random tests, while SmallCheck systematically tries to use all combinations to a certain size.)

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

Haskell can help too. I read an article about the library [I don't know if it has ever been released] that actually uses the lazy Haskell score to determine what code is testing with input. As in the case, it can determine whether the function under test checks the contents of the list or only the size of this list. It can determine which fields of this customer record relate to. And so on. Thus, it automatically generates data, but does not spend hours producing various random variations of data parts that are not even related to this particular code. (For example, if you sort clients by ID, it does not matter what is in the "Name" field.)

As for the test functions that accept or produce functions ... yes, I have no answer to this question.

+3
source share

All Articles