I recently “talked” to my past self of several years ago, and we came to talk about unit testing. Back then, I did not care about unit testing. Today, I still do not care, but in a different way. Despite years passed, many of the issues raised in this conversation are still relevant. Do you want to know how it went? Here it is, embellished with snippets of code to show my progression…
You know, you should start putting a little more emphasis on testing. It’s not just a fashion, it’s going to stay and believe me, it’s much more important than you can think.
I don’t care about unit testing! We have a quality assurance team that makes sure our applications are working.
But how often does it come back to you for “little bugs”? Unit testing would have prevented this, and the QA team could have focused on checking the application’s operation, not checking if it was broken or not.
But Bruno, it’s complicated to write unit tests. How can I test a function like this one?
Certainly, testing a function like this one is a challenge, because it does a lot more than it should. The function creates or updates a customer, calculates the price based on taxes, and finally saves the invoice. Although it is necessary in the process, all this logic has no place here. You could move the customer and the taxes in their own class, so that each one takes care of a single thing. This would allow us to respect the SOLID principle of single responsibility, which says that a class should only have one single responsibility. This way, regarding the testing of this method, we can focus on verifying that it is doing its job well, which is orchestrating everything.
I followed your recommendation. The logic to create or update the client is encapsulated in its class, and I now have a class to calculate my price. On the other hand, I still do not see how it can help me with my tests, I have no control over these objects inside my method… What do you think?
I totally agree with you, this was only the first step. We will talk about another SOLID principle, that of dependency inversion. The current problem, which is probably the main reason why you have trouble testing, is that your class depends on these other classes to work. So, what if rather than depending on these classes, you depended on interfaces? All your class would need is to receive these interfaces, in the builder for example, and it is the caller of this one which would take care of creating the objects which implement these interfaces.
Okay. I did a little refactoring. My classes are now instances of interfaces that are passed into the builder of my class. But I still have to create them to test my method, so if you want my opinion, I just moved the problem…
We come to the interesting part: the “mocks”. Basically, these objects are behaviour simulators for a given object. So we can tell our simulator how we want it to behave, just to allow us to test something else based on that behaviour. If we take your example, it can allow us to simulate a return of the database for a new customer, as if it had really been created. And as we control this value, we can easily verify that it is actually the one that was sent to save the invoice. For example, if you wanted to check that there are no tax calculation and saving when you do not have a customer number, your test might look like this:
And besides, now that each of your three classes is well isolated, you will see that it becomes rather easy to test their behaviours individually.
Here I understand! That’s great! So now that I know all my parts work well, I can be confident that my app will always work, right? No need to conduct quality assurance about this?
If only it were so simple… Conducting unit testing on classes that have only one responsibility, by simulating their dependencies’ behaviour, is already a big step forward. But that only tests each component. Imagine that your car manufacturer tested that the brakes are working, that the pedal can be pushed, but not the joint operation of these two elements. Would you trust your car? Well, the answer is the same on the software side. Integration testing, which is essentially automated tests for specific scenarios on a complete environment, and manual testing by real people, are still necessary.
I understand, so we must continue manual testing, but still focus on unit testing. At least I now know how to apprehend everything in order to revamp my code.
We talked about your method that was already in place, and we moved it to something a little better structured. Moreover, what is good in this is that not only can you conduct your unit testing more easily, but in addition we have greatly improved your code’s structure based on fundamental programming principles. And understand that you could even do this for new code, not just for existing stuff. It can only help your career…
And if we had more time, we could talk about Test Driven Development, where you write down tests before you write code, and you gradually build your functional code as you need it at the test level. We could also have talked about “fakes” that are concrete classes that return specific data without following the real logic in the background, or “stubs” that are somehow the old way to make mocks. Even if I personally think it’s better to focus on the mocks.
I’m interested, but we do not have much time left. It would be nice to find another time to talk to each other. And tell me, since I am talking to my future self, could you give me a little scoop on what I’m going to do?
This may create a problem with the space-time continuum, but why not… You’re going to be co-host of a YouTube channel, the Bracket Show, which talks about issues related to software development. Better than that, you’ll even make an episode about what we just talked about: