Basics of Unit Testing
Introduction
Software testing is a process to ensure whether a software application meets the specified requirements or not and it also identifies the defects to ensure that product is defect/fault free to produce the quality product. A quality product brings the more satisfaction and trusts from the customers.
A study says, fixing a defect in each phase of SDLC is relatively higher than the previous phase and it also says the defect fixing cost in initial phases is very less than the final phase. The following chart showing the relative cost to fix a defect in a traditional software development and it clearly explains that we can save costs by identifying most of the bugs in the earlier phases of SDLC.
Relative cost to fix a defect in a traditional SDLC:
The following image showing the phase-wise software tests in a SDLC.
Software testing starts with unit testing at the development phase and unit tests are designed by a developer. It is one of the most important tests so developers should understand the basics of unit tests before start writing them. In this article, we are going to cover some of the basic of unit testing and these concepts apply to all programming languages. I have used some C# examples in this article so please forgive me if you are not a C# programmer but still you can relate all these concepts with your own language.
What is the Unit Test?
A unit test is white box test and it checks the behavior of a section of code and verifies the correctness. The section of code may be a method/function or module/class. It is an automated test, which designed by a developer and it can run without manual intervention. All unit tests should produce a boolean result, it should be either pass or fail. The unit test also called as the component test.
Properties of Unit Test
Unit test properties defined as an acronym “FIRST” and each letter explained as below,
-
F-Fast
A unit test should run quickly as much as possible. It means we should not use external dependencies like network call, DB calls, etc.
-
I-Independent
Each unit test should run independently without depending on other unit tests. It should test only one unit at any time.
-
R-Repeatable
A unit test should run on any environments like development, QA, etc. It means unit should not have any environment specific elements in it.
-
S-Self-validating
Each unit test should report unambiguous result and it should be either Pass or fail.
-
Timely
A unit test should be written on or before a component or module development.
Sometimes people use the database, file system or web calls in their unit tests but these tests are not called as unit tests because these tests are violating the first three properties from FIRST.
Let see some benefits and limitation of Unit Test.
Benefits
- It creates a better understanding of what to build among the team members since it starts validating the system in the earlier stage of development. It also creates a collaboration between PO/BA, Developer, QA and Web designers to understand the requirements.
- It enforces to design the system as testable and loosely coupled units.
- Unit tests are executable specifications for a system. It documents the behavior of the code and how the unit intended to be used.
- It saves lots of time on integration and regression tests since it points out which unit failed and why in advance.
- It enables the continuous integration and deployment
Limitation
- It can’t find all errors since it focuses on one aspect of a unit.
- It will not find integration related errors in advance since it focusing on a unit
- Adding additional effort in development to write and manage it.
Vocabulary
The following list shows some of the vocabularies used in the unit test.
-
Test case
Contains a set of instructions or checklist to test a piece of feature or unit
-
Test Assertion
A test condition which confirms the requirement by comparing the expected and actual value
-
Test Scenario
Logically groups one or more test cases to achieve specific aspects of a feature or unit
-
Test Suite
Collection of test cases. It is not necessary for all test cases to belong to one feature or unit.
-
System Under Test(SUT)/Class or Code under test(CUT)
It represents the module/class/method/function which undergone for testing.
-
Test Fixture
It facilitates to test a SUT by providing the fixed state of the SUT and the required resources. Basically, it will do things like initializing the required objects, creating fake/mock objects and their inputs. It has two methods and those are SetUp and Teardown. SetUp method initializes the required resources for tests and Teardown methods cleans up all initialized resources.
-
Test Framework
Test framework facilitates to write unit tests, provides a test runner to run all unit tests and report viewer to see the test results and current status of execution.
-
Test Runner
It executes all tests and produces the test results.
-
Code Coverage
It is a code metrics which quantify how much percentage of a unit or SUT code covered by written unit tests.
Unit Test Case Design
While writing a unit test we need to ensure the following five elements,
-
Name
Readability always important since a unit test is an executable specification and name should be self-descriptive and precise.
-
Arranage
Split your test code into three sections and use the first section to arrange the required resources including SUT.
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
//Arrange
var expected = 5;
var calculator = new Calculator();
//Act
var result = calculator.Add(2, 3);
//Assert
Assert.AreEqual(expected, result);
}
- Act
Use this section to arrange code that executes the unit needs to be tested.
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
//Arrange
var expected = 5;
var calculator = new Calculator();
//Act
var result = calculator.Add(2, 3);
//Assert
Assert.AreEqual(expected, result);
}
- Assert
Use this section to arrange the assert statement which confirms the requirement by comparing expected and actual result from the system under test and collaborators.
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
//Arrange
var expected = 5;
var calculator = new Calculator();
//Act
var result = calculator.Add(2, 3);
//Assert
Assert.AreEqual(expected, result);
}
- Cleanup
It is an optional section. Use this section to arrange code that cleanups all initialized resources in the arrange section.
Best Practices
Let see some of the unit test best practices and tips in this section.
- Create more readable test cases and scenarios
[TestClass]
public class CalculatorTests
{
[TestClass]
public class When_adding_two_numbers // Test Scenario
{
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
var expected = 5;
var calculator = new Calculator();
var result = calculator.Add(2, 3);
Assert.AreEqual(expected, result);
}
[TestMethod]
public void It_should_return_negative_5_when_add_negative_2_and_negative_3()
{
var expected = -5;
var calculator = new Calculator();
var result = calculator.Add(-2, -3);
Assert.AreEqual(expected, result);
}
}
}
- Define unit test naming convention specific to your team.
- One concept per test case
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
var expected = 5;
var calculator = new Calculator();
var result = calculator.Add(2, 3);//it only focusing on adding two positive numbers
Assert.AreEqual(expected, result);
}
- One assert statement per test case
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
var expected = 5;
var calculator = new Calculator();
var result = calculator.Add(2, 3);
Assert.AreEqual(expected, result);
}
- Write your unit tests right after your development of a unit when you are not following the TDD
- Use AAA (Arrange, act and assert) pattern to arrange your test code in a test
[TestMethod]
public void It_should_return_5_when_add_2_and_3()
{
//Arrange
var expected = 5;
var calculator = new Calculator();
//Act
var result = calculator.Add(2, 3);
//Assert
Assert.AreEqual(expected, result);
}
- Use the TDD methodology for your development or write all required unit tests during the development phase itself, not after QA phase.
- Keep your tests cleaner. Treat your test code as important as production code.
- Prepare team specific unit test checklist to follow during the design and peer review.
- Do the peer review for the unit test cases.
- QA/BA review for test case design
- Developer review for test code
- Define a code coverage threshold specific to project or product. It doesn’t mean 100% code coverage is 100% effective testing. Sometime, it will end up with writing more unwanted or repeated test cases to bring 100% coverage but it is waste of time and it adds overhead to unit test maintenance.
- Configure all written unit tests in the build pipeline to enable the automated build. It is also recommended for the manual build.
- Categorize your unit tests the way you can configure and execute in different builds like nightly, staging and production build.
- Choose all testing frameworks in one programming language for the unit to end to end(e2e) tests otherwise it will add more overhead to maintain the code and it required the different skill set people in a team.
Thanks for your patience to go through this article and I have the plan to write more articles to cover topics like introduction to MS Test framework, xUnit, Unit Test methodologies, Test doubles and introduction to Moq and other frameworks which help us to write more effective and maintainable tests. Please share your valuable feedback to improve my article content.
One Reply to “Basics of Unit Testing”
Nice article.