A New Graduate Software Developer’s Journey —Unit Testing
New grads are war heroes who never joined a real war! In this series, I will share the struggles that I faced as a New Graduate in my first full-time job at ÇiçekSepeti.
Okay, we got the whole data structures and algorithm classes. We passed the exams of genius professors and graduated from a university like war heroes. Now we are ready to build something like Jarvis. I don’t know about you but I thought I was completely ready. I made a graduate project like detecting cancer with Deep Learning, so what kind of problem could stop me?
1-) University Projects vs Production Code
At university, we mostly make our projects as individuals or with small groups. Mostly, these studies become short-lived projects. After the deadline, nobody decides to make changes. For instance, in our graduate project, we haven’t paid any attention to make the project readable or maintainable. Our first motivation was to make it ready and getting results with better accuracy.
On the other hand, the production code serves business for a long time. During this long-lived period, the first engineers of the code can leave work or new engineers can join the team. Perhaps, business shifts its strategy through this product and adds new features or changes existing ones. That’s why in production code we apply different strategies than university projects. We’re building the product, to keep it alive with the minimum cost possible.
2-) Understanding the Production Code
When I started my first internship at Insider and after the graduation with a full-time role at ÇiçekSepeti I realized that production code is completely different from university projects. Yes, we can say for both of them, we are handling all edge cases and performance optimization issues. But there is two main difference between them: Readability and maintainability.
You need to code like talking to the future developer who you will never meet. From the future developer aspect, WTFs per minute!? will be readability measure for your code. At the same time, you need to assure that your code will show its harmony after any business decision, related to the product given by non-developer colleagues. That’s the maintainability.
Software that contains a lot of cruft -elements of the software that impede the ability of developers to understand the software- is much harder to modify, leading to features that arrive more slowly and with more defects.
— Martin Fowler
There are concepts to provide these features on your codebase. For example, A design pattern used appropriate place can gain flexible design to the changes, or we can find easily where we will add the new feature’s code as following the structure of the design pattern. But in this article, we will discuss another important concept that helps us to build a maintainable product. Yes, you heard about it before. With this concept, we will assure that “Even God himself could not sink this codebase!”. Yes, it might seem overconfident but Unit Testing is really crucial for an application. We hope our codebase will not have the same destiny as the Titanic. But I believe, you got the message.
3-) Unit Test Basics
Like I mentioned before, there are some important rules while you are coding in production and maintainability is one of them. With unit tests, you can assure that even a newbie developer -like me- will not break your code accidentally. Yes, it might seem overconfident but let me explain with an example. We will go through a real-world e-commerce scenario.
We have a service layer to code the business logic separated from the database and from controller logic. After an operation, the system needs to update product variant stock. To inform the readers who haven’t work any e-commerce project, “product” is the shoe itself, and “variant” is each of the size options available in the shop. Eventually, we buy a variant of the product.
Let’s create a method named DecreaseProductVariantStock. When a purchase occurred this method will be called to decrease the purchased product variant stock. For example, we sold the last 34 sized X red shoes. And all other variants(size options) for this product is out of stock. In this case, we don’t want to show this product on the listing pages. So we are managing this case with a boolean value named availability. Show if true don’t show if not.
During this process, we need to check is there any in-stock product variant for the given product id.
It’s a basic business logic for an e-commerce website.
Now, let’s code the unit tests that will protect the written business logic. I am following a template to write readable tests.
Naming: Test method naming important to give quick information about the test. During a failure, the developer will look into the test and will try to understand why it failed.
Method Body: We can have a standard order to make things in a test method. I prefer to add comments for each part to make it more readable. Firstly, you will arrange the required mock data and will mock the dependencies to be able to test the service independently from other dependent services. After that, you will call the method that you test. That is the act part. Lastly, you will compare the expected and actual results, and depends on that you will produce assertions. That’s the basic flow for a unit test, mostly.
Now, let’s implement an example unit test.
ProductVariantService and ProductRepository classes are dependent components for our class under the test. So we are mocking these dependencies and trying to test our isolated method. Mocking is the setting up responses for the callee method and returning values as predefined. In the act part, we are creating our class with the mocking dependencies and calling the tested method. Finally, we are checking the result values with the assertion methods. It will throw an exception and the test will be failed.
Static methods can’t be mocked. So if you have a static dependency that needs to be mocked, you can wrap it with a public wrapper method and use the static method via it. So if you have a chance, don’t use the static method directly and replace it with your wrapper public method. Otherwise, If there is a public method directly used in the static method, this method can be mocked either.
Private methods, shouldn’t be tested directly, but only their effects on the public methods that call them. You can hit these methods while you are testing public classes that private methods used in. Private methods can’t be accessed from the test class so don’t look for workarounds.
Don’t use mutable values
The unit test mustn’t depend on mutable values. For instance, if you have DateTime.Now usage in your code and your test depends on this mutable value, you need to mock it. A unit test must always produce the same results for the unchanged code.
4-) Continuous Integration Part
If we want to understand why unit testing gained popularity, we need to understand how our approach changed for delivering products. I will go through it as fast as I can.
Continuous Integration doesn’t get rid of bugs, but it does make them dramatically easier to find and remove. — Martin Fowler
In ancient times, deploying a new version of the product was a big deal. A lot of new features come together and these feature bundles became the new version of our product. But this approach was taking so much time and effort. So software gods decided to separate all features from each other and to handle the features as deliverable units. But for this agile approach, there was a problem. In the old approach, at the end of the long testing process, we can assure that the new version of the product works with no problem as a whole. But now we got these tiny deliverable units. For each of the units, will we go through all the test process over again? This will cost us too much time. So how can we be sure, these tiny features will not break any part of the system without doing costly tests? At this point, God sent us Automated Tests.
When you made the change on the codebase, automated tests will run and will confirm your integration didn’t break anything. If any tests didn’t get the expected results, It will warn you with the assertions.
I am trying to show you the place of Unit Testing in the Big Picture. The big picture is the journey of the code from the developer’s laptop to the money maker production environment. Let’s follow the CI Pipeline from the diagram to understand the big picture and to figure out the role of the unit tests.
This pipeline occurs hundreds of times a day. So automated tests guarantee that result of the each build produces expected outputs with the given predefined inputs.
Before finishing the article, I want to mention to Code Coverage myth. Covered code means executed part of the codebase while tests are running. In my experience, code coverage doesn’t show anything about the solidity of your code If you didn’t follow the rules of the unit testing. Even you didn’t use correct assertions, the tested code part will be seemed covered. So don’t trust too much to code coverage or use it as a target parameter. The main goal should be to provide an assurance for the changeable part of the code. Don’t overengineer it or don’t underestimate it :).
Thanks for reading, feel free to comment!
- Clean Code by Robert C. Martin