Summary:
In this article we review how to create a simple MVC 4 application
using a
Test
Driven Development (
TDD) approach.
We'll use the
Inversion
of Control (
IoC) pattern, along with
Microsoft's
Unity
Dependency Injection (
DI) container and
the
FakeItEasy (he easy mocking
library for .NET) framework. We start by considering the typical
Red, Green, Refactor TDD cycle, and
describe how IoC and mocking are a
desirable and logical consequence of TDD. We start from Functional Requirement
and conceptual model and start writing tests first of all. Then define interfaces, blank entities,
services and enforce business logic, validation, logging much more – all at
abstraction level without any implementation. Its remarkable.
Introduction:
TDD is not a testing
methodology; it's a design and development methodology!
It is nice to know variant views
about TDD. ‘TDD is not a good design methodology.’ You can read popular article at
The TDD Apostate.
Let us not go into details whether TDD results in good design or not, TDD is
still accepted, widely liked and remain would be alive in future.
I read this
article before
decided to write similar thing with ‘FakeItEasy’ mocking library and slightly different mind.
TDD emphasizes an iterative
development cycle where requirements are created in the form of unit tests, and
each test is written first - before the code that defines interfaces,
services, business logic, or UI.
Typically, for each work item (e.g. a particular new feature) the developer
will use a three-phase (Red, Green, and Refactor) cycle, with each cycle
being of short duration (e.g. an hour):
- Red:
write a unit test that fails
- First, make sure you understand the requirements for
the work item
- Design/imagine how you'd like the feature to be
implemented, then write the test code as though the code existed
- Create just the necessary interfaces and 'stubs' (or
use a Mocking framework - see below) so the test code compiles
- Run the test - it will fail because the 'real code'
has not yet been written. However, this verifies our mocked code isn't
working by accident, which could potentially provide a 'false positive'
at a later stage in development
- Green:
make the minimum changes required to pass the test
- add the minimum code required to make the test pass -
make use of mocking or hard-code method return values
- don't add error handling code - do this later, again
driven by specific tests
- re-run all unit tests (regression testing) to ensure
you haven't broken anything
- Refactor:
improve
design, add business and data persistence logic, etc.
- gradualy replace stubbed or mocked code with
real-world implementation logic
- improve the design, etc.
- re-run tests to ensure eveything still works
It's interesting to note that this
deceptively simple approach actually encapsulates a major change in the way we
approach the task of writing software. Normally we ask the question, "How
will I write the code to create the solution?". With TDD the question
becomes, "How will I know I've solved the problem?". As J.
Timothy King says in his article Twelve
Benefits of Writing Unit Tests First: "We're taught to assume we
already know how to tell whether our solution works. It's a non-question. Like
indecency, we'll know it when we see it. We believe we don't actually need to
think, before we write our code, about what it needs to do. This belief is so
deeply ingrained, it's difficult for most of us to change."
There is a complete mental shift as
thinking with TDD approach for developers, designers and architects. Instead of
thinking in three basic traditional steps: design, implement, and test, we must
build and test small components over time and improve with refactoring – thus
lead us to create a system architecture, what benefits are we likely to see?
- Design:
being able to run automated unit tests forces us up-front to design
a system composed of loosely-coupled components (e.g. separation of
concerns)
- Reassurance:
re-running the unit tests assures us that any change we make hasn't broken
anything
- Team-working:
re-running all tests (e.g. at the end of the day) is a good way of picking
up any breaking-changes at an early stage
- Documentation:
tests actually document how the system works (and they can't be
out-of-date)
- Metrics:
tests provide a practical measure of progress (e.g. "the system
passes 78 of 100 tests")
Obviously, the first step is to
capture the functional requirements that define what the system must do and the Quality of Service (QoS) requirements
that define how well it must
perform.
Problems with Conventional Approach
Conventional methods divide the
development process into three basic steps: design, implement, and test. The
problem with this approach is that the two types of strategic defects, those
related to requirements and architecture, are often introduced early but
detected late. Of all defects, these two are the most costly because they can
significantly affect most or all of the system.
If tested primarily at the end of
the development cycle, the system is likely to include defects with complex
design interdependencies, making their identification and removal expensive,
error-prone, and time-consuming. These strategic defects are often not
identified until the validation phase when they might have thousands of subtle
dependencies based on assumptions that the fix invalidates. Finding these
dependencies and repairing them can be difficult.
TDD solves this problem by building
and testing small system components over time. With TDD, testing is not dealt
with all at once toward the end of the development cycle. Instead, it is
performed incrementally throughout the development life cycle to ensure that
the system works as specified at every phase.
This incremental approach requires
that developers test a project at every stage of its development, meaning that
the system must be constructed from executable and testable models. Scenarios
specified during requirements capture are used in downstream development to
show that models are correct and meet the requirements. The goal is to identify
strategic defects as early as possible; these defects can cost up to 1,000
times more than coding defects if they are discovered late in the project. A
good rule of thumb is that no more than 5 percent of defects should be
identified in the last 10 percent of the project life cycle.
TDD in Action - an MVC 4 Demo App
To look at TDD in practice, we'll
develop a very simple "Subscription" application using the following:
- Microsoft Visual Studio 2012 with the MVC 4
- Microsoft Entity
Framework 4 (we'll get the latest version using the NuGet
Package Manager in Visual Studio)
- Microsoft Unity
dependency injection container (we'll get it using NuGet)
- The FakeItEasy
mocking framework (we'll get it using NuGet)
Our simple 'MvcOnlineStoreApp' will
be iteratively developed with simple set of functional requirements as follows:
- There will be a simple home page with a link that takes
the user to another page that displays a list of categories
- A Category must have a descriptive name.
- Initially, units tests will use the mocking framework
to return the list of categories.
- We'll use the repository pattern to de-couple
the data persistence layer from the controller
- The dependency injection container will be used
To get started, open Visual Studio
and create a new MVC 4 project (and when prompted, choose the 'Empty'
template) - I named my project 'MvcOnlineStoreApp':
In next screen, select template
‘Internet Application’ and check ‘Create a unit test project’ option to true.
Move both projects (MvcOnlineStoreApp
and MvcOnlineStoreApp.Tests) under a logical forlder named ‘UI’.
Let us create two more logical
folders named ‘Domain’ and ‘OnlineShop’.
Create four library projects under
‘Domain’ folder.
- Domain.Entity: Contain dummy DTO classes like Category.
- Domain.Infrastructure: Contain common interfaces used across application
- Domain.Util.Infrastructure: Contain interfaces used for Utility classes
- Domain.Util.Implementation: Contain implementation of Utility interfaces
Now
consider ‘OnlineShop’ conceptual box may contain some abstract services. We
must start creating empty WebApi services having no display functionality.
Think of following services in Online Shop.
-
CategoryService
-
ProductService
-
PartyService
-
OnlinePurchaseService
-
OrderService
Right click on ‘OnlineShop’ folder and add new
project. Select ‘ASP.NET MVC 4 Web Application’ template, click browse button
next to Location text box and create a folder named ‘OnlineShop’.
Next step
us to select ‘Web Api’ and click OK button. Two projects are created.
Delete
following four folders because we never use this content in back-end service.
Under
‘Controllers’ folder delete Home and value controllers because we never use
them. Similarly delete a test controller under ‘Controllers’ folder in project
‘ProductService.Tests’.
Build the
project now, it should run without errors.
Add new
controller named ‘CategoryController’ as follows.
Add a new
interface named ‘ILoggerService’ under ‘Domain.Infrastructure’ project. I know
its is responsible of loggin information and errors somewhere, we don’t know
yet. But let us assume we know nothing until test will drive us to add methods
in it.
Keep it
simple:
Go to
project ‘Domain.Enitiy’ and create new class named ‘Category’. Uth is we only
know Category contains a Name. So code looks like this.
Go to
‘ProductService.Tests’ folder and new plain class under ‘Controllers folder
as:
Now in
same project install NuGet packages ‘FakeItEasy’ and ‘Unity’ as follows:
Now add
references to three more projects to the project ‘ProductService.Tests’ as
follows:
Let us add
some code in test class to look like this.
Above code
contains [TestInitialize] method that is executed at start.
I tried to create Fake objects of ICategoryService and ILoggerServcie. And pack
the CategoryController object in Unity container to use it in further test
cases.
So I need
to create two things.
1 - Need to create ICategoryService Interface (proper place should be Domain.Infrastructure project)
2 - CategoryController parameterized constructor to inject ICategoryServcie and ILoggerServcie dependencies
Point 1: Letus
add ICategoryServcie and a ListCategories method as well (I should not add it
until I need it in tests but let’s do this way for now)
I shifted my
scope by assuming I should for the time being use Repository ‘ICategoryRepository’ instead of IRepositoryService. So
I added bit of code as follows.
Add two
methods under ILoggerServcie interface. (again I should add them after writing
test but let us do it for now – sorry violating TDD)
Point2: Create a constructor now in ‘CategoryController’
Let us
write first basic test that fails passing Null as first parameter to
‘CategoryController’
Let us try
to pass it. I added two lines in contructor and run test – it passes.
Reason to
fail was because test was expecting ArgumentNullException when passed NULL as first parameter to constructor.
I added
some more three tests same way and refactored the Contoller code accordingly,
so final code looks like this:
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void
Initializing_CategoryController_with_Null_ICategoryRepository_throws_ArgumentNullException()
{
var sut = new CategoryController(categoryService: null, loggerService: A.Fake<ILoggerService>());
}
[TestMethod]
public void
Initializing_CategoryController_with_Valid_ICategoryRepository_sets_CategoryService_property_to_provided_value()
{
var categoryService = A.Fake<ICategoryRepository>();
var sut = new CategoryController(categoryService:
categoryService, loggerService: A.Fake<ILoggerService>());
Assert.AreSame(categoryService,
sut.CategoryService);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void
Initializing_CategoryController_with_Null_ILoggerService_throws_ArgumentNullException()
{
var sut = new CategoryController(categoryService: A.Fake<ICategoryRepository>(), loggerService: null );
}
[TestMethod]
public void
Initializing_CategoryController_with_Valid_ILoggerService_sets_CategoryService_property_to_provided_value()
{
var loggerService = A.Fake<ILoggerService>();
var sut = new CategoryController(categoryService: A.Fake<ICategoryRepository>(), loggerService: null);
Assert.AreSame(loggerService,
sut.LoggerService);
}
And final shape
of CategoryController looks like this:
public class CategoryController : ApiController
{
public ILoggerService LoggerService { get; private set; }
public ICategoryRepository CategoryService { get; private set; }
public CategoryController(ICategoryRepository categoryService, ILoggerService loggerService)
{
if (categoryService == null)
throw new ArgumentNullException();
if (loggerService == null)
throw new ArgumentNullException();
CategoryService = categoryService;
LoggerService = loggerService;
}
}
My four
tests are now succeeded.
Let us
implement First requirement now “To Fetch List of Categories From whatever (aka
DB)”
Wrote my
first requirement test code as follows. It is success scenario fetching categories
list.
The catch
is, we need to implement sut.ListCategories method in the CategoryController
class. (sut = system under test)
Here is
the method implementation:
Run all
the tests again on CategoryControllerTests class and you will get all of them
succeeded.
At the end
– you see how we deal in abstractions and write tests using FakeItEasy library.
It is remarkable to have no implementation but sufficient code coverage. It
shows my code is well written and bug free in very start. We can always start
from Functional requirements, conceptual model and start writing system
architecture in Visual Studio with all necessary components in mind. We know it
will continuously change with requirements but at least we have pretty solid
ground to start with every changing requirements especially we know less in the
beginning.
I will discuss
the concept more in next posts but if you need running sample please DOWNLOAD from skydrive. (FileName: MvcOnlineStoreAppUsingTDD.zip).