Introduction:
Many of us hear today about
unit test, and mock objects, other may touched unit test first
time with NUnit
tool
http://www.nunit.org/Or
through visual studio 2005 Team Suite, There are many articles about how to
create unit
test, or how to test your applications And how to
use different patterns for test.
This is not the target of this article, this
article focus in how to create
testable application Or
how to refactor your normal application code, to be testable code.
Acknowledgment:
I want to thank first person, introduce the unit test and mock objects to me, through a quick
training session, he is Mark Focas Senior Analyst Programmer, Center for Learning
and Innovation
My own experience:
I did not realized the
important of unit test, until I worked in a big application it contains
more
than 15 .Net projects this application did not rely on dataset at all, so all
objects are
custom objects and collections, so the total
number of classes about few thousands, to
control this code without unit test is quite impossible.
Each time I finished module I want to
know what is the impact in the other modules in
the
application. at
the same time, it saves time, in web application development, to write
the
code and run the web application each time you finished the code,
it will be killing process.
So
after creating thousands of unit tests, and running all of them after finishing
each
module I can recommend to work with unit
test even if your application looks simple.
Quick definitions:
Unit Test:
Unit test is automatic test,
test a unit or piece of code may be an object, method in object,
or
property, or any other unit through a Framework is called Nunit
this
Framework give you all the tools that you need for comparison, assertion,
expected
Exceptions ,.. that you need to test your applications.
Mock object :
Is an Object that emulate
(act as) other real object dependency, so we can isolate the
objects from
their dependencies as possible to focus in each object itself.
Integration test:
The tests that use 1 or
more objects and dependency, to see the effect of each object
works with its
dependencies
Bug fixing:
Bug fixing is
a process of changing a piece of code and running a unit test to test this code,
then running an integration test to test the effect of this change of the
other code.
Using the code:
- First
you should download Nunit open this page
http://www.nunit.org/index.php?p=download
then choose win .net
2.0 NUnit-2.4.0-r2-net-2.0.msi
- Then unzip the article code, and open the article solution in Visual
studio 2005 and
make UnitTest Project is
the Start up project.
- Choose UnitTest project properties and be sure
that the Start external program: field
And Working Directory are pointing to the right location.
figure 1, the unit test application
configuration
- Run the application, the program will launch
the Nunit , then Click Run You should
see
this screen
figure 2, the unit test result
The Project's Idea:
The project
simply is an example of simple billing system, The Client will fill the invoice
fields
data then will Issue the Invoice With this process the Invoice Database
will be effected and
the Customer database will be effected as well,to
Debt the customer with the total value of
the invoice.
The
Payoff process will simply pay off the invoice, and will update the Customer
account with
the transaction
figure 3, 2 versions of Code Testable, and non
testable
for the demo we created 2 versions of the Project Objects
- Non
Testable Objects
All objects that under
the Nontestable namespace
- Testable Objects.
All objects that under the Testable
namespace All objects that under the Testable
namespace
Characteristics of Non Testable
Applications:
Every application can be testable, as manual test or minor
automatic test,
the QA department is still testing the
application through running the application and creating
different scenarios,in
different Platform(s) but we are focus in the Automatic unit test,
that
made by programmer as a
part of his job .
To create unit tests for Non testable
application is not easy at all, because simply you always
will test the
outer shell of integration test, and in most case you
could not know where is
the bug, unless you step in the code line by line to
know where the
code was broken and we can summarize the Non testable application
1) Is not separated in well designed
layers:
we consider that is the same condition for well designed OOP, if
your application is not well
separated in different layers it means you could
not design objects had specific
responsibilities , and encapsulate the internal
implementation.
2) The code is not
reusable:
That is the first step of refactoring the code, we gather the
common code in methods,
then we may encapsulate the code that has
identity to objects, then abstract the objects
to
more abstracted object, until we find that there are no redundancy in the code
at all,
the result will be very clear , reusable and
wonderful maintainable application
3) The
Code hides internal dependency that we could not isolate:
This point is
first point that differentiate the testable application and good OOP
application,
we know that the concept of OOP is built in
Encapsulation.
So we need both the code be encapsulated and
hide the internal implementation and
be open for changing the dependency in
runtime "And that is the Challenge"
4) The
objects has not any contracts :
If the object has not any contract
(interface or abstracted class that inherit) it means that
client object .will
consume the concrete class not the abstracted or the interface, And finally
we
will end up with very static code, unchangeable So it
will be very hard to test.
5) The objects
methods and properties are not virtual:
This requirement Is not mandatory
but it will be helpful if you think
to replace any object with another one in
runtime for testing purpose It means that in run
time, the
client class still see the old implementation not the new implementation that
supplied with the new version (new subclass or new
inherited class) ,
and we will end up with non testable
application because we could not change the behavior
of the objects in runtime.
if you already worked with automatic mock objects, specially in
.Net 1 this
concept will be more clear.
Apply
these simple concepts on the sample code:
figure 4, class diagram of non testable
code This design is a good sample for Non
testable Application for these Reasons
- The
Implementation of the business objects and data objects exists in the same
place,
it is very hard to separate the objects in layers so we have at least 2
layers mixed in
one layer.
- The code is not reusable , we found the method
ExecuteStoredProcedure is common in
Invoice object and Customer object, however it is implemented twice, so we can
see
clearly that we need a new object like a DalcBase to encapsulate all these
common
methods in abstracted class, then we will inherit for each object his own
Dalc.
- The Code hide non reachable implementation,
ExecuteStoredProcedure is private and
it
lives inside the objects and you could not access this method to change its
behavior for
test.
- All objects have not a contract neither
interfaces or abstracted class it means , that all
Client classes use the
concrete class directly and that is obvious in invoice class use
the Customer
Class not CustomerBsase or ICustomer
figure 5, Sample of non
testable code.using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data;
namespace TestableApplication.NonTestable
{
public class Invoice
{
private int _invoiceNumber;
�.
Customer _customer;
public Customer Customer
{
get { return _customer; }
}
}
}
In this code we can see that the
Invoice object use the Customer object directly
not
interface or abstracted class, so we could not change the Customer dependency in
runtime , and we could not create a mock object that
inherit Customer because all private
methods and fields are inaccessible , so
now we end up with fixed dependency non
Changeable.
Effect
of the Non testable Application on the Unit test:
figure 6, Sample of non testable result. In this figure 6 we see that the same error
that happened in the Customer test in Debt
method, is
the same in all IssueInvoice Unit tests The Error is "InvalidOperationException
Customer Debt method, This Error is done in purpose as Example of
Customer Dependency
Error" Let us see the code
public class Customer{
public void Debt(decimal value, string description, int transactionID,DateTime transactionDate)
{
"844424930132190">throw new InvalidOperationException("Customer Debt method, This Error did in
purpose as Example of Customer Dependency Error");
Console.WriteLine("--- Debt Method was invoked from object {0} ", this);
Console.WriteLine("press enter to continue ....");
Console.ReadLine();
List<sqlparameter __designer:dtid="844424930132191"> parameters = new List<sqlparameter>();
SqlParameter param = new SqlParameter("@VALUE", SqlDbType.Money);
����..
}
</sqlparameter></sqlparameter>
This Exception was thrown inside
Customer Debit method, however it was happened in the full
cycle of Issue
invoice method test. Now let us see the Issue invoice Code
public Invoice IssueInvoice(string description, Customer customer, List"844424930132194"> items)
{
Console.WriteLine("--- IssueInvoice Method was invoked from object {0} ", this);
Console.WriteLine("press enter to continue ....");
Console.ReadLine();
List<sqlparameter> parameters = new List<sqlparameter>();
SqlParameter param = new SqlParameter("@DESCRIPTION", SqlDbType.NVarChar);
param.Value = description;
parameters.Add(param);
param = new SqlParameter("@CUS_ID", SqlDbType.Int);
param.Value = customer.ID; ;
parameters.Add(param);
DateTime issueDate = DateTime.Now;
param = new SqlParameter("@ISSUE_DATE", SqlDbType.DateTime);
param.Value = issueDate;
parameters.Add(param);
param = new SqlParameter("@DUE_DATE", SqlDbType.DateTime);
param.Value = issueDate.AddMonths(1); ;
parameters.Add(param);
object idObj = ExecuteStoredScalerProcedure("stp_add_invoice_header", parameters);
idObj = DateTime.Now.Millisecond;
int invoiceNumber = int.Parse(idObj.ToString());
parameters.Clear();
int seq = 0;
foreach (InvoiceItem item in items)
{
param = new SqlParameter("@INV_ID", SqlDbType.Int);
param.Value = invoiceNumber;
parameters.Add(param);
param = new SqlParameter("@QTY", SqlDbType.Int);
param.Value = item.Quantity;
parameters.Add(param);
param = new SqlParameter("@SEQ_NUM", SqlDbType.Int);
param.Value = ++seq;
parameters.Add(param);
ExecuteStoredScalerProcedure("stp_add_invoice_line", parameters);
}
Invoice newInvoice=new Invoice(invoiceNumber, description, customer, issueDate,Status.Issued, items);
customer.Debt(newInvoice.TotalAmount, newInvoice.Description, newInvoice.InvoiceNumber, newInvoice.IssueDate);
return newInvoice;
}
</sqlparameter></sqlparameter>
The Issueinvoice, has code to
create new Invoice instance , and to update the invoice
database and
to notify the customer with the invoice value, that he should pay.
So
if any thing wrong was happened in invoice database or customer object,
the
IssueInvoice method will fail, which is not good;
The good design that will test the database
updates separately in unit test, and the
customer Debt method in different unit
test and finally the issueInvoice method
separately
after all the code pass the unit test we
will run the integration test for all of them together .
And now, let us talk about, the good OOP , testable design
Characteristics of Testable
Applications:
- Is
separated in well designed layers , as above it is
one of the OOP design condition for
good encapsulation and maintainable application.
- The code
should be reusable, all methods should be in appropriated
objects, and the
common members should be in abstracted layer.
- The Code should expose internal dependencies as
interfaces.
All dependencies should be gathered in
specific objects, then we should extract
interface(s), and
let these dependency interfaces appear as properties in object (or
may be passed
as parameter in special constructor as we will explain soon) ,
With
this simple Idea we can inject the object through the properties (or
constructors)
with this dependency This idea is perfect but unfortunately it
violates the concept of
OOP, and it will open the hidden implementation to the
client, which is not good, and
the objects will not be secure at all.
- Objects should always implement
interfaces. That is the best practice,
Specially if you want to use the
external mock objects,
If you like to use abstracted class always
as contract (to write your own default
Implementation), you
may need to wrap all abstracted classes with the interface and,
let
all the client objects use interfaces not the concrete or even the abstracted
classes
- The objects methods and properties may be
virtual, It is not a mandatory it is just
recommended in special situations,
it will be useful In case of creating a mock object
from the class directly,
to create a quick custom mock object
But If all classes already implement
interfaces, there is no need to make all public
members virtual, that is already
side effect as delay
in performance for
virtual members rather than non virtual members
Steps of refactoring the old code to make it
testable code:
- Isolate all objects in different layers:
In
our example all code under Testable namespace is separated in 2 layers Business
component layer and Dalc layer
figure
7, Sample of testable code, separated in layers.
- Let all objects implement interfaces, and let
the client classes use these interfaces
figure
8, Sample of testable code, each object has an interface.
In this diagram, was extracted interfaces from classes and we implemented all
these
classes public partial class Invoice : "844424930132289">IInvoice
{
private ICustomer _customer;
public "844424930132290">ICustomer Customer
{
get { return _customer; }
}
private IInvoiceDalc _invoiceDalc;
public "844424930132291">IInvoiceDalc InvoiceDalc
{
get {
if (_invoiceDalc == null) _invoiceDalc = new InvoiceDalc(this);
return _invoiceDalc; }
set {
this._invoiceDalc = value;
}
}
private int _invoiceNumber;
public int InvoiceNumber
{
get { return _invoiceNumber; }
set { _invoiceNumber = value; }
}
We can see hear how we exposed the Customer as Interface and we did that
with the
Invoice Dalc as well
- All dependencies
should be gathered in specific objects
figure
9, Sample of testable code, Dalc objects as sample of ,encapsulation all dependencies in objects.
In this diagram, we encapsulated all database, methods to Dalc objects, then
we made
all these classes implemented interfaces and we
used these interfaces to expose the
Dalc objects in all objects that use data
base through Dalc. Using
Custom Mock objects:
To create a Custom Mock object, you should implement
the same interface of the
mocked object,and in your unit test, replace the
real object with the mock object,
You can implement the mock object with
different ways, may be you will leave all
methods as empty methods, or
Logging all call in log file, or generate and return data
for specific methods
and parameters, or save data in hash table,
There is a lot of use of mock objects, but in our example we made a simple log
to
Console just to indicate that the methods was
invoked,
We did that just to isolate the dependency
(like invoice Dalc, and customer
Dependency for Invoice Object) to focus on the
main object (Invoice object in our
sample)
figure
10, Sample of testable code, each object implements
interface.
figure 11, Sample of testable code, Mock
Objects should implement the same object interface.
Test testable code:
the
main technique fro unit test is, replacing dependencies, in run time.
Change the Dependency in Runtime:
The most
famous techniques, are:
Dependency Injection.
To
do that with the new design is not hard at all, but the problem is,
if
we add Set property to our
code, our classes will not be secure, and it will violate the
OOP design.
In
this case you may use #if DEBUG
directive for Dependency Injection
So what you should do, Expose the
dependency as interface, and create a Set
accessors (in Debug mode only ) to
change it in runtime public partial class Invoice : IInvoice
{
private ICustomer _customer;
public ICustomer Customer
{
get { return _customer; }
}
private IInvoiceDalc _invoiceDalc;
public IInvoiceDalc InvoiceDalc
{
get
{
if (_invoiceDalc == null) _invoiceDalc = new InvoiceDalc(this);
return _invoiceDalc;
}
"844424930132362">#if DEBUG
set
{
this._invoiceDalc = value;
}
#endif
}
the code above is a sample of dependency injection in debug mode
this code is a sample of using dependency
injection in unit test Invoice invoice = new Invoice().GetInvoice(12);
IInvoiceDalc invoiceDalc = new InvoiceDalcMock();
"844424930132369">invoice.InvoiceDalc = invoiceDalc;
int count1 = invoice.Items.Count;
invoice.Items.Add(new InvoiceItem(45, "ss", 55, 23.4m));
invoice.Update();
int count2 = invoice.Items.Count;
Assert.IsTrue(count2 == count1 + 1, "Item Count")
Constructor Injection
it is very easy to
create constructor (or any other method) injection in .Net 2 through
using
partial classes namespace TestableApplication.Testable
{
public partial class Invoice : IInvoice
{
#if DEBUG
public Invoice(int invoiceNumber, string description, ICustomer customer, InvoiceDalc invoiceDalc)
: this(invoiceNumber, description, customer)
{
this._invoiceDalc = invoiceDalc;
}
public Invoice(int invoiceNumber, string description, ICustomer customer, DateTime issueDate, Status status, List"844424930132376"> items, InvoiceDalc invoiceDalc)
: this(invoiceNumber, description, customer, issueDate, status, items)
{
this._invoiceDalc = invoiceDalc;
}
public Invoice(InvoiceDalc invoiceDalc)
{
}
#endif
}
}
we made the constructor injection (or any
other method injection)
in #if DEBUG
to be sure that this code is working only in
debug mode, so your class in release mode
will be secure, and encapsulate all
deatils and dependencies
partial class
to put all extra code in
separate file, and gather all these files in special folder and you
can exclude
it before release. Now we can test this object using this unit test code
[Test]
public void InvoiceTestUpdate_UsingDalcMock2()
{
IInvoiceDalc invoiceDalcMock = new InvoiceDalcMock();
Invoice invoice = new Invoice(123, "description test", customerMock, invoiceDalcMock);
int count1 = invoice.Items.Count;
invoice.Items.Add(new InvoiceItem(45, "ss", 55, 23.4m));
invoice.Update();
int count2 = invoice.Items.Count;
Assert.IsTrue(count2 == count1 + 1, "Item Count");
}
Let us see the effect of the new design and
with the small design guidelines for the
test result
figure 12 , The Testable application, will
fail only in faulty code, not the container code
That is a graet result
For
the first glance you can see the Update method of invoice and
Debt
method of customer Failed and all the other code is working fine
After while you can see, that the UpdateMethid using DalcMock is working fine,
it means the problem will be in Update Method of Dalc Method not of Invoice
Update
Method and if you write the InvoiceDalc unit test
your code will fail in Update method Conclusion
Before you create a unit test
, your code should be testable And to make your code
testable you
have to follow the guide lines of testable application.