Why I write Tests First.
Mohammed Hussein
Solutions Architect @ pyraCode GmbH | Knowledge Sharing, Interpersonal Skills
This is the second article I ever write in my life. Back in college, I did write some scientific papers but I never published them. I learned that writing an article is a huge responsibility because if you are wrong, and you very well could be, you have to take the criticism and you need to let experts educate you to make yourself a better person. Now, accepting that criticism or not, that entirely is up to you. I would rather be honest to myself and learn more.
So there is a game I guess they call it "shooting" where you go to a shooting range and you get a rifle and you try hitting the target. I have a vivid image of Tom Cruz in the movie "Jack Reacher (2012)", where he goes to a shooting range and the owner asks him to shoot a target. I want you to take a look at the scene to see how much badassery Tom Cruz exhibits in that particular scene; it is really something. I took the liberty to share this link on YouTube here: Jack Reacher Scene.
Anybody would agree that shooting is not so easy as in the movies and everybody knows movies are not like the real thing. However, we know from the bottom of our hearts that movies try to simulate the reality as sincere and accurate as possible. That is why when they get something wrong cinema sins goes after them. I see something different here though. In that one scene I just mentioned, Tom Cruz walks to fix a bullseye and then he gets back to the owner at the firing line and assumes a laying down sniping position. He then tries to shoot the target and just that he does. One might ask: "well, what in is so special about that?" And the answer to that question is: "The sequence of actions." Tom Cruz fixes the Bullseye first and then he shoots the target. That is exactly what writing tests first is.
When I start with a list of features I think of them as a collection of targets that I would like to hit. It makes no sense to shoot and then draw the bullseye where your shot landed. It would be a sin. I hate sins, especially those which are obvious. Not only does that make one an incompetent sniper, it would make people laugh at him, lose respect for him. Well, why should it be any different when it comes to writing production code for your feature list?
It does not matter what process you follow. If you are not testing your code, then you cannot prove it works according to a client's specs. And you, surely, will not prove their correctness mathematically; Dijkstra tried that and failed miserably. He might have given us structural programming in 1968 and for that I am personally thankful but he could not draw, from mathematical themes, how a piece of code could be proven correct. That should not be a problem though because testing is here for the rescue.
As Robert C. Martin says:
"Imagine if you are in a room full of programmers and no matter which one you pick at any given moment of time, everything worked for them 10 seconds ago. How much debugging are you going to need to fix any problem? The answer is not very much debugging."
Why is that statement very powerful? I tell you; because this is exactly what we suffer when we are programming. See, "programming" and "writing code" are two different actions. When you are programming you are:
- Reading previous code you have written.
- Reading previous code others have written.
- Trying to understand why the code was written in the way it is right now.
- Trying to understand why it does/not work as you expect.
- Debugging to find the problem of your code to finish your task.
- Writing the feature code, at last.
We all have been hindered by rotten code, that is, code that looks ugly and that stinks. I used to work for a company and we had a function that we called "Legendary function." You know why we called it that? Because it was more than 240 lines of code long.
Since then, no body wanted to clean it up because every time they try to clean it, or should I say refactor it, they were scared that it would break something in the system. They did not want to take responsibility of the code. They were not professional. It is as simple as that. Ever since I swore to write clean code and that is why I picked up the book under the same title "Clean Code: A handbook of Agile software craftsmanship" by Robert C. Martin. Today I brag about my code. The longest function that I am not proud of is 4 lines of code long. Not 10, not 15, not even 20.
Rules of Test-driven Development are simple but not so easy to grasp nor to appreciate. These rules protect you from yourself. You are not a great programmer. No body is. Even Kent Beck himself says that about himself. Kent Beck said: “I'm not a great programmer; I'm just a good programmer with great habits.” A good programmer knows how to follow great practices. A good programmer knows how to do it right. A good programmer realizes he is not done when the code works, he is done when the code is clean.
Writing clean code from the get going is almost impossible. The only way to do it. Is to do it after the code is written. It is now a matter of how to reserve the functionality we know works. A reliable test suite will tell you if you broke something. That is what is called "Regression." If you know you broke something you can revert back and plan your action again. It is always feasible to clean and refactor the code. Test first is the answer as to how to start with simple code and evolve the code as you go. However, the code stays simple throughout its lifetime. The 2 reasons why you should go test first are:
- They force you to write the minimum necessary.
- They force you to write decoupled code which is the prerequisite for being testable code.
I developed a real system that was a shell program that you start like a terminal and start giving it commands and it will start scaffolding a file structure for a framework I was developing. I built that without Big Design Upfront (BDUF.) That company I used to work for had a very weird process they made us follow. We had to come up with database schema before the screens/views of the application. We had to come up with the tables and relations between those tables before we could tackle the basic architecture. This is called Big Design Upfront, that is, I want to know everything about the application from day 1. That, of course, is absurd. How did I build that Shell program? Well the answer is simple: Passing one Test at a time and starting with the most primitive, stupid, simple test first. Eventually, I found my system to be very neat, tidy and had a very beautiful class structure.
As you can see, it is very simple. This system was implemented using PHP 7.4 and it did not make any use of a database. I only started thinking about the Application class and eventually I found myself developing a console interface to be able to take standard input and print to standard output. Designing that component, I programmed for an interface and not for implementation (Classic Design Principle.) I found myself developing a class for parsing each command coming from the application. I found myself implementing a Command design (Command Design Pattern.)
If you are paying attention to the class diagram above, I was not hindered by my tests. My tests kept telling me what I needed, when I needed it, and exactly, how I needed it to be designed. Test-driven development did not stop me from using design principles. It did not stop me from using a modern design pattern. As a matter of fact, when I developed that Command pattern, I did not notice I was actually implementing the Command design pattern. When I sketched the diagram to attach the image to this article, I found a striking resemblance between my diagram and that of a command design pattern, that naming, the relation, even the hidden Observer design pattern between the Application and the CommandParser class. Those are two different design patterns working together to achieve a functionality.
The beautiful thing about this design is that I never forced anything. I just followed the algorithms I had in mind. I satisfied the list of requirements, or should I say targets, I had their bullseye in front of me. I fixed the bullseye first and then I shot. I got errors. I fixed them by adding the minimum necessary code to make the currently failing test pass. And that design emerged sort of magically out of the mist. I am not saying this is perfect. There, definitely, is room for improvement here. I want you to tell me how I can improve the performance of my system's design if it is possible.
Finally, I hope I contributed to your understanding of how testing first before writing production code could make your life, as a professional software engineer, better. Let me know if you have any questions, comments, or requests.
Full-Stack Software Engineer
4 个月Great article on Test-driven development I also have noticed you have a TDD in Laravel playlist which is presented in Arabic. Can you also make something similar while reviewing the new unit testing library called pestphp made by one of the Laravel staff members?