Home

Writing code which does what it is intended to do

As a developer, our aim should be to write code with minimum bugs (0 bug is ideal but reality is always different). So, what do we classify as a bug? - Anything in our code which makes our service/software diverge from the requirement is a bug and who is going to decide those behaviours as bugs? - Its our ultimate tester-“the customer”. Here customer may be a human operating on the CLI/GUI or a service/sofware/library which is going to use our service/software/library.

Lets take an example of developing a birthdaygreetingsAPI service. There are developers who are very confident of their coding skills - they claim they only write perfect code. So any code they write can go directly to production!

Unfortunately, I am not that genius programmer. I may have used the best of the design patterns and done most of the error checking, but still I am not confident to put that code in the production directly. I need to test. Testing will help to catch my bugs before it hits production.

During my initial years of programming, I used to just do manual testing of the written code. This was okay to start with but it is very inefficient. As I need to spend a lot of time on doing those manual tests and it will only uncover small amount of bugs. Lets take an example class User, which has 3 attributes - name, birthdate and email. Everytime, I touch that class directly or indirectly I need to test all the aspects of that class. With just an input validation, there are 3!(3*2*1=6) possibilities. Then there are business logic validations. When your testing it manually, everytime you change something you need to test all those validations. While executing half way you discover an error, then you go back fixing that error. Once done, start over those tests. Reproducibility is very difficult. How are you sure that you are performing exactly same number of steps in the same order for those manual steps everytime! I might succeed to reproduce those steps of the test that day during the development of the class, but say I have to come back to that class after few days, most of the time we will not remember those tests or the exact steps of those tests. So, codify those steps of the test. You write test cases which will validate your code. There are different types of tests and each are targeted to capture different types of bugs. In this I am going to talk about 2 types of tests - unit test and integration test.

Unit test will test your smallest unit of software. You can define what constitutes your unit. Ideally, it is the class or the function of your program. As explained by Martin Fowler there are different schools in the unit test itself. I am of the opinion, you should mock out external dependencies like DB or file or third party libraries or even other units. The reason being, in this unit test we are trying to capture those errors caused by our logic and not due to wrong integration or error in the dependencies of our program. As an added advantage, mocking those external dependencies will fasten your unit test executions. Each time, I update the code, I execute my unit tests to make sure the changes are not breaking existing things. My birthdaygreetingsAPI service is written using Spring Boot framework and I have used JUnit5 unit test framework (dependency declaration, actual class and its unit test cases).

Integration test will test if your individual units/modules work when connected to each other. It will also test if your code’s integration with external dependency works as expected. I do agree the line between unit test and integration test is blur. One of the major types of bugs is related to integration. We assume data/behaviour from our dependency is of particular format when we developed our module or unit. When our unit actually interacts with dependencies, those assumptions might turn out to be false leading to errors. So, ideally we will have a set of integration tests which will be executed right after your deployment to capture such integration issues. If the integration tests fail, your deployment is rolled back.

I used to write my logic first and then write the unit tests for it at later point, until I read the “The Clean Coder” book. In Test Driven Development, you write your tests first and then your functionality to satisfy that test. The mindset is, first define what is that expected from your system or unit (requirement). This expectation/behaviour is defined as unit test. Initially, this defined unit test will be failing because there is no corresponding functionality behind it. Then you start writing your functionality to make that test pass. Thus you are meeting what is expected of your system/unit (requirements). Once you pass the test, you go about refactoring the code to fine tune it. This will make sure your focus stays on the requirements. Usually, when I first write code without the unit test, there is a high chance I will deviate from the requirements or I try to do somethings extra than what is required. These things will be avoided if I followed Test Driven Development (TDD). TDD will also give that extra focus and purpose to the function you are writing. I know this is difficult to adopt but it is worth it. I have not yet completely changed to TDD, sometimes I still do write the code first (old habits die hard!) and then the test cases. Slowly I am updating my habit towards TDD (test case first).

The next important tool to understand how effective your testing is code/test coverage. Code coverage tells you how much portion of your code is tested. The more important to notice in code coverage is what portion of the code is not tested. I usually go over the results of the test coverage to find which portion of them are missing tests.These results also help to identify dead code present in my code repository which can be removed, thus helping to write a better maintainable code. One more tool which will help to write maintainable code is Code Complexity. With the code complexity analysis I can refactor and reduce the complexity of my code more efficiently. Reduced complexity leads to a better maintainable code. With better maintainable code, you are reducing the chances of introducing new bugs in future. In my birthdaygreetingsAPI service, I have integrated the CodeCov into my build tasks. The CodeCov reports provide me detailed code coverage as well as code complexity of my code. Each time I build birthdaygreetingsAPI service, it will generate the code coverage and complexity report.

Every code we write needs to be peer reviewed. When I write the code, its my perspective of the logic which best suits the requirement I am solving. When this code is peer reviewed, others will review it and they will be able to provide their insights. They might think of even better ways of solving a problem, better ways to refactor it or they might also find some more edge cases which I might not have handled yet. For my birthdaygreetingsAPI service, I am using the github provided pull request. Here are the code reviews - Github pull request, reviewable link of same pull request.

I had mentioned at the beginning of this post that ultimate testers are your customers. So make sure your changes reach your customers at the earliest, so that you get very early feedback. Based on that feedback do the necessary changes to your code, release it again to customers - rinse and repeat. Basically, every commit (change) you make to the code repository should trigger a build, execute tests on it and deploy it to production. This concept is called Continuous Deployment. For me, one of the great thing of Continuous Deployment is we can clearly know what is causing the bug. There is almost always a direct one to one relationship between new bugs and the changes we did as every commit is leading to a build. Second benefit of using the continuous deployment is simplified and completely automated build. Anytime an issue is encountered in the service/software/product, we can easily rollback to the previous good state without losing customer trust. As each day my change survives the production environment, its a great confidence builder that my code is doing what it should.

I am using Travis CI for continuous deployment. I have defined the TravisCI configuration in my birthdaygreetingservice repository - travis ci config. I do the code changes in a branch like issue_1 branch, and generate a pull request. After the review is approved, I will merge the changes to the master branch. Travis CI is linked to my repository and as soon as a commit happens to master branch, it will trigger a build. As part of this build, I have configured Travis to compile, run tests and generate codeCov reports using gradle. If they complete successfully, then Travis will build a docker image using jib and uploads it to docker hub. Now I can download this docker image on any machine having docker and start up my service (you can also try :-)). Once docker image is uploaded to docker hub, Travis will go ahead one more step and actually deploy this service into heroku. You can visit https://birthdaygreetingservice.herokuapp.com/ to see the deployment, and use that endpoint to access birthdaygreetingAPI service. Incase, I want to rollback, I have two options -

  • I can go to github and reverse the commit which will kick start a new build and deployment OR
  • I can go to heroku and initiate a rollback, as heroku provides a dashboard to monitor and rollback to previous versions.

You might ask, with all these things would my code have 0 bugs? Unfortunately, no but with this investment, I will be more comfortable in the code I am writing, my code is better maintained, my code is better prepared for the change, I will respond early to the issues and yes I will reduce the bugs reaching the production.

For Reference:

Credits: @AnuKadekar Thank you for reviewing :-)