CHAPTER 6

image

Advanced Unit Testing and Test-Driven Development

If unit testing is a practice that you choose to utilize in your projects, chances are that once you get the hang of writing effective unit tests, you will write a lot of them. It’s important to remember that a unit test fixture is a class, and as such, you should take advantage of the benefits offered by object-oriented design.

Test Fixtures Are Classes Too

When writing your unit tests, be sure to keep your eyes open for any chance to reduce complexity. Since your test fixtures are usually centered on the same domain, you will find yourself creating instances of entities to be used throughout all of your test classes. If you find that you are writing the same code in several methods or test classes, you should remember to adhere to the DRY principle: don’t repeat yourself. Inheritance is obviously a great tool to use in this case.

Use Inheritance to Avoid Duplicate Code

The business domain that you are modeling in your application’s design is based on creating and taking quizzes to assist the user in studying for official exams. As we’ve previously discussed, there is a root entity called Quiz. A Quiz is a collection of Question instances, and a Question is a collection of Answer instances.

You will generally create a unit test library for each layer of your application. To test each layer of the application, it’s safe to assume that you will need routines to create instances of the Quiz root entity as well as all of the Quiz properties. Instead of duplicating all of the boilerplate code used to create these test classes, you can create a base class with methods to create all of the test entities that you will need throughout your unit test classes. All unit test classes can inherit from this base class and take advantage of inheritance to eliminate duplicated code.

Consider the base class shown in Listing 6-1.

As you can see, this class provides methods to create instances of any entity that you will need while unit testing the layers of your application.

Unit Testing Classes That Have Dependencies

As we’ve discussed in previous chapters, dependency injection and inversion of control are two ways to design code that is open to change and closed to modifications (also known as the open/closed principle). If you design your classes with this open/closed principle in mind, you can greatly reduce the costs associated with a project’s maintenance after the solution has been deployed.

Dealing with Dependencies

Unit testing and Test-Driven Development can be great tools to use to design and develop loosely coupled components that are open to change and easy to reuse in other projects with a minimum amount of code modification. To test a specific class effectively, you need a way to deal with the dependencies of the class in which you can’t control or predict the behavior.

Some examples of such dependencies include objects that interact with a database, the file system, or any third-party dependency in which you don’t have access to the source code. For instance, suppose you are testing a web service or an implementation of the repository design pattern. In either case, you need to write tests that produce the same results on every test run.

If you are unit testing a domain service class that has a dependency on a web service, how can you control the speed in which packets of information are transmitted across the network to and from the web service? Remember, one of the requirements of a good unit test is that it needs to run fast. Suppose the developer’s workstation that is running the unit test loses its connection to the Internet. How can you control or predict the return value from a method call on a dependency class if it’s possible that an exception could be thrown?

You run into the same issues when unit testing a class that depends on a database connection. Let’s pretend that your DBA’s only two weaknesses are kryptonite and schema changes. Super DBA can guarantee that the database server will never lose its connectivity, and it has created indexes that result in SELECT statements that are faster than a speeding bullet.

For argument’s sake, connection issues and speed of execution aren’t your problem. There are still many issues that you will face if you try to write unit tests for a class that has a dependency on the super database. First you will need a database that is dedicated to unit testing. After all, you can’t write tests against a database unless you can guarantee the state of the data and the values that will be returned with every query.

To achieve this, you will need to develop the unit test with a considerable amount of setup and teardown logic to prepare the tables that you plan to use. In a situation like this, you will need to make sure that the test table is in the correct state before each test, populate the table with the return values that you expect, and reset the table to the initial state after each test is run.

Even with a dedicated database, what happens if two developers execute the same unit test at the same time? What about three, four, or twenty developers? I think you can see where I’m going with this. If your unit test causes any INSERT statements to execute on the super server while another developer executes a test that will SELECT data, then chances are that someone will end up with an unexpected return value, which could fail the test.

