a few of TDD principles:
Some benefits of automated unit tests:
Unfortunately, there are many pitfalls in the path, and the coding unit tests can be a very hard (if not impossible) task, due to bad design choices made by merely applying to new projects the same paradigms of the legacy projects. This is why software architectures should add testability as a requirement for their development cycles.
It might be said that an application can be deemed easy to be tested if their components (assemblies and classes) are easy to be tested.
An easy-to-be-tested class is one which can be easily isolated from the other classes it interacts to. That is, we should be able to test a class individually, without having to be concerned with other classes' implementation. Thus, if a unit test fails, it would be much easier to find the source of the bug.
In unit testing, we isolate the class being tested by creating mocks of the classes it depends on. Mocks are fake instances of a class/interface, and stand for concrete objects. Mocks are critical tools for unit testing isolation.
This is called "tight coupling", and is a bad design, which hinders the testability of your application, because you will not be able to easily isolate the two classes. Most mock frameworks can't work with tight-coupled classes. Certainly there are tools (like TypeMock) which can do that job.
Dependency Injection Pattern, also known as Inversion of Control (IoC)
The Dependency Injection Pattern (a.k.a Inversion of Control)
InvoiceServices.Test project/*
* Created By HOME
* User: BPLOVEGCY
* Date: 2007-6-8
* Time: 16:12
*
* http://bplovegcy.blogspot.com/
*
*/
using NMock2;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace InvoiceServices.Test
{
[TestFixture]
public class InvoiceServicesTest
{
[Test]
public void CalculateInvoiceTest()
{
Mockery mockery = new Mockery();
ITaxServices mockTaxServices
= mockery.NewMock<ITaxServices>();
InvoiceServices invoiceServices
= new InvoiceServices(mockTaxServices);
Expect.Once.On(mockTaxServices).
Method("CalculateTax").With(100.00F).
Will(Return.Value(5.35F));
float totalAmount
= invoiceServices.CalculateInvoice(10001, 10002, 10003);
//we expect an invoice with product codes 10001, 10002 and 10003
//to have an amount of $100.00 + a tax amount of $5.35 = $105.35
float expectedTotalAmount = 105.35F;
Assert.AreEqual(expectedTotalAmount,
totalAmount,
string.Format("Total amount should be {0}",
expectedTotalAmount.ToString()));
}
}
}
* Created By HOME
* User: BPLOVEGCY
* Date: 2007-6-8
* Time: 16:12
*
* http://bplovegcy.blogspot.com/
*
*/
using NMock2;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace InvoiceServices.Test
{
[TestFixture]
public class InvoiceServicesTest
{
[Test]
public void CalculateInvoiceTest()
{
Mockery mockery = new Mockery();
ITaxServices mockTaxServices
= mockery.NewMock<ITaxServices>();
InvoiceServices invoiceServices
= new InvoiceServices(mockTaxServices);
Expect.Once.On(mockTaxServices).
Method("CalculateTax").With(100.00F).
Will(Return.Value(5.35F));
float totalAmount
= invoiceServices.CalculateInvoice(10001, 10002, 10003);
//we expect an invoice with product codes 10001, 10002 and 10003
//to have an amount of $100.00 + a tax amount of $5.35 = $105.35
float expectedTotalAmount = 105.35F;
Assert.AreEqual(expectedTotalAmount,
totalAmount,
string.Format("Total amount should be {0}",
expectedTotalAmount.ToString()));
}
}
}
InvoiceServices project
/*
* Created By HOME
* User: BPLOVEGCY
* Date: 2007-6-8
* Time: 16:12
*
* http://bplovegcy.blogspot.com/
*
*/
using System;
using System.Collections.Generic;
namespace InvoiceServices
{
public class InvoiceServices
{
ITaxServices taxServices ;
public InvoiceServices(ITaxServices tax)
{
taxServices = tax;
}
public float CalculateInvoice(params int[] productCodes)
{
float invoiceAmount = 100;
//Here goes the code to calculate
//the invoice amount based on products
return invoiceAmount + taxServices.CalculateTax(invoiceAmount);
}
}
public class TaxServices:ITaxServices
{
public TaxServices() { }
public float CalculateTax(float invoiceAmount)
{
float tax = 0;
// Here goes the code to calculate invoice tax
return tax;
}
}
public interface ITaxServices
{
float CalculateTax(float invoiceAmount);
}
}
* Created By HOME
* User: BPLOVEGCY
* Date: 2007-6-8
* Time: 16:12
*
* http://bplovegcy.blogspot.com/
*
*/
using System;
using System.Collections.Generic;
namespace InvoiceServices
{
public class InvoiceServices
{
ITaxServices taxServices ;
public InvoiceServices(ITaxServices tax)
{
taxServices = tax;
}
public float CalculateInvoice(params int[] productCodes)
{
float invoiceAmount = 100;
//Here goes the code to calculate
//the invoice amount based on products
return invoiceAmount + taxServices.CalculateTax(invoiceAmount);
}
}
public class TaxServices:ITaxServices
{
public TaxServices() { }
public float CalculateTax(float invoiceAmount)
{
float tax = 0;
// Here goes the code to calculate invoice tax
return tax;
}
}
public interface ITaxServices
{
float CalculateTax(float invoiceAmount);
}
}
The Northwind Solution: putting things together
The application architecture looks like this:The Model-View-Presenter pattern
To solve this problem, there is a set of design patterns such as Model-View-Controller and Model-View-Presenter. I prefer using the MVP approach because it enhances testability.Model: The domain model objects. (e.g. the Customer class)
The participants of a MVP pattern are:
Separated Interface pattern
Taking a closer look on the interaction between view and presenter in the package diagram below, we'll see that the Northwind sample uses a Separated Interface pattern. The Windows Form namedCustomerView
implements an ICustomerView
interface, which is placed in another assembly (the presenter package). The CustomerPresenter
object knows it will have to control a view, but since it holds a reference to the ICustomerView
interface, it really doesn't know how the concrete view is going to look like. This pattern is another good design for enabling unit testing, because we'll be able to easily inject a "mocked" view in the presenter, thus performing isolated unit tests on presentation logic.Widgets
The Services layer
Instead, it should call methods in the Services layer. The Services layer is a good design option, because it exposes well defined processes of the application. Besides, it is a good place where you can define transaction scope, and if you want to implement transactional updates on domain objects, you can implement the Command pattern in Service layer (the Command pattern would enable you to support undoable operations on domain model objects).Services layer test results
The Tranfer Objects layer
The transfer objects are read-only POCOs (Plain Old C Sharp Objects) that transfer domain model object data between the layers in the application.Tranfer Objects layer test results
ActiveRecord
attribute provides the table name the domain object is mapped to. This attribute is used by the ActiveRecord framework, an Object-Relational Mapper framework, and will be discussed later on in this article. MappedTO
attribute provides the TransferObject fullname, and is used by the TOHelper
class (in the TransferObjects package) in the Domain Object-to-Transfer Object copy operations.The Unit of Work pattern
The Active Record pattern
Martin Fowler describes Active Record as
An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.
The Castle ActiveRecord was built on top of NHibernate, and provides a powerful, yet simple, set of Object-Relational Mapping functionalities which allows you to persist your domain objects very easily. With Castle ActiveRecord, you can focus on designing your domain model, while saving time working in data access maintainance.
The Repository pattern
The last pattern in this article is the Repository pattern. Edward Hieatt and Rob Mee state that the Repository patternMediates between the domain and data mapping layers using a collection-like interface for accessing domain objects
No comments:
Post a Comment