Design for testability

Testing a web application can be challenging, there are a lot of tools to accomplish such a difficult task, replay browser capable of recording the input of the user, library as nUnit Asp.NET or WatiN, but in my opinion the best way to do unit testing on a site, is design the site itself to be testable. In the example attached with this article I show a very simple asp.net page to add two integer number together starting from a canonical version to a more testable form. Page Default.aspx shows a standard implementation where all the code to perform the validation and the addition is enclosed in the code behind file. This example is very simple but is significant because it follows a standard path for web.page, validate user input, perform business logic and show result or error messages to the user. What is wrong with this implementation is the difficulty to do a unit test to verify the correctness of both validation code and business logic. (I know that there is no need to test a simple addition, but think as if this example really contains complex business logic).

protected void Button1_Click(object sender, EventArgs e) {
   
if (ValidateInput()) {
      txtResult.Text = (
Int32.Parse(TextBox1.Text) + Int32.Parse(TextBox2.Text)).ToString();
   }
   
else {
      txtResult.Text = 
String.Empty;
   }
}
 
private Boolean ValidateInput() {
   
Int32 num;
   lblError.Text = 
String.Empty;
   
if (!Int32.TryParse(TextBox1.Text, out num)) {
      lblError.Text = 
“Primo addendo errato”;
   }
   
if (!Int32.TryParse(TextBox2.Text, out num)) {
      lblError.Text += 
“ Secondo addendo errato”;
   }
   
return String.IsNullOrEmpty(lblError.Text);
}

Sadly enough a lot of application are written in this way, the first thing to do to make this code testable, is moving away business logic code and verification code in a different assembly, and write some unit test against it. Page3.aspx is a possible result

protected void Button1_Click(object sender, EventArgs e) {
   
if (ValidateInput()) {
      TextBox3.Text = DomainLogic.
Domain2.Add(
         
Int32.Parse(TextBox1.Text),
         
Int32.Parse(TextBox2.Text)).ToString();
   }
   
else {
      TextBox3.Text = 
String.Empty;
   }
}
 
private Boolean ValidateInput() {
 
   lblError.Text = DomainLogic.
Domain2.ValidateInput(
      TextBox1.Text, TextBox2.Text);
   
return String.IsNullOrEmpty(lblError.Text);
}

Now you can see that code behind delegate the business logic and the validation to an external class contained into an external assembly. It is worth to notice that if you put code in app_code directory this code cannot be tested with xUnit frameworks. If you look at the example you can now see that the Domain Logic and validation code can be tested in Nunit project, the test file is called TestPAge3Logic.cs.

But this page is still not fully testable, this because some logic still lurks around code behind file. To achieve full testability really no logic should be included in code behind file, but in page3.aspx we have still embedded the logic of displaying the result to the user. The route to maximum testability lead to Model View Controller pattern, but it is not the subject of this post. If you look at page4.aspx you can see that code behind file really contains no logic.

public partial class Page4 : System.Web.UI.Page, DomainLogic.IPageAddition {
   
protected void Page_Load(object sender, EventArgs e) {
 
   }
 
   #region IPageAddition Members
 
   
public void SetError(string errorString) {
      lblError.Text = errorString;
      lblError.Visible = 
true;
   }
 
   
public void SetResult(string resultValue) {
      txtResult.Text = resultValue;
   }
 
   #endregion
 
   
protected void Button1_Click(object sender, EventArgs e) {
      DomainLogic.
Domain3.PerformAdd(TextBox1.Text, TextBox2.Text, this); 
   }
}

The key is to enclose the operation of the UI in an interface, and make the page implement this interface. In this example the logic of the page can be reduced to two operation: display the result and display the error messages to the user, so the interface contains only two methods: SetError() and SetResult(). The handler of the button now delegate all the work to the Domain3 object, that can be seen as the controller for the MVC pattern. Domain3 has a method called PerformAdd() that contains all the logic of the page, it validates input, calls business logic method, and finally drive the UI to show result or errors. As you can see PerformAdd() method accepts as parameters the raw input of the user as well as an instance of DomainLogic.IPageAddition object. The whole method is really simple

