The previous part came out a little bit moot – but before you accuse me of selling the sizzle and not the stake, let's look at some code.
Here goes a simple example everybody would easily catch up with. There's a blogger and a reader: the blogger blogs and the reader um, reads.
var blogger = new Blogger();
blogger.WritePost();
...
var reader = new Reader();
var post = reader.GetLatestPost(blogger.Blog);
reader.Read(post);
Unfortunately, in this backstabbing universe the flow could go through a lot of different APIs and the actual code could be less natural. Much less natural.
Some "real code" and tests.
Most likely, you will not have Blogger and Reader types (you may not even have a Post type). The code could look something like
BusinessBase.Authenticate(credentials);
...
if (BusinessBase.IsAuthenticated) {
if (Roles.IsUserInRole(Roles.Editor)) {
Post.SavePost(subject, text);
}
}
Such architecture, however, is arguably valid and one of unit tests for that matter might be
[Test]
public void SavePost_Ok()
{
BusinessBase.Authenticate(FakeEditorCredentials);
Guid postId = Post.SavePost("subject", "text");
Post post = Post.GetPost(postId);
Assert.IsNotNull(post, "Post wasn't saved.");
}
Is it a good test? Yes and no.
Good thing is that it is brief, has a clear name and only one assert. Good thing is that the assert has an error message and it's a useful "Post wasn't saved" message (compare to a mere "Post was null").
Bad thing is that it is twofold. It tests two actions: saving the post and reading it. If you see this guy failing you cannot instantly tell where's the problem.
So, if you're testing SavePost, you'd better off writing a separate method for retrieving the saved post from the database using something simple (LINQ to SQL?). Thus, a better (though a bit clumsy) test would look like
[Test]
public void SavePost_Ok()
{
BusinessBase.Authenticate(FakeEditorCredentials);
Guid postId = Post.SavePost("subject", "text");
Post post = ReadPostFromDb(postId);
Assert.IsNotNull(post, "Post wasn't saved.");
}
Can we improve further?
Absolutely! The goal is to make tests as brief and as clear as possible. Why? If a test fails, anyone in the team should be able to tell what went wrong. Instantly. Even a newcommer.
It's easy when you have a use-case flow in your tests.
First off, let's analyse the roles we need and introduce a helper to create them. This class should be put in a separate library so that any test library in the solution could use it. The "role based" types (Blogger and Reader in our case) would also live in that common library and have some general purpose logic, sort of Login and Logout etc.
public class Users
{
private static Blogger blogger;
private static Reader visitor;
public static void Create()
{
blogger = new Blogger();
visitor = new Reader();
}
public static Blogger Blogger { get { return blogger; } }
public static Reader Visitor { get { return visitor; } }
}
Another part of the job is creating extensions. In order to further increase readability, every test fixture would have an extension class (living in the same file but in a separate namespace). This class would have methods specific to this test fixture. Here goes an example (assuming Users.Create() gets called in SetUp):
[TestFixture]
public class PostTests
{
[Test]
public void SavePost_Ok()
{
Users.Blogger.Login();
var post = Users.Blogger.SavePost();
Assert.IsNotNull(post, "Post wasn't saved.");
}
}
namespace PostTestsExtensions
{
internal static class Extensions
{
internal static Post SavePost(this Blogger blogger)
{
Guid postId = Post.SavePost("subject", "text");
return ReadPostFromDb(postId);
}
private static Post ReadPostFromDb(Guid guid) { //... }
}
}
Bottom line.
Invest in readability.
You write a test only once, but after that people get to it all the time. However, there are only two reasons to get back: either to understand how to use the code that is being tested, or to fix the test if it started to fail.
Turgid tests do not add much value in either case.