In Part 1 we discussed the merits of unit tests for the GUI portion of a Web application
and examined the basics of NUnitAsp, a tool designed to help with said tests. In this second and final part we'll look at
performing more advanced tasks with NUnitAsp.
Abstracting Common Functions to a Base Class
After calling Browser.GetPage(), the next thing is to check that the web application did not redirect to
another page (such as a global error page if the page load fails). We can do this with the following line of code:
However because every test we write will need this, let's abstract it to a common base page:
public class TestBase : WebFormTestCase
{
public TestBase()
{
} //end of con
public void CheckPage(string strExpectedUrl)
{
WebAssertion.AssertEquals("Expected page does not match actual page. " +
"Expected=[" + strExpectedUrl + "]. Actual=[" +
Browser.CurrentUrl.ToString() + "].",
Browser.CurrentUrl.ToString(),strExpectedUrl);
} //end of method
} //end of class
Notice how the base page now inherits from WebFormTestCase. By creating the method CheckPage() here,
it will be accessible to all tests. Armed with this base class you'd change your testing class's declaration from:
[TestFixture] public class TestBasic : WebFormTestCase
To:
[TestFixture] public class TestBasic : TestBase
Using Wrapper Pages
NUnitAsp can only directly access WebForms, not User Controls or the HttpContext (which contains
items like Session values). However, by wrapping these things with a helper WebForm, NUnitAsp can indirectly reference
them. For example, suppose we wanted to test a User Control, UCDropDown.ascx, that had a DropDownList and Label Web
control. Imagine that the User Control has the following methods and property:
public string Subject
public void SetValues(string[] astrValues)
public string GetSelectedValue()
We could create a WebForm, TestUCDropDown.aspx, put the User Control on it, and then add extra controls to
access the User Control's properties. That is, in order to create a unit test for the User Control's Subject
property I'll create the TestUCDropDown.aspx WebForm so that it has not only the UCDropDown.ascx User
Control on it, but in addition a TextBox, Label, and Button. This TextBox will be used to set the value of the User Control's
Subject property when the Button is clicked; the Label will be used to display the property's value.
Because NUnitAsp can easily manipulate the Web controls on a page, it can use these to indirectly
manipulate the User Control. The screenshot below shows the WebForm in action.
While this is great for testing, we obviously don't want these kinds of pages in production code. We can solve that problem
by enclosing the wrapper WebForm in #if DEBUG #endif directives so that it only appears in the DEBUG code, not
the release version. For example if you put #if DEBUG at the top of the codebehind and the #endif
at the bottom, then the entire page will only be available during debugging. The code below shows these concepts all put
together:
#if DEBUG
using ...
namespace WebMain
{
public class TestUCDropDown : System.Web.UI.Page
{
...
}
public string Subject
{
get {return this.LblSubject.Text;}
set{this.LblSubject.Text = value;}
}
}
#endif
Besides just testing User Controls, this technique can also handle HttpContext utilities classes and
manipulating the HttpContext object itself. For example, if you had a utility class to validate querystrings,
or if you needed to manipulate the Session and cache values, you could wrap these with a WebForm, access them through the
WebForm's controls, and then build unit tests with NUnitAsp. The Wrapper folder in the code download provides
several examples of this.
Ensuring that Each Test Resets to a Baseline
A basic principle in testing is that each test starts from the same baseline and that one test should not affect another.
For web applications this means that we don't want the Application, Session, or cache values generated from one test to
persist into another. You could use a wrapper page, as discussed with the previous technique, to call methods that reset
the state. Another solution, which is slower yet more thorough, is to reset IIS with the command line iisreset,
which will reset the state for the entire application. You can programmatically call this using the System.Diagnostics.Process
class, like so:
public void IISReset() {
string strFile = "IISRESET";
ProcessStartInfo psi = new ProcessStartInfo();
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.FileName = strFile;
Process p = System.Diagnostics.Process.Start(psi);
p.WaitForExit();
}
Besides resetting the cache and Session, you may also need to reset the Culture and UICulture of the current thread. For
Globalization, the current thread has a Culture that determines how its dates and currencies will be formatted, and a UICulture
that determines which resources files to use. If you're testing a global application, you'll want to ensure that you reset
these after each test by setting the current thread like so:
public void ResetCulture() {
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture= new System.Globalization.CultureInfo("en-US");
}
Depending on which tests need resetting, you could call these methods individually from the tests themselves, or you could
put them in the TearDown() method of either the class or the base class (for a global scope).
Creating simple Integration and Functional Tests
There are certainly legitimate GUI unit tests, such as testing for dynamically added controls or a configurable menu
User Control. However, because in an N-Tier application, the Presentation layer often integrates all the layers below it,
it is easy to associate GUI tests with Integration and Functional tests. By controlling the presentation layer, NUnitAsp
implicitly offers the ability to write simple Functional and Integration tests too.
An example of a functional test would be coordinating a user's actions across an entire business process flow.
An example of an integration test would simply be having the page load correctly, which confirms that the application can
pass through all the tiers to the database and back.
The downloadable code shows a sample in the Flow folder where PageA prompts you to select an
animal from the drop-down. This takes you to the confirmation PageB, which displays the selected animal.
You can write a test that mimics each step performed by the user, and therefore provides a basic functional and integration
test. Although this example is trivial, the concepts can be extended to include many complex business processes.
While these tests do not replace powerful (and expensive) third part tools, they are great for quick tests, and certainly
better than nothing if your team simple doesn't have the financial resources, or full-time testers.
Running your Tests in the Visual Studio Debugger
Every developer appreciates being able to step through and test code with the Visual Studio .NET debugger. However because
of how NUnitAsp accesses the web application, the AspTester objects will be null if you step
through with the debugger. One solution is to download the free TestDriven.Net add-in
for Visual Studio.Net. This add-in lets you enter into any method in debug mode by simply putting your cursor in that
method and right-clicking.
Summary
In this article we saw seen how NUnitAsp can provide Unit tests for GUI components, and even make simple Functional and
Integration tests. NUnitAsp obviously has a lot of good things going for it, including:
Is a free tool,
Integrates directly into NUnit, so it's already familiar for many developers,
Is intuitive to use because it simply provides a class library to control the WebForm and lets you use that
library programmatically in your favorite .Net language, as opposed to requiring you to learn a whole new testing application,
Can write tests quickly with just a few lines of code, and
Is open source and therefore can allow for improvements and extensions faster than third-party licensed tools.
Perhaps its biggest drawback to NUnitAsp is that it only tests the code behind classes in a Web application and not client-side
code, like JavaScript. It also requires properly formed HTML, not merely HTML that renders in Netscape or IE. You can
confirm the validity of your HTML pages with the online validator at http://validator.w3.org/.
NUnitAsp is a great tool, especially for GUI Unit tests. However it is not a substitute for the rest of the testing process,
or an excuse to write tightly coupled code that can only be tested from the GUI. You should be using other testing frameworks,
such as NUnit, to test the backend Business and Database layers. (For more information on NUnit be sure to read
Test Driven Development Using NUnit in C#.)
Because it's free, and quick to learn, NUnitAsp is a great program to add to your .Net toolkit.