Unit testing is a way of testing a small unit of functionality. A unit is the smallest testable unit of program usually referred by a function, procedure etc which is a part of a class. Ideally, a unit test is independent of any other unit tests.
The primary goal of a unit test is to isolate a smallest unit of a program and prove that it works correctly. A unit test can be referred to a requirement that a function should satisfy. This is the primary reason it has several benefits; one of the key benefit to find bugs early on in the development.
Unit testing allows a developer to refactor code at a later stage in the application development phase or in maintenance phase. It provides an umbrella ensuring that all the modules work correctly and as expected after that change. It is a great way identifying regression errors. The correct process if to write a unit test case for all methods and functions; so that if a change has introduced a defect it can be identified and quickly.
A good unit test suite ensures that all the paths in the code are covered including if-conditions and loops.
Unit testing helps to eliminate defects and ambiguity in the functions themselves. This leads to a much simple integration approach when we want to test all the functions together. If we have all the methods do what they are expected to do, doing an integration testing automatically becomes easy.
In last few years, how we document a code is changing. In past years, the community has been moving away from writing word documents and using Java Docs and inline comments. Unit testing can also provide a document to the system. Developers looking to ramp up on an application can use the unit testing to have a basic understanding of the application.
Unit test for a method encompass all testing scenarios. While positive test case provides information on how to use the method, a negative test case provided scenarios how not to use the method and what to expect.
While a normal textual document has a high likelihood of drifting away from the actual implementation, unit tests will remain aligned to the implementation. However, developers should not reply solely on unit tests for documentation.
If you use Test Driven Development to develop software, Unit test can be used to provide a formal design. Each unit test can be looked at design specification providing information on interfaces, classes, methods, return types, error conditions. Let us look at the code sample below:
A test case that specifies there has to be a static class called MathUtil with a method called divide. This method takes two parameters of type int and returns a parameter of type double. In addition, you can expect an exception from the method.
public class TestMathUtil
Public void testDivide()
Double result = MathUtil.divide(10, 2);
Double resultError = MathUtil.divide(10, 0);
assertFail(“We expect an exception);
assertTrue(ex.getMessage(), “Divide by zero”);
You can clearly see that how looking at this test case a developer (consumer) of the MathUtil class understands the requirements. One significant advantage of using Unit testing as design element over UML based design is ensuring that the implementation adheres to the design. A developer reading a UML diagram can potentially name the class MathUtilities, which will instantly make the design disharmonious with implementation.
However, now we have code generation tools available for all major languages that eliminate such inconsistencies.
Limitations of Unit Testing
We can expect to catch all errors during testing – it is impossible to evaluate all possible paths for all but trivial scenarios. This is as applicable to unit testing as it is applicable to other forms of testing.
Unit testing by definition, tests only units of functionality and does not guarantee catching integration errors; it only facilitates integration. Unit testing may not catch integration errors across multiple units.
Software testing is combinational problem. For every decision case, we need at-least two test cases. If you have complex conditional logics, the complexity of unit test cases will increase exponentially. As a result, there will be times when the code written for test cases will be mush more than the code itself.
To achieve the most from unit testing activity, a team needs rigorous sense of discipline throughout the software development lifecycle. It is most essential to keep a record of failing test cases. A very close eye has to be kept on when test cases fail. There should be a process in place ensuring review of test case failures every day and addressing them actively.
Use of a Continuous Integration tool is a most common practice in the software development lifecycle to through out test cases results post a build cycle.
A Team should also consider using a version control for the development process so that they can look at various baseline versions for changes in code to identify regression scenarios.
Writing unit test is an art
It is very easy to get overwhelmed when starting to write unit test cases. The best way is to create unit test cases for new code. Although, it is possible to create unit test cases for existing code but it is not worth the effort. Start with the new code added to the application, get familiar with the process and then revisit the decision to write test cases for existing code.
Mastering the technique for unit test cases if 95% mental and 5% technical. You have to be patient with the Java Compiler. When creating a new test cases you should assume that the class or the method exist. Stick to the various syntax errors that are displayed. When you write your class things will come to order.
Getting developers to think like testers will be your greatest challenge. Many projects that I have seen fail unit testing, fail for this reason.
Walking the fine line
Very often it is not clear when a test cases is actually a functional test case. TO be honest, it is not clear to me if I know the line myself. However, I try to stick to the following guidelines. A unit test case might be a functional test case:
- If a unit test cases crosses class boundaries;
- If a unit test case is becoming complicated
- If a unit test case becomes fragile
- If a unit test case is harder to write than the code itself
- If a unit test case has lots of asserts
Remember, there are no rules. If you find another approach that works for you, best feel free to use it. Be careful to document it so that the entire team can use it consistently.