Functional programming and testing. Maybe you’ve given them a try in isolation, but somehow you never made either a part of your regular practice. They may sound innocent by themselves, but together testing and functional programming can create an irresistible temptation, almost compelling you to write cleaner, tighter, more maintainable code.
Well the good news is that working with both techniques together can offer some real advantages. In fact, once you’ve tasted how sweet this combination can be, you might find yourself as addicted to it as I am, and I’d be willing to bet that you’ll be coming back for more.
Testing is about making sure that the code in your application does what you expect it to do, and keeps doing what you expect it to do when you make changes so you have a working product when you’re done. You write a test that defines your expected functionality under a defined set of circumstances, run that test against the code, and if the result isn’t what the test says it should be, you get a warning. And you keep getting that warning until you’ve fixed your code.
Then you get the reward.
And yes, it will make you feel good.
Testing comes in a lot of flavors, and there is room for healthy debate about where the borders are drawn, but in a nutshell:
- Unit tests validate the functionality of isolated code
- Integration tests verify the flow of data and the interaction of components
- Functional tests look at the behavior of the overall application
You can write a test at any point in the coding process, but I’ve always found that its most efficient to write a unit test before writing the function you’re planning to test. This practice, known as test-driven development (TDD), encourages you to break down the functionality of your application before you start writing and determine what results you want from each section of code, writing the test first, then coding to produce that result.
A side benefit is that TDD often forces you to have detailed conversations with the people who are paying you to write your programs, to make sure that what you’re writing is actually what they’re looking for. After all, it’s easy to make a single test pass. What’s hard is determining what to do with all the likely inputs you’re going to encounter and handle them all correctly without breaking things.
As you can imagine, the way you write your code has a lot to do with how easy it is to test. There are some code patterns, such as tightly coupling the behavior of one function to another, or relying heavily on global variables, that can make code much more difficult to unit test. Sometimes you may have to use inconvenient techniques such as “mocking” the behavior of an external database or simulating a complicated runtime environment in order to establish testable parameters and results. These situations can’t always be avoided, but it is usually possible to isolate the places in the code where they are required so that the rest of the code can be tested more easily.
Functional programming allows you to deal with the data and the behavior in your application independently. You build your application by creating a set of independent functions that each work in isolation and don’t rely on external state. As a result, your code becomes almost self-documenting, tying together small clearly defined functions that behave in consistent and understandable ways.
How Pure Functions Work
Functional programming encourages you to build your application out of tiny, reusable, composable functions that just do one specific thing and return the same value for the same input every single time. A function like this is called a pure function. Pure functions are the foundation of functional programming, and they all share these three qualities: