Program correctness has always been an important topic in computer science, and is only showing growth as software becomes more complex. Statically typed functional programming provides compiler guidance and guarantees. Advanced typing features can provide additional ways to restrict and formally prove properties within the constraints of the original program.
This has even (half-seriously) popularized remarks about how it's enough to get a program to compile to be sure it is correct. While there is some degree of truth to this (Haskell programs can escape some classes of bugs by merely enabling all warnings and having a reasonable coding standard in place), this is obviously not true for all but most trivial software.
Formal verification can help whenever that is not possible, but it is still impractical for all but small and critical systems. We still rely on writing automated unit, property, and integration tests to ensure our programs behave in the way we expect them.
At Hasura, we run these tests automatically in continuous integration (CI) services, and they often take up the majority of CI time. We dedicate a significant amount of time writing and maintaining these tests.
What is a good test? How do we write good tests? How do we organize them? What's important to consider when writing tests?
I describe common patterns and recommended style guides for writing tests that optimize for ease of reading and understanding rather than writing or modifying. It should be apparent what it is that we're testing, what are the assumptions of the test, what is being set up or arranged before the test, as well as what it is we are testing and what is the expected response. We should strive to maximize the domain information and minimize accidental complexity, such as particularities of the testing framework or programming language we're using.
In a language as expressive as Haskell, we should be able to write unit and integration tests that are understandable and modifiable by domain experts (such as power users or product owners), not only Haskell developers.
This can generally be achieved by focusing on getting the domain-specific data together in a record, thus making the test-relevant information easily readable and understandable.