You can’t rely on these types of dependencies. So, how do you unit test classes with dependencies that you can’t control? To begin with, it’s important to keep these issues in mind when designing your classes. This is one of the many benefits of Test-Driven Development. If you write your tests before you write your code, your code will be open to change as a side effect of designing classes that can be effectively unit tested.

Another important design decision that will help you manage dependencies in unit testing is to utilize interfaces to create classes that are open to change without modification. If you utilize polymorphism through interface references when designing your classes, then you can easily change the implementation of the dependencies used in your unit tests. You simply create different implementations of the dependencies used by the class under test. If you can easily switch the implementation of an interface reference, you can then create a class that will always behave exactly as the assertions in your test methods expect them to behave. The only requirement is that each class implementation must adhere to the contract defined by the interface. These special test class implementations are called stubs.

For example, let’s say you’ve created a data access class with an implementation that is tightly coupled to the SQL Server–specific ADO.NET data access classes, which are baked into the .NET Framework class library. Listing 6-2 provides some example code.

The first thing to note is that the class implements the IQuizDataAccess interface. Listing 6-3 shows the interface definition.

As you can see, the QuizSqlDataAccess class requires a valid connection to a SQL Server database in order to load quiz data to return to the caller. Now you could use the data access object directly in your domain service classes; however, what happens when you need to support a different database system? If your domain services directly reference the ADO.NET-specific implementation of the IQuizDataAccess interface, you would need to create an implementation of the interface that is designed to work with the new database technology. You will also need to modify every class in the entire project, as well as any other project that uses the QuizSqlDataAccess class. If you apply object-oriented design principles, you would write your classes so that they can be reused in other applications. (If you don’t, then shame on you.) This can be a big problem, depending on how many different applications share your data access class. If only there were a design pattern that you could use to abstract the data access technology away from the code that uses it. Well, you’re in luck! You can use the repository pattern to achieve the level of abstraction that you desire.

Repository Pattern

This is the problem that the repository pattern was created to solve. As discussed in Chapter 4, the repository pattern is designed to provide a level of abstraction between the database access logic and the caller of that logic. The repository is meant to act as an in-memory collection of all objects stored in the database. This provides a level of abstraction that removes the complexity of dealing with the database and places it in a class that is dedicated to one specific data access technology. This also allows the developer to switch to any database system easily without making any modification to the repository. If a new database technology is created, all that is required is for a developer to create a new implementation of the IQuizDataAccess interface. The dependency injection pattern and inversion of control principle allow you to change an interface’s class implementation in only one place. This is great. However, you still need a way to test the repository in your unit tests without relying on the database. This is where you can apply stubs and mock objects.

Stubs

One way to deal with dependencies in unit testing is by creating stub classes. Before we discuss stubs, we need to talk about interfaces. We’re sure that you know the importance of interfaces in relation to basic object-oriented programming and design. Interfaces are essential in leveraging polymorphism in C#.

When you create an interface reference, your code’s design makes it easier to replace the implementation of an interface by dynamically creating an instance of the appropriate class and then passing that object as an interface reference.

If you utilize interfaces and polymorphism when designing your dependencies, then you can create an implementation that is used only for unit testing purposes. This is known as a stub. Let’s take a look at the example shown in Listing 6-4.

As you can see, the Stub class implements the IQuizDataAccess interface as well as inherits the functionality of the QuizTestBase base class created at the beginning of the chapter. The sole purpose of the Stub class is to provide an implementation of the IQuizDataAccess interface that removes the dependency on the database-specific logic, and therefore it guarantees that the methods will return the same results on every call. This allows you to achieve your goal of writing predictable unit tests that execute quickly. Stub objects are great for simple dependencies. Sometimes the dependencies are complex, however, and you find that you are writing a stub that needs unit tests. If you find yourself in this position, then Stub classes may not be the best tool for the job. If you are dealing with complex dependencies, there is another option to help you to write effective unit tests, known as mock objects.

Mock Objects

Stub classes are extremely useful when you need to create an implementation of a dependency in order to dictate the return value of a method or property. Sometimes, though, you need more control over the dependencies of the class under test. For instance, you may need to verify that a dependency’s methods are called in a specific order. When you need this type of control over the dependencies that you deal with in your unit tests, you don’t need a stub—you need a mock object.

Since you use mock objects for complex dependencies, their design and implementation are more complex than stubs. As such, there are existing open source frameworks that can save you time and effort when creating mock dependency objects. We will deal with only one mock framework in this chapter.

The mock framework that you will use is called Moq. You can easily install the framework using the NuGet package manager. You can also download the full source code for the framework from https://github.com/Moq/moq4.

Using the Moq Framework

We will provide an overview of some of the essential tasks that you will use the most when utilizing the Moq framework to enhance your unit tests. Don’t fret, the Moq project has a nice overview on the GitHub project site mentioned earlier. We’ll also provide other URLs that will come in handy as you learn to use this awesome Mock framework.

Using the Mock Class to Set Up Your Dependencies

The first step in using the Moq framework is to create an instance of the Mock class.

Create an Instance of the Mock Class

You start by creating an instance of the Mock<T> class. The Mock<T> class is a generic type where T represents the type that you want to mock. In this instance, you are mocking a class that implements the IQuizDataAccess interface:

var mock = new Mock<IQuizDataAccess>();

As you can see, the Mock class makes use of generics to specify the type of mocked instance that you want to create. The next step is to set up the behavior of the method or property that you want to use in your unit test.

Set Up the Behavior of the Mock Dependency Object

When you set up the behavior of the Mock dependency object, you use the framework to specify which member you plan to work with and the return value of that member. Moq allows you to do this using LINQ expressions, which will give you IntelliSense and enforce type safety.

In this example, you want to test the GetAllQuizesFromDatabase method of the data access object. The QuizTestBase class defines the PrepareTestQuizCollection method that will return a collection of Quiz objects. You will use this method to create a collection of Quiz objects to use to set up the return value of the Mock object’s GetAllQuizesFromDatabase method. Listing 6-5 shows an example.

As you can see, Moq is flexible in that it allows you to set up a dependency without the need to create a class by hand. This is only one of the benefits associated with the Moq framework. You can also use the Moq framework to determine whether a method has been called or a property has been accessed or had its value set in the unit test.

A Complete Example

To complete the coverage of how to deal with dependencies in unit testing, we will provide an example of two unit test fixtures. Both of the classes inherit the functionality defined in the QuizTestBase base class. The first unit test class demonstrates the use of a Stub class. The other unit test class demonstrates the use of the Moq framework to create a mock dependency. Review the example shown in Listing 6-6.

Listing 6-7 shows the unit test fixture for the IQuizRespository using the Moq framework. The Moq framework is used to mock the SQL-specific data access service that is used by the repository.

Now that we’ve covered some of the more advanced concepts involved in writing unit tests, we will explain the basics of a powerful design methodology that, when used correctly, can help you to design loosely coupled classes that are open to change, closed to modification, and have proper unit test coverage. The design methodology that we’re referring to is Test-Driven Development (TDD).

Design by Testing: Test-Driven Development

Test-Driven Development is an iterative process. There are three basic steps required when implementing TDD. The steps are easy to understand; however, they can be difficult to implement correctly without discipline, which can be gained only with a lot of practice. The steps involved are covered in the following sections.

Step 1: All Unit Tests Should Fail on the First Test Run

When you use TDD, you start by writing one unit test to validate the expected result of the class under testing. You may simply need to verify that a method returns the correct value or that an object’s property contains the expected value after a sequence of method calls.

Keep in mind, however, that when you begin writing a unit test, the class in which you are testing should not even exist. Once you have finished writing the unit test, your first goal is to run the test so that it fails. You might be asking yourself, “Why would I want a unit test to fail?” For a unit test to fail, you must be able to run the test. To run a unit test, your solution must be buildable. So, your first step will be to create the class definition of the class under test.

Image Caution  It is extremely important to write the smallest amount of code possible in order for your unit test to build.

This means that you should write enough code to make the test build. If you are testing a method that returns a value, you shouldn’t worry about writing any code that is related to the logic of the routine. You simply define the method signature and throw a new System.NotImplementedException instead of leaving the method’s body empty. Otherwise, the method body will be empty, and since the method signature specifies a return data type, the project will not build unless there is a return statement or you throw an exception that specifies that the method has not yet been implemented. This exception will allow the project to build without implementing the specified method. Once you have defined any class, interface, method, or property that is used in your unit test, you should be able to build your unit test project. Once the project is in a buildable state, you are ready for the first step.

You simply run the unit test using your framework of choice and verify that the test fails as expected. Remember, up until this point, your class does not have any implementation details. However, you have created a test, and at the same time, you have created an interface for the class under test that is designed with the use of the class’s interface in mind. This provides a layer of abstraction, which allows you to focus on the high-level design of how the class is used while encapsulating the gory implementation details, allowing you to ignore the complexities of how the class’s members are implemented. To put it simply, you first define how the class is used, and then you iteratively define what makes the class work.

This will help you to write tests that clearly document how the CUT is meant to be used and, just as important, how it should not be used. If a new developer joins the team, they can simply review a test fixture to understand the intended use of a specific class, method, or property.

The first test run fails because the class under test GetAll method currently does not have an implementation. The method throws a System.NotImplemented exception, which allows the solution to build. You can see the failed test in the Visual Studio test runner in Figure 6-1.

9781430267768_Fig06-01.jpg

Figure 6-1. Failed unit test in Visual Studio

One of the keys to understanding and mastering TDD is to make sure that you make small iterative modifications when implementing a method in order to make your test pass.

Step 2: Add Only Enough Code to Pass the Test and No More

At this point, you’ve written a unit test to define how to use the class under testing. You’ve created an empty class definition, along with any class members that are used in the unit test. The next step is to implement any method that you call in the unit test so that the test passes.

Image Caution  Remember to add only enough code to make the test pass and nothing more. This is important because it keeps your methods simple, and it promotes the design of tightly cohesive methods. In other words, the method will do one thing and only one thing. Any extra code that you add at this stage adds complexity. Complexity increases the potential for bugs, as well as diverting attention away from the intent of the method. You should follow the KISS principle: keep it simple, stupid.

Now that you’ve implemented the method, the unit test should now pass. Figure 6-2 shows the test pass in the Visual Studio test runner.

9781430267768_Fig06-02.jpg

Figure 6-2. Passed test in Visual Studio

Step 3: Tighten Up Your Code by Refactoring

So now you’ve created a unit test to define how the class will be used, and you implemented the logic required to make the unit test pass (and only enough code to make it pass). At this point, you can look at the implementation of the method that you’ve created to identify ways to improve the readability of your code as well as to add any defense programming–related input checks. The term for improving the internal design of a routine without modifying the external interface in which the method or routine is called is referred to as refactoring. You should always remember that all TDD steps should be performed in small iterative steps. With each iteration, it’s mandatory that you execute all unit tests. This will guarantee that any modifications that you made in the previous iteration haven’t caused a side effect that introduced a bug of which you aren’t aware. Once you’ve used refactoring to clean up the code that you’ve written and all of your unit tests pass, you can start the process over again and start a new unit test.

Image Caution  I can’t stress enough the importance of sticking to the steps of fail, pass, and refactor. It’s also extremely important that you make modifications in small increments. As we’ve said, this requires a great amount of discipline that can be learned only through lots of practice.

Summary

We covered a lot of information in this chapter. Test-Driven Development can be an effective tool when implemented properly. It can be a terrible waste of time when done incorrectly. One great way to practice TDD is a method that is borrowed from martial artists: katas. Katas involve writing lines of code over and over again to learn the iterative approach and other disciplines required to utilize TDD effectively. Here is a URL to a website by Roy Osherove, which is full of resources to help you to learn how to implement TDD properly: http://osherove.com/tdd-kata-1/.

Next up in Chapter 7, we will cover exception handling and logging using the Microsoft Enterprise Library Exception Handling Application Block.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.142.232