Working Effectively with Unit Tests

A programmer’s life can be defined in two phases.

  1. Before writing tests.
  2. After writing tests.

I spent starting four years of my career in the first phase. After reading a lot of benefits about testing. I started to write tests.

To be honest, I almost gave up after a few months. But I keep practicing it again and again until it’s become a habit for me.

Once you move to the second phase, then there is no going back. In phase two, it becomes hard to work on code that does not have tests, which was way easier in phase one.

Now I am more comfortable with writing tests. However, nothing is perfect and I always had a feeling that there should be a better way to write tests.

So I pick up this book whose title was inspired by another favorite book of mine called “Working effectively with legacy code”.

After reading the book I see tests differently now. Earlier I used to write tests for just testing the different use cases for my code. Now I think about how can I write tests which is effective, add more value with high ROI and help me understand failures better.

The book starts with one example and keeps improving tests with different techniques. So I am gonna share a few of them which helped me a lot.

1. DAMP for test

We usually follow DRY (Don’t repeat yourself) while writing code. However, this approach does not work well while writing test code. Follow DAMP (Descriptive And Maintainable Procedures) instead of DRY in tests.

For example, instead of asserting each item using a loop, have a separate test case for each case. Instead of having one model class shared across all tests, have a new model for each test.

2. One assert per test

Initially, it’s hard. But once you get into the habit of writing tests, it can be achieved. Having one assert per test helps us to focus on one thing at a time and if something fails it helps us to identify the failure quicker.

For example, if one test has four assertions and if the second one fails, then the test won’t run the remaining assertions even though they are passing. Dividing these four assertions into each test will lead to a better understanding of failing tests.

3. Solitary and Sociable Tests.

A solitary test is a test where you are allowed to have only one concrete class which you are testing. All the collaborators are mocked or faked.

A sociable test is where we don’t mock collaborators and try to test with multiple concrete classes.

For more detail here.

4. Using DataBuilder

In unit testing, we focus more on business logic than anything related to the framework. In those test cases, we need some model/POJO classes to test different scenarios with different states.

One of the problems I face while doing this was, I was using the same model/POJO class across multiple tests by changing just 1-2 properties for each test case.

DataBuilder solves this problem by having default model values and adding flexibility to change properties as per need using Builder Pattern.

More here.

5. Test ROI

80-20 principal state that 80% of the value comes from 20% effort. The same goes for testing ROI.

All your edge cases do not need to be tested. Focus on writing test cases first, which impact the business the most.

For example, testing getting and setter does not have any ROI. Better to test a case where this getter setter serves a business purpose.

Also, 100% code coverage does not mean your tests are effective. Even 40-50% coverage which covers most of the business use case is more effective.

This is a short book and If you are a programmer then I highly recommend reading it. Especially those who are at the intermediate level.

You can get this book from amazon.

Author: Jay Fields

If you like this kind of content, you can subscribe to my blog and get notified first. You can also follow me on Twitter or Email me.

Highlighted Quotes

  1. When writing these tests it’s obvious and clear where “duplication” lies and how “common” pieces can be pulled into helper methods. Unfortunately, each time we extract a method we risk complicating our tiny universes.
  2. The decision to write DRY or DAMP tests often comes down to whether or not you want to force future maintainers to deeply understand code written strictly for testing purposes.
  3. You won’t write better software by blindly following advice. This is especially true given that much of the advice around testing is inconsistent or outright conflicting.
  4. Only through experience can a developer know how to judge whether the current moment would benefit or suffer from switching to a TDD cycle.
  5. My largest complaint with this test is that I now know of one failure, but have no (automatically generated) information about the remaining tests.
  6. I believe code should not only express how it works, but also why it’s been written in a particular way.
  7. This is always the largest problem with negative testing: they guarantee that something didn’t happen, but they make no guarantee that the system is integrating as desired.
  8. Clever tests are fun to write and enjoyable from a language geek perspective; however, they have no place in a test suite maintained by a team looking to deliver effectively.
  9. Duplicate code is a smell. Setup and Teardown are deodorant.
  10. If a private method is important enough to test, you likely have a tested public method that relies on that private method.
  11. Remember: If you find yourself repeating any idea in multiple test cases, (then and only then) look for a higher level concept that can be extracted and reused.
  12. The key is to test the areas that you are most worried about going wrong. That way you get the most benefit for your testing effort

Site Footer