Unit testing

Unit testing has been around for quite some time now. It works on the principle of validating an assumption in code. So, even if the code changes after some time, the assumption should remain valid. As it used to test a particular unit of code with a particular assumption, it is referred to as a unit test case. Let's suppose that you are writing a Java class and there are 10 methods in that class, each of them serving 10 different purposes. Then, we can say that you have 10 units of code to be tested. To go with the example, one of the methods is taking ID as input and returning back a Boolean about that ID: is it enabled or disabled in the system. Following is the sample code for a method which checks whether the given ID or, say, user ID is enabled or disabled. :

public boolean isEnable(String Id){ 
   return someRepo.findOne(Id).getStatus().equals("ACTIVE"); 
} 

The expected output from this method is either true or false. What happens if the ID does not exist in the database? Should it raise an exception or give a value? Again, the developer has to look back at the assumption. If the assumption says that it should return false, then the developer has to make a change to the code. This is how unit test cases evolve.

Let's look at a very common example of a unit test case, a calculator. A calculator provides arithmetic operations on two integers:

public class Calculator { 
   public static int addition(int number1, int number2) { 
      return number1 + number2; 
   } 
  
   public static int subtraction(int number1, int number2) { 
      return number1 - number2; 
   } 
  
   public static int multiply(int number1, int number2) { 
      return number1 * number2; 
   } 
   public static int divideInteger(int number1, int number2) { 
      if (number2 == 0) { 
         throw new IllegalArgumentException("Cannot divide by 0!"); 
      } 
      return number1 / number2; 
   } 
} 

Here are some sample test cases for the preceding code:

public class ExampleCalcTest { 
   @Test 
   public void shouldPass_forAdditionOfNumber() { 
      assertEquals("error..",  8, Calculator.addition(1, 7)); 
      assertEquals("error..", 23, Calculator.addition(17, 6)); 
      assertEquals("error..",  17, Calculator.addition(8,9,)); 
   } 
  
   @Test 
   public void shouldFail_forAdditionOfNumber() { 
      // assertNotEquals(String message, long expected, long actual) 
      assertNotEquals("error..", 0, Calculator.addition(13, 2)); 
   } 
  
   @Test 
   public void shouldPass_forSubstractionOfNumber() { 
      assertEquals("error..",  5, Calculator.subtraction(12, 7)); 
      assertEquals("error..", 3, Calculator.subtraction(4, 1)); 
        } 
  
   @Test 
   public void shouldFail_forSubstractionOfNumber() { 
      assertNotEquals("error..", 0, Calculator.subtraction(2, 1)); 
   } 
} 

The TDD approach supports unit test cases. These test the cases only to validate the logic inside a particular unit of code in the microservice. Depending upon the classes and methods inside the microservice, the number of these test cases can be high or low. Tests are automated test cases. If we take an example of a Java + Maven project, then it will run at build time. The Jenkins build tool will fetch the latest code from source version control (for example, Git). In the next step, on the latest pulled code, unit test cases are run, after the successful completion of those test cases, builds are made and uploaded to the artifact repository and a new tag is committed in the Git code. If the unit test cases fail, then the build is marked as failed and its feedback is given to us. The following image depicts the preceding steps:

Unit test cases cover a small part of code. Depending on how much TDD has been focused on, the number of test cases increases. They target a very small part of code and its assumption, so if any problems occur in the unit test cases running for an application, then it is easy to identify the exact cause of the problem because whichever test case failed is responsible for testing a small piece of code; identifying issue in a small piece of code is faster.

The required dependencies in the particular code which has to be tested should be mocked in unit test cases of that code. There should be the appropriate output expected from these mocked dependencies with respect to input. There could be a rule in mocked dependencies such as should respond X output for Y input, which makes test case writers focus on small parts of code for which the test case is being written. Until now, we have tested small chunks of code in isolation, assuming that all the other units of code are working properly. Going forward, we need to test how these different code units work together. For this, we need to perform integration testing, which we will cover in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset