What Unit Testing Really is: Guide to Good Design, Decouple and Protection

Overview

Unit Testing is a guide to good design, low coupling, and protection, I write more, I like it more: post to clarify my previous misunderstanding and confusion.

What Unit Testing Really is: Guide to Good Design, Decouple and Protection

1) Unit testing isn’t testing at all. Unit testing, especially test driven development (TDD), is a design or implementation activity, not a testing activity. My personal feeling is Unit Testing brings us three major benefits:

  1. Guides our design to be loosely coupled. If doing test driven development, it limits the code we write to only what is needed and helps us to evolve that code in small steps.
  2. This actually related to the first benefit, that is, if things are not loosely coupled or say some method does a lot of things together, it would be extremely painful and very difficult to do unit testing, in such cases, you will have to decouple things.
  3. Unit Testing Code is written by you and follows your code logic (white box), this is extremely useful to protect your part of code from being broken by any accident changes later on, because accident changes do not make sense, and it would easily break the unit testing code, this is good for early detection of problems.

2) Unit Testing also gives us living documentation about how small pieces of the system work. Indeed, when trying to understand a big system, running unit testing code and follow the logic step by step would be easier for you to figure out how things are going on internally.

3) Unit testing forces you to actually use the class you are creating and punishes you if the class is too big and contains more than one responsibility. This is because if the class is too big, it would be extremely painful to write unit test for it, especially there are a lot of complex dependencies on other objects. This is why dependency injection pattern is very useful and frameworks like Guice plays a big role now. I will elaborate this point more soon in the following based on my true painful experience: By that pain, you change your design to be more cohesive and loosely coupled.

4) When we are done, we end up with some automated tests that do not ensure the system works correctly, but do ensure the functionality does not change. So do note that unit testing is not an actual testing work but about implementation, and never mistreat it as something else of true testing like functional testing.

5) In reality, the majority of the value is in the act of creating the unit tests when creating the code. This is one of the main reasons why it makes no sense to go back and write unit tests after the code has been written. And plus

Here are some bad no-nos that indicate a don’t understand of unit testing:

  • You are writing the unit tests after the code is written and not during or before.
  • You are having someone else write unit tests for your code.
  • You are writing integration or system tests and calling them unit tests just because they directly call methods in the code.
  • You are having QA write unit tests because they are tests after all.

6) Unit Testing is good for protecting the old code from being broken when other people try to merge new features into say your own branch. So Unit Testing is necessary!

7) One useful strategy to do good Unit Testing is: figure out all the collaborators. Say we want to test Class C and C use objects of Class A and B and other couple of objects. We would want to avoid instantiate  these collaborator objects within C at all. What we need in unit testing about these collaborator objects is to assume these objects are working correctly, and only testing the logic of the current class C by mocking those objects. Same thing should be done within those objects too, this is unit testing and we should not mixture testing A B C together. I will elaborate more about new those objects (e.g., new A(), new B(), etc) within C could be a big pain for unit testing, which is why we need DI framework like Guice which makes unit testing life easier because we can simply mock objects by injecting them either in the client code or the testing code.

8) I now give an example about what Unit Testing is really want to test:

public Class C {
    public boolean foo() {
        ... // (I)  several if statements
        A a = new A();
        ... // (II) might be while or for loop
        B b = new B();
    }
}

Like the above example, what Unit Test wants to verify is that part (I) and part (II) are working as expected assuming a and b are working correctly. This does not necessary indicates that foo() will be working correctly in say production environment because it actually depends on A and B.

What we typically want to do is to mock object a and b rather than instantiate them within foo, further more we want to mock the behavior of a and b by something like ‘when(a.bar()).thenReturn(“success”)’. Inorder to mock a and b and control their behavior, there has to be some way to inject these objects instead of instantiating them within any places of Class C. This is why Dependency Injection like Guice comes into place.

In other words, whether a or b behaves correctly or not is none of the business of the Unit Testing for class C! If you want to verify them as a whole (Both class A B C are working correctly within method foo()), then you are actually doing functional testing!

To elaborate a bit more, since we should not test a and b within C, then how are we gonna achieve this? If we new A(), new B() like in the sample code above, it is not possible! Because A and B might dependent on other objects, then you have to create objects for those as well, it would be disaster and a big big pain when what you want is only test the method foo(), trust me, we never want to do that, so we want to mock a and b, to mock and isolate the business logic influence from A and B,** we have to have the access to object a and b externally outside C and directly in the client code like main() method or the @Test function body in Unit Testing,** so we can use some framework like Mockito to control the behavior of a and b. Now we are truly testing the business logic of foo() in Class C!

9) To recap, we want to control internal object’s behavior to do unit testing, so we have to get the reference of those objects outside class C, this is why we cannot new A() or B() within C but only inject A and B into C using DI framework say Guice.

10) I also mentioned Unit Testing is about protection. In another sense, unit testing is white box test, so we need to cover every branch or work flow of the business logic (if else statement, while loop and so on) to protect the logic, so if any of the other changes accidentally break the code flow, it can report, unit test is really a guide and a protection on all of your business logic, rather than a true test. The more Unit Testing covers on your code logic, the more protection it promises!

To recap all: Correct understanding of Unit Test from my opinion and current understanding is that assume we want to test Class C which has several collaborator objects of class A, B… which could be very complicated classes, and there is a public method in C called foo() using A, B… the unit test for this public method foo() will be based on the correctness of A, B…, and what we need essentially to test is the logic for organizing A, B… including say the if else statement and while loops and so on, to test these are correct, assuming A, B… will be working correctly, do not mixture everything together, the correctness of A, B… are guaranteed by their own unit testing, in this way, such unit tests could protect the logic of this public method from broken by other changes and make sure C is working correctly as long as A and B are correctly.

Reference: Unit testing The Purpose of Unit Testing

Summary

Unit Testing is a guide to good design, low coupling, and protection, I write more, I like it more: this is a post to clarify my previous misunderstanding and confusion.

Written on April 9, 2015