If we change code, we need to change tests because otherwise it would get stale, but they don't, because it's a live documentation. But how do we know and check if our program is doing what it's expected to do? How can we check the quality of this feedback? And here I'm not going to talk about 100% coverage.
So let's start with accuracy. If your tests fail, can you determine exactly which portion of the code has failed? How long do you take to know where it failed? So what we want here is functions that are really granular, so as granular as it is, the more accuracy you can get. So it's way faster if you have small functions with no dependencies from the outside world, so that remember pure functions.
Also about speed. The faster we find a test failing, the sooner we can identify the problem and the smaller their impact will be. So what we need here is tests that are simple to write and to maintain, and also that are fast to run. If we have a lot of complex code with a lot of dependencies from the outside world, it's going to take a lot for us to maintain these tests. And also we need tests that are reliable. We need to trust the outcome of our tests. If the outcome changes from one execution to another, if we don't change the code, the configs or the dependencies, why do we keep these kind of tests? Because we don't trust them, so it makes no sense to keep them.
Here we are usually talking about end-to-end tests. End-to-end tests are a problem because you don't walk anything, you really save stuff to the database. It's really common for them to fail for obscure reasons, like latency, garbage collection, asynchronous operation. So we need to think in a different way. Here we need to think a little bit larger. We know that unit tests cover the function levels, integration tests cover the flows, and we need to guarantee that the conversation between services is working. So usually we use end-to-end for that, but we can just think a little bit larger and test the conversation between two services at a time, for example. So here we use contract tests.
So every contract test is going to have a provider service and a consumer service, and for each communication between them, we need to define a clear interface called a contract. So now we have like automated tests that are sure that the data generated by the provider can be consumed by the consumer. And if we change anything, the contract is going to break. So it's important here to have in mind, this is not a one-to-one replacement with end-to-end tests, but most of the errors that end-to-end do catch, those tests can also catch them. So with that, we can have accuracy, speed, and reliability. And that's what we want.
So now, I'm going to talk about how to design a record. Because okay, we have the functional programming, we have the test, but how to design the code to leverage this? What we want is high maintainability with low tech debt, because we want the application that are used to give maintenance and have low tech debt. We need them to be simple and easy to work on. And this is hard because maintainability is a long term concept because in the beginning, it's really easy.
Comments