public static void PerformAdd(
   
String addendo1, 
   
String addendo2, 
   
IPageAddition page) {
 
   
Int32 add1, add2;
   
String errorString = String.Empty;
   
if (!Int32.TryParse(addendo1, out add1)) {
      errorString = 
“Primo addendo errato”;
   }
   
if (!Int32.TryParse(addendo2, out add2)) {
      errorString += 
“ Secondo addendo errato”;
   }
   
if (String.IsNullOrEmpty(errorString)) {
      page.SetResult((add1 + add2).ToString());
   }
   
else {
      page.SetResult(
“”);
      page.SetError(errorString); 
   }
}

As you can see this method is really simple and it is Fully Testable. Working this way is the path to Design For Testability, a page designed like this can be easily tested with mock object. Using RhinoMocks we can do a lot of useful tests: first of all we check that the controller does a good addition

[Test]
public void TestGood() {
   
MockRepository rep = new MockRepository();
   DomainLogic.
IPageAddition ipa = rep.CreateMock<DomainLogic.IPageAddition>();
   ipa.SetResult(
“15”);
   rep.ReplayAll();
   DomainLogic.
Domain3.PerformAdd(“12”, “3”, ipa);
   rep.VerifyAll();
}

This test verifies that passing “12” and “3” as user input the controller calls SetResult() method on the UI with the value “15”, but we can do more interesting tests.

[Test]
public void TestWrong2() {
    MockRepository rep = new MockRepository();
    DomainLogic.IPageAddition ipa = rep.CreateMock<DomainLogic.IPageAddition>();
    ipa.SetResult(“”);
    ipa.SetError(“”);
    LastCall.IgnoreArguments();
    rep.ReplayAll();
    DomainLogic.Domain3.PerformAdd(“12”, “32f”, ipa);
    rep.VerifyAll();
}

This test verifies that passing parameters “12” and “32f”, the controller calls SetError(), because the second addend is not an integer number, and also calls SetResult() with an empty string to clear a previous result if present. The key concept here is that the UI is abstracted with an interface, so it can be really simulated by a mock object and we can verify that the controller interacts with the UI correctly.

This little example shows that to really test every aspect of an application, the application itself must be designed to be testable. Moreover, an application designed in this way is really loosely coupled, if we needs to realize a windows version of the UI it is matter of seconds, because all the logic is detatched from the UI.

Alk.

sample11.zip

Caution with disposing session.Transaction

In nhibernate 1.2.0 beta versions a similar code path works well without problem

ISession session = SessionHelper.GetSession();
session.BeginTransaction();
//do something
session.Transaction.Rollback();
session.Transaction.Dispose();
session.BeginTransaction();
//Do something
session.Transaction.Rollback();

But above code is not legal since a disposed transaction cannot be used anymore. Until version 1.2.0 beta 3 the Dispose method of the Transaction Object did not mark transaction as disposed, so it can be reused. Starting from NHibernate 1.2.0 GA the above code will throw a NullReferenceException. The solution is not to dispose Session.Transaction if you plan to restart a new transaction again.

Alk.

When sql compare creates anger

I enabled session store in database in a test server, the application is ok and after some test I proceed to store the session in database even in the production site. I create a new session state database into the production server and I copy structure doing a synchronization with session state database of the test server. The application stops to work, whenever I ask for a page the server returns a blank page and nothing is showed on the browser. After investigating log files I checked that asp.net still search state database with the name of the database in temp server. The problem originates from the fact that all the stored procedures that are in session state server use three part name. Golden rule is that asp.net session store database and authentication database should be created using aspnet_regsql.exe and not with a synchronization procedure from an existing database.

Alk.

The advantage of Word2007 blogging

Some of my friends told me that word 2007 is not the right tool to blog, I really must admit that some aspects are not so good, such as code formatting. But one of my favorite option is the ability to include office 2007 graph and images. Really amazing.

Just one click and I can really change the aspect of everything in the graph, for technical blogs this is really a good thing.

And I’m only changing the predefined styles of graphs, I’m actually just copy and paste and click a couple of buttons

Really amazing.

Alk.