April 5, 2017
How to Use TDD with C#: Introduction
Software testing is the art of measuring and maintaining software quality to ensure that the user expectations and requirements, business values, non-functional requirements (such as security, reliability and recoverability) and operational policies are all met. Testing is a team effort to accomplish the well-understood and agreed-upon minimum quality bar and definition of “done”.
Definition of Done (DOD): is a team definition and commitment to quality to be applied to the solution during each iteration (this may also occur at the Task or User Story level, too). Consider design, code reviews, refactoring and testing when discussing and defining your definition of done.
Understanding and Adapting TDD:
TDD is a happening term in the industry these days, especially among those software development organizations that are practicing the Agile development methodology.
TDD is an evolutionary approach and mindset towards software development that enforces writing Unit Tests as you are coding the functionality or feature.
TDD gives an opportunity to think as a “user of the code” instead of an “implementer of the code”. Eventually, TDD helps in better design, quality and test coverage.
What is Unit Test?
An ideal unit test is a piece of code written by a developer that exercises a small but specific area of code functionality to ensure that it works as expected and can be tested in isolation from other units.
Why Unit Test?
According to the Test Triangle, in a software project the largest number of tests must be Unit Tests. Because multiple individual Unit Tests contribute to Integration Testing and so on.
The image below also depicts that, over the period of time of a project, a Unit Test needs to be continuously written and run for software quality.
Benefits of TDD
Unit testing helps in various ways as in the following:
- Reduce bugs by identifying all the use case scenarios to reflect intent (end user’s mindset, business needs, expected functionality and business validations and so on).
- Less-and-less time on debugging.
- Avoid collateral damage, in other words a fix in one area may break functionality in another possibly related/unrelated area.
- Helps you achieve YAGNI that is “You Aren’t Gonna Need It”. In other words saves you from writing code functionality that you don’t need.
But I am a Developer, Not a Tester
First, creating a Unit Test is the developer’s responsibility. Agreed, developers are not testers and that is true of testers too. But thinking of the most common scenarios worth testing that might cause failure to your code functionality is all that you need to code in your Unit Tests.
Got it, but I don’t have a Testing Mind Set
Not having a Testing Mind Set is actually a genuine issue and this happens because developers usually never think of testing their code until it’s deployed to QA or production. To overcome this issue you must pair with a QA Engineer or Software Development Engineer in Test (SDET) in your team. You must write all the possible test areas that you think of and get it reviewed.
Career Tip: In today’s software world, a unit testing skill is a major requirement for any developer, lead or architect position.
Requirements for other types of testing as well
Do I need to do other types of testing as well? From a developer’s point of view, the short and straight answer is no, but many teams and organizations require their developers to write even Integration, Load and Stress Tests as well. A dedicated QA/Test team is usually responsible for doing non-unit type of testing, whether it is manual or automated.
Career Tip: Educate yourself about other types of testing.
System Under Test (SUT)
System Under Test (SUT) is the system that will be tested by Unit Tests for code accuracy and possible scenarios that might either break the functionality at runtime or not produce legitimate results.
Assume you are working on a Bank Application’s Business Logic (BankApplication.dll) and your code looks as in this:
This Bank Application’s DLL should work properly, assuming all the right data and parameters are provided. But when you start thinking from a Test Driven Development (TDD) perspective and you put the DLL under test then you start detecting the areas of further improvement and refactoring.
What to Unit Test
But what do i unit test? the most critical and common area of Unit Testing can be categorized under Boundary and Error Conditions.
Boundary conditions are very critical to the success of any software code ever written. All your business validation/rules are actually a candidate for Boundary Unit Tests.
Identifying boundary conditions is very important for unit testing, such as:
- Empty or missing values (such as 0, “”, or null).
- Inappropriate values that are not realistic from a business point of view, such as a person’s age of -1 or 200 years or so.
- DOB is tomorrow’s date or a time in the future.
- Duplicates in lists that shouldn’t have duplicates.
- The password is the same as either First name or Last name
- Special characters or case related conditions.
- Formatting of data, for example name must be capitalized. For example Vidya Vrat, Agarwal.
- Type of acceptable values in a field. For example name can’t hold a numeric and age can’t hold letters.
- Range is another critical thing to test and it’s often coded as business validation rules.
Building a real-world application causes real-world errors at run-time and errors do happen. Hence, you should be able to test that your code handles all such errors, for example think of the following scenarios:
- Can’t handle DivideByZeroException
- Consider scenario of AccessDenied
- Don’t ignore NullReferenceExceptions
- Check for existence; FileNotFoundException, DirectoryNotFoundException and so on
Properties of a Good Unit Test
Units Tests are very simple and usually small C# code segments, but there are a few criteria that can really define what a good Unit Test is. Here are the properties that a good Unit Test must have:
Automatic: Each Test must “Automatically” exercise small functionality in terms of invoking the test and verifying the results.
Thorough: Unit Tests are supposed to test all the possible areas of functionality that are subject to failure due to incorrect input.
Repeatable: Unit Tests must be repeatable for every build and must produce the same results. The development best practice suggests that if you are working on code that is impacting a Unit Test then you must fix the affected Unit Test as well and ensure that it passes.
Independent: Unit Tests must be independent of another test. In other words, no collateral damage. Hence, a Unit Test must focus only on a small aspect of big functionality. When this Unit Test fails, it should be easy to discover where the issue is in the code.
Professional: Even though at times Unit Tests may appear to be very simple and small, you must write Unit Tests with coding practices as good as you use for your main development coding. You may want to follow Refactoring, Code Analysis and Code Review practices and so on as for your Test Projects as well.
Structure of a Unit Test – Arrange, Act and Assert
An ideal unit test code is divided into the following three main sections:
- Arrange: Set up all conditions needed for testing (create any required objects, allocate any needed resources and so on).
- Invoke the method to be tested with possible parameters or values.
- Assert: Verify that the tested method returns the output as expected.