Introduction
Until now, I had been developing applications without unit testing, but my new team has asked me to start doing it.(Yes, what I had been doing was abnormal, and apparently it is normal to implement unit testing.)
Having studied the fundamentals of the test runner (NUnit3) on Udemy, I am now looking into the theoretical aspects of unit testing, as I am unsure what kind of test cases to create or when to use mocks.
I read chapter 4 of Unit Testing Principles, Practices, and Patterns: Effective testing styles, patterns, and reliable automation for unit testing, mocking, and integration testing with examples in C# First Edition by Vladimir Khorikov (Author) (after Khorikov 2020) , quoted what I thought were important points, and wrote my interpretations and impressions.
Khorikov 2020 refers to the following things as four pillars of a good unit test.
- Protection against regressions
- Resistance to refactoring
- Fast feedback
- Maintainability
I will explain these and share my thoughts.
Four pillars of a good unit test
Protection against regressions
“A regression is a software bug. It’s when a feature stops working as intended after some code modification, usually after developer roll out new functionality.
Khorikov, 2020
The larger the code base and the more complex the specifications, the more likely regressions are to occur. Furthermore, since I had not implemented unit testing at that time, I had to fix the code after discovering the regression during system testing, which was very time-consuming. Therefore, I believe that unit testing greatly contributes to preventing regression in complex specifications.
Registance to refactoring
The larger the code base and the more complex the specifications, the more likely regressions are to occur. Furthermore, since I had not implemented unit testing at that time, I had to fix the code after discovering the regression during system testing, which was very time-consuming. Therefore, I believe that unit testing greatly contributes to preventing regression in complex specifications.
Khorikov, 2020
Unit testing helps refactoring by providing an early warning when programmer break existing functionality and provide programmers with confidence that programmer changes won’t lead to regressions (Khorikov 2020). But, refactoring sometimes leads to false positive which alert to the code works incorrectly by mistake while it works correctly(Khorikov 2020).
False positives weaken unit test, Khorikov 2020 cited an example where frequent specification changes lead to false positive then programmer underestimated the results of unit testing, leading to serious bugs being missed.
I can’t agree more with his opinion. I assume this is similar to “Protection against regresssions”. For example, I wished unit testing had been implemented when I add new functionality to existing app and refactor some codes.
The third and fourth pillars : Fast feedback and maintainability
Fast feedback is important. Including myself, programmers prefer to finish quickly, not only for testing, but also for building, updating, and so on.
Finally, maintainability, Khorikov 2020 said maintainability depends on 2 components, “How hard it is to understand the tests” and “How hard it is to run the tests”.
I believe understanding test runner (e.g., NUnit, xunit) contributes to former. About NUnit, I learned it, which provides many useful functionality. For example, Testcase Attribute pass values to argument.
/// <summary>
/// Calculates the entrance fee based on age.
/// System Under Test
/// </summary>
/// <param name="age"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public int CalculateEntranceFee(int age)
{
if((age < 0))
{
throw new ArgumentException("Age cannot be negative");
}
else if(age >= 0 && age < 12)
{
// Child ticket
return 0;
}
else if(age >= 12 && age <= 18)
{
// Under 18 ticket
return 600;
}
else
{
return 1000;
}
}
[TestCase(0, 0)]
[TestCase(11, 0)]
[TestCase(12, 600)]
[TestCase(18, 600)]
[TestCase(19, 1000)]
public void CalculateEntranceFee_ValidAge_ReturnInt(int age, int expectedResult)
{
// Arrange
var _sut = new SampleService();
// Act
var result = _sut.CalculateEntranceFee(age);
// Assert
Assert.That(result, Is.EqualTo(expectedResult));
}
Testcase Attribute leave out writing almost identical code with only the argument values different. There are many functionality to help programmers in each test runner, programmers should make use of it and writing clean test code.
The latter (“How hard it is to run the tests”) means the time or effort when the test works with out-of-process dependencies like third-party library and Web API, programmers have to spend time keeping those dependencies operational (Khorikov 2020).
I assume programmer could leave out the effort and time. For example, writing code inserting the ideal test data to database and deleting unnecessary record. Although it is so helpful for not only a programmer but also programmers who will maintain this code in the future, It requires developers effort and time, so programmers develop it considering the circumstances.
Summary
I learned the four pillars of good unit test Khorikov 2020 proposed.
- Protection against regressions
- Resistance to refactoring
- Fast feedback
- Maintainability
I feel they follows basics of unit test such as “validating observable behavior not implementation detail”, “Maximize effectiveness with minimal code” and “sustain the project.”
My next step is tto apply thease principles in real projects and refine my understanding through practice.
コメント