In today's article I'm going to introduce unit testing, and show how basic unit tests can be written and run from within SharpDevelop. This is just one way of doing unit testing; Visual Studio's integrated unit testing suite is a pretty good alternative, or else you could use NUnit separately from your IDE; but let's not go there at this stage.
As usual, let's avoid lengthy overviews and learn about unit testing by doing it. To start off, create a new SharpDevelop project. Then, right click on the project in Solution Explorer and select Add -> New Item...; from there, add a new class called EmailAddress:
Set up the class such that it can be used to store an email address provided in its constructor:
public class EmailAddress
{
private String emailAddress;
public EmailAddress(String emailAddress)
{
this.emailAddress = emailAddress;
}
}
In the EmailAddress class, add a simple method to check whether the email address it contains is a valid one:
public bool IsValid()
{
if (this.emailAddress.Contains("@"))
return true;
else
return false;
}
Let's add also add some code in Main() that will allow us to test this manually:
public static void Main(string[] args)
{
Console.WriteLine("Enter your email address: ");
String emailStr = Console.ReadLine();
EmailAddress emailAddress = new EmailAddress(emailStr);
if (emailAddress.IsValid())
Console.WriteLine("Congratulations, your email is valid!");
else
Console.WriteLine("Oops, that's not a valid email address!");
Console.ReadKey(true);
}
Now, we could run the program several times, each time giving it a different email address to test it, but this would be tedious. Instead, we can write unit tests. These are normally kept in a separate project, but to keep things simple, we'll use the same project. Before we proceed, though, you'll need to download and install NUnit. When you're done, you should be able to find nunit.framework.dll in NUnit's bin\framework folder:
Back in SharpDevelop, from Solution Explorer, right click on the project and select Add Reference...:
In the window that appears, select the ".NET Assembly Browser" tab, hit the "Browse..." button, and locate the nunit.framework.dll file as above. This will allow you to use NUnit's functionality directly from within SharpDevelop.
Add a new class called EmailAddressTest and replace the default contents of the file with the following code:
using System;
using NUnit.Framework;
namespace CsSdUnitTesting
{
[TestFixture]
public class EmailAddressTest
{
[Test]
public void EmailAddressTest_Simple_Valid()
{
EmailAddress email = new EmailAddress("test@example.com");
bool isValid = email.IsValid();
Assert.IsTrue(isValid);
}
}
}
Note how, on the second line, we are using the NUnit.Framework namespace, which comes from the nunit.framework.dll to which we have just added a reference. This allows us to mark classes containing tests with the TestFixture attribute, and test methods with the Test attribute.
The method you see above is an example of a unit test: in it, we create an instance of our EmailAddress class, and test a particular method (in this case the IsValid() method). We hardcode an input, evaluate the method, and define an expected output using one of the many methods in the Assert class.
Let's actually run this unit test. Open the Unit Tests window by going to the View menu and then selecting Tools -> Unit Tests:
Once the Unit Tests window opens up on the right, you can just hit one of the Play buttons to run your tests:
Your unit tests should capture not only valid data, but also cases that are meant to fail. For example, this unit test catches invalid email addresses that are missing the '@' character:
[Test]
public void EmailAddressTest_NoAt_Invalid()
{
EmailAddress email = new EmailAddress("hello");
bool isValid = email.IsValid();
Assert.IsFalse(isValid);
}
When you run this test, you should get a green light just as before, showing that the test has passed.
Now, let's try a different test:
[Test]
public void EmailAddressTest_NoDomain_Invalid()
{
EmailAddress email = new EmailAddress("test@");
bool isValid = email.IsValid();
Assert.IsFalse(isValid);
}
This test is supposed to catch cases where there is no domain, and we expect it to be invalid. So we run the tests again:
Crap. Our test failed, so we need to go back and fix the code. When logic is complicated, you can set breakpoints in the tests and run the tests within the debugger, saving you from having to actually run the program itself (which may take time for more complex applications).
There are many ways to verify an email address, including using the MailAddress class or using a regular expression. In my case I wrote a simple regular expression which does not cover every case but is enough for what we need. I haven't covered regular expressions, but don't worry, just use this code for IsValid():
public bool IsValid()
{
if (Regex.IsMatch(emailAddress, @"\w+\@\w+(\.\w+)+"))
return true;
else
return false;
}
You will also need to put this at the top for it to work:
using System.Text.RegularExpressions;
If you run the tests now, they should all pass. If someone happens to change the regular expression in the future and breaks something, re-running the tests should allow you to detect the issue. Re-running a test to see whether something broke is called a regression test, and is a great way to detect problems early and fix them before they make it into a release.
In this article you have seen how unit tests are used, with the example of validating an email address. There is a lot more to say about unit tests, but this should suffice as an introduction. I will mention a few points however:
- Unit tests allow you to test a single unit of functionality, often a method or property.
- As such, they work great with methods that have a clear input and output (such as a method which takes an email address in String format and returns a boolean indicating whether it is valid or not).
- Unit tests are not easy to write for methods which are not public, which are void, or which do not take parameters. In Visual Studio there is a way to get around this, but in NUnit you have to refactor your code.
- Unit tests are really tricky to write for methods which rely on external resources such as files, databases, or remote network locations. To make them work in such scenarios, techniques such as dependency injection and mocking come into play.
- Unit tests won't make your code bug-free, especially if you don't write unit tests to handle the majority of possible inputs. They won't solve all your problems, so use them only when they are worth the effort.
- Some people like to write unit tests before the actual development code - this is called Test Driven Development (TDD).
That's all, folks! Stick around, as articles are posted here regularly. :)
Congratulations, you were very clear and objective.
ReplyDeleteThis article was helpful for me when I found it almost 6 years later in August 2019.
ReplyDelete