Unit Testing – Explained
A process which involves writing code to verify a system at a lower and more granular level is known as Unit Testing. Unit Testing gives us the ability to verify that our functions/methods work as expected. It is used by programmers for programmers.
Unit tests are written to ensure that code performs as per the programmer’s expectation. They are generally focused at a lower level than other testing, making sure that the underlying feature work as expected. By example – an acceptance test might walk a user through an entire purchase, where as a unit test might verify that a ShoppingCart class correctly defends against adding an item with a negative quantity.
Unit testing is an example of white box testing, where knowledge of internal structures is used to identify the best ways to test the system. This is a complementary approach to black box testing, where the focus is not on implementation details but on overall functionality compared to specifications.
Why we need Unit Testing?
A common reaction to unit testing is resisting the approach since these tests seemingly make more work for a developer. However, unit testing offers many benefits that may not be obvious at first. The process of writing tests uncovers design or implementation problems. The unit tests virtually acts as the first users of the system and will frequently identify design issues or functionality that is lacking. Once a unit test is written, it serves as a form of documentation for the use of the target system.
Unit tests are an essential element of regression testing. Regression testing involves retesting a piece of software after new features have been added to make sure that errors or bugs are not introduced. Regression testing also provides an essential quality check when we introduce bug fixes in our product.
One of the most important benefits is that a well-written test model provides the original developer with the freedom to pass the system off to other developers for maintenance and further enhancement. If they come across a bug in the original functionality, there is a strong possibility that those unit tests will detect that failure and help diagnose the issue. Meanwhile, the original developer can focus on current tasks.
Unit testing does require more explicit coding, but this cost will be recovered, when we spend much less time debugging our application. Once unit tests are added, it’ll become a permanent part of the project, run each time a change is made to help ensure that the system still functions as expected. Tests are stored in source control very near to the code they verify and are maintained along with the code under test, making it easier to keep them synchronized.
Best Practices
Since unit tests are themselves code, we are allowed to proceed with multiple approaches while writing them. Here are some recommended guidelines which we could follow:
- Always separate unit test assemblies from the code we are testing. This allows us to deploy the application code without unit tests, which has no use in a production environment.
- Avoid altering the code we are testing solely to allow easier unit testing.
- Each test should verify a small slice of functionality. Writing long sequential unit tests that verify a large number of items are not recommended. This will result in easier to maintenance.
- All tests should be autonomous. Avoid creating tests that rely on other tests to be run beforehand. Tests should be executable in any combination and in any order.
Microsoft Unit Test Framework – Visual Studio 2012 Unit Test
Unit testing is a part of .NET framework. It is accomplished by the namespace Microsoft.VisualStudio.TestTools.UnitTesting, which includes the classes that provide unit testing support.
Creating a simple Unit Test:
The ASP.NET MVC team has included Unit Testing capability in the ‘New Project’ dialog for MVC applications. This is shown below. Alternatively we can create a Unit Test project directly inside Visual Studio, but it’ll result in additional work for getting started with unit testing MVC application.
The ASP.NET MVC has been architected for testability without dependencies on the IIS server, on a database, or on external classes. If we check ‘Create a unit test project’, a test project will be created in the ASP.NET MVC solution that contains unit tests for home controller.
Anatomy of Unit Test:
If a class should be treated as a Unit Test then it must be assigned with the attribute ‘TestClass’, otherwise the unit test methods in the class will not be identified by the test engine. Unit tests are required to be hosted within public classes, so need to include the public descriptor for the class.
The Unit Test methods must be assigned with ‘TestMethod’ attribute. These methods are always public, non-static, accept no parameters and have no return values. A typical unit test method will contain three main parts:
- Arrange: Here we setup environment needed for the running the tested code. This includes any initialization of dependencies, mocks and data needed for the test to run.
- Act: The actual invocation of the test method.
- Assert: Verification of pass/fail condition for the test.
Running Unit Test:
Once we build the test project, all the available tests will appear in the test explorer. Test explorer can be opened from Test > Windows > Test Explorer.
We can run all the tests in the solution by using ‘Run All’, or we can run all the tests in a group by choosing ‘Run’ and then choose a group on the menu, or we can run a set of selected tests. The Test Explorer displays the results in default groups of Failed Tests, Passed Tests, Skipped Tests and Not Run Tests.
Test Results:
The test summary of the test run are displayed at the details pane at the bottom of the Test Explorer. We can view the details of an individual test upon selecting it. The test details pane displays,
- Test method details such as source file name and the line number of the test method.
- The test status.
- The time that the test method took to run.
If the test fails, the details pane also displays:
- The message returned by the unit test framework for the test.
- The stack trace at the time the test failed.
Using Assert Methods:
The most common way to determine success in unit tests is to compare an expected result against an actual result. The Assert class contains many methods that enable us to make these comparisons quickly.
Assert.AreEqual and Assert.AreNotEqual: These are the most used assert methods. As the name indicates we are comparing an expected value to a supplied value. If the operands are not value-equivalent (or are equivalent for AreNotEqual), then the current test will fail. Assert.AreEqual and AreNotEqual have many parameter overloads, accepting types such as string, double, int, float, object, and generic types. One of the third parameter for this method is an optional parameter, a string that will be displayed along with our unit test results, which we can use to describe the failure.
Assert.AreSame and Assert.AreNotSame: AreSame and AreNotSame functions in the similar manner as AreEqual and AreNotEqual. The important difference is that these methods compare the references of the passed arguments. For example, if two arguments point to the same object instance, then AreSame will pass. Even when the arguments are exactly equivalent in terms of their state, AreSame will fail if they are not in fact the same object. This is the same concept that differentiates object.Equals from oject.ReferenceEquals.
Assert.IsTrue and Assert.IsFalse: IsTrue and IsFalse are used simply to ensure that the supplied expression is true or false as expected.
Assert.IsNull and Assert.IsNotNull: Similar to IsTrue and IsFalse, these methods verify that a given object type is either null or not null.
Assert.IsInstanceOfType and Assert.IsNotInstanceOfType: IsInstanceOfType simply ensures that a given object is an instance of an expected type. For example, suppose we have a collection that accepts entries of any type. We’d like to ensure that an entry we’re retrieving is of the expected type:
Using the CollectionAssert Class:
The Microsoft.VisualStudio.TestTools.UnitTesting namespace includes a class, Collection Assert, containing useful methods for testing the contents and behavior of collection types. The below example uses some of these methods of CollectionAssert class to verify various behaviors of a collection type, List. The example is runs with the test results in success. Please note that a proper unit testing would separate these checks across multiple smaller test methods.
Data-Driven Unit Tests with Mock Object Framework:
The applications we develop generally has external dependencies. These dependencies can be other objects in the system, files, databases or 3rd party libraries. In order to write unit tests for codes with such dependencies, we would prefer to use mocking frameworks. Mock objects helps us to simulate an object that we need to work with. Sometimes we may also come across situations or errors that are difficult to reproduce. Say for example we need to simulate a network error, or a specific error in DB operations, with a mocking framework we can easily do this. Another advantage of using mocking frameworks is external data which are used will not be affected. We can simulate a DB call and not affect our DB at all. We can also write a mock for a piece of code that doesn’t exist.
Microsoft.VisualStudio.TestTools.UnitTesting namespace doesn’t include any mock object or mocking frameworks. There are many open source and commercial mocking frameworks are available for .Net. Most of these are part of NuGet family of libraries. Some of the example mock object frameworks are Moq, Rhino Mocks, Typemock Isolator. The first two are having open source BSD license and the last one is a commercial framework.
In the following example we are going to use ‘Moq’ framework for creating Mock Objects for Employee Manager Repository. We can add this framework to our Unit Testing application using ‘Manage Nuget Packages’ under solution explorer in visual studio. What I have done here is,
- In the Sql server created an EmployeeManager database with a table in the same name with some general columns for employee details.
- Added an ADO.Net Entity Model to my application using this database.
- I’ve used Repository design pattern to isolate the data access from the rest of the application. This involves creating an interface and a concrete class that implements this interface that looks like below
- Created a Service Layer class for interaction between the controller and repository class. The service class has a structure of constructor dependency injection pattern, to use Repository class and Validation logics here.
- Created a Test Class for the service layer class which intern has Test Methods as listed below,
CreateEmployee() – Tests that CreateEmployee() returns the value true when a valid Employee is passed to the method.
CreateEmployeeRequiredFirstName() – Tests that an error message is added to model state when an Employee with a missing first name is passed to the CreateEmployee() method.
CreateEmployeeRequredLastName() – Tests that an error message is added to model state when an Employee with a missing last name is passed to the CreateEmployee() method.
CreateEmployeeInvalidPhone() – Tests that an error message is added to model state when an Employee with an invalid phone number is passed to the CreateEmployee() method.
CreateEmployeeInvalidEmail() – Tests that an error message is added to model state when an Employee with an invalid email address is passed to the CreateEmployee() method.
- Build the application and run the newly added tests using Test Explorer.