Automated Testing
Takeaway: Project code should have at least a basic success indication check test and it should be able to run in an automated fashion. If you're not already writing automatic unit tests for your code (using a tool like NUnit, MbUnit, or XUnit.NET), I strongly encourage you to do so.
| Author's Note: Advancement in the art of unit testing over the past few years and evolving patterns and practices have had a huge impact on the way I code and the quality of the software I produce. One particular practice I've adopted is known as "Test-Driven Development" (TDD). A more recently refined incarnation is known as "Behavior-Driven Development" (BDD). You may find other patterns such as "Test-First Development" (which is subtly different than TDD), "Test-After Development" and many more. I strongly suggest you investigate these patterns and practices (especially TDD and BDD). |
Even if you decide not to pursue the automated unit testing path, you should still have at least one form of automatic test (integration test, smoke test, acceptance test) to confirm that the automated build procedure you put in place from the last section not only successfully compiled the code, but also verifies that the code can run according to expectations. For example, you might consider an ASP.NET Web application verification test successful if the code compiles and your test can execute the "login screen" (or some landing page that triggers the startup procedures of the Web application) without causing something like the ASP.NET "Yellow Screen of Death." A test that causes a login to occur and bring up the user's home page, which may involve a database round trip of some kind, would be even better. This would be a good smoke test to know that the application works and that it's able to connect to and retrieve data from the database.
Griffin Caprio
wrote about unit testing in the November/December 2004 issue of
CoDe Magazine.
Installing NUnit
To get started with unit testing, I recommend
NUnit (though
MBUnit and
XUnit.NET are very good too, and are definitely worth investigating). The most current stable release version is 2.4.8 at the time of this writing. You'll find several flavors including an MSI installer and a ZIP file. I usually recommend the ZIP file (
NUnit-2.4.8-net-2.0.zip) because I tend to update NUnit as soon as the next version comes out and it's tedious to install and uninstall MSIs all the time. Also, having the binaries in your "tools" folder makes it easier to maintain version compatibility among different projects.
After downloading the NUnit ZIP file, extract the contents of the
bin folder to
trunk\tools\NUnit. NUnit is now installed and ready to be used.
For more information on NUnit, see
Dan Jurgen's article in the November/December 2004
CoDe Magazine.
Adding the Test Project
In my opinion, it's generally a good idea to put your unit tests into a separate project, although there's some debate about that among the developer community. For now, keep them in a separate project—you can always move the tests into your core project(s) later.
Add a new class library project to your Visual Studio Solution and call it something recognizable (such as
YourSolution.SomeProject.Tests) so that it appears underneath the
YourSolution.SomeProject in Visual Studio's Solution Explorer. Next, add a reference from your
Tests project to the project that will be tested (e.g.,
YourSolution.SomeProject). Also, you'll want to add a reference to the
nunit.framework.dll file in your
trunk\tools\NUnit folder.
Adding the First Unit Test
To keep things simple, suppose you want to test a class called SimpleCalculator. Create a new class in your testing project called SimpleCalculatorTester. Add a
using directive for the NUnit.Framework namespace and then decorate the class with the
[TestFixture] attribute. Add a new
public, void method and decorate it with the
[Test] attribute. Call the first test
should_add_two_numbers_and_get_expected_result(). Your first test class should now look something like this:
using NUnit.Framework;
namespace CoDe.Repeatability.Core.Tests
{
[TestFixture]
public class SimpleCalculatorTester
{
[Test]
public void should_add_two_numbers_and_get_expected_result()
{
Assert.Fail("TODO: Fill in this test");
}
}
}
You may have noticed the peculiar test name. I recommend that you make tests very descriptive about what they're doing and what they hope to accomplish. Try to avoid specific numbers or values in the test name—but that's not a hard and fast rule; sometimes it makes sense. Tests can serve as a form of documentation and help capture real-world requirements about your system (whatever the actual software requirement documentation may say). Descriptive names can help bring new developers up to speed with the assumptions made about how the system should work, and can help prevent all developers from breaking previous assumptions accidentally.
Next, add some actual test code to exercise the tested code and assert your assumptions. Try to keep the tests very small and focused. Ideally, you want to test only
one thing per test. Try to avoid "epic" tests that test the database, a Web service, the file system, and seventeen different assumptions all in one test. These tests will be brittle and cause a lot of heartache as the code evolves.
To keep things simple, my SimpleCalculator class has an "Add" method that takes two operands and adds them together. I know, I know, this isn't very practical but please bear with these simple illustrations.
[Test]
public void should_add_2_and_2_and_get_4()
{
var calc = new SimpleCalculator();
Assert.That(
calc.Add(2, 2) == 4,
"2 plus 2 should equal 4");
}
Running the Unit Tests
You can execute these unit tests in a couple of ways. NUnit comes with both a GUI/Windows runner as well as a command-line runner. Both are quite capable and useful but I find that having to leave Visual Studio to run my tests adds friction to my development/test cycle. Two utilities make this problem go away:
TestDriven.NET and ReSharper's
UnitRunner. Both are commercial software that are well worth the cost. If you're not determined to run tests in the IDE, the NUnit GUI is free. It can also watch for changes to assemblies, so when you recompile your application it can run your tests automatically, helping you keep up to date with any test failures. The automatic change detection can be just as compelling a feature running the tests directly in the IDE.
Regardless of which runner you choose to use for your development, you should also add the console test runner to your automated build process so that the build is not considered a complete success until all the tests pass.
Adding Unit Testing to the Build
Open the
build.xml file again and add a new target called
test. This target is dependent upon the "build" target, so you must add the
depends="build" attribute to the
element. Inside the target, call the
task. The following code
shows an example of how the test target should look:
<target name="test"
depends="build">
<nunit2>
<formatter type="Plain"/>
<test>
<assemblies>
<include
name="${dir.src}/CoDe.Repeatability.Core.Tests/
bin/${project.config}/
CoDe.Repeatability.Core.Tests.dll"/>
</assemblies>
</test>
</nunit2>
</target>
At this point I would also recommend changing the default target for the build script to
test rather than
build.
Now, run
build.bat and your output should look something like this:
Target framework: Microsoft .NET Framework 3.5
Target(s) specified: test
build:
[exec] CopyFilesToOutputDirectory:
[exec] CoDe.Repeatability.Core ->
trunk\source\CoDe.Repeatability.Core\bin\Debug\
CoDe.Repeatability.Core.dll
[exec] CopyFilesToOutputDirectory:
[exec] CoDe.Repeatability.Core.Tests ->
trunk\source\CoDe.Repeatability.Core.Tests\bin\
Debug\CoDe.Repeatability.Core.Tests.dll
test:
[nunit2] Tests run: 1, Failures: 0, Not run: 0,
Time: 0.026 seconds
[nunit2]
[nunit2]
[nunit2]
BUILD SUCCEEDED
At this point, you have a fully automated build and test setup. You can continue adding tests to the
Tests project as needed. NUnit will automatically detect and run them as long as you remember to include the
[TestFixture] and
[Test] attributes on your test classes and methods respectively.
If you ever add another test project, remember that you will need to edit the list of
used by the
task in the
test target in your NAnt build script.