Let’s polish tests until they shine – part 1

If you are doing Test Driven Development, your tests will be your requirement specification. So your test should be readable. We need to maintain the quality of the tests as same as the quality of the business logic. In this article I am going to take a very simple test and refactor tiny little things that helps to to make it more expressive.

This is the test case that i have used for my last blog post. It is used to test the case sensitivity of a database query. This is how i wrote it first using NUnit and Fluent Assertions.

[Test]
public void ActivationKeysCaseSensitivityTest()
{
    var db = new ApplicationDbContext();
    db.Softwares.RemoveRange(db.Softwares.ToList());

    db.Softwares.AddRange(new List<Software>
    {
        new Software { Name = "Word", ActivationKey = "AcaT" },
        new Software { Name = "Excel", ActivationKey = "acat" }
    });
    db.SaveChanges();

    db.Softwares.Count(s => s.ActivationsKey == "acat").Should().Be(1);
}

So what wrong with this test. Let’s see what this test is going to tell us.

  1. It  clear all the data in the table.
  2. Save two software in the database
  3. Count of the software that ActivationKey equal “acat” should be one.

If we take the assertion of this test it slightly hurt the readability of the test.  Our requirement is about returning matching software, it is not about a count.  So i changed the assertion like below.

db.Softwares.Where(s => s.ActivationsKey == "acat")
    .Should().Contain(s => s.Name.Equals("Excel"))
    .And.NotContain(s => s.Name.Equals("Word"));

Now our assertion is better it specifies exactly which of the products should be included, but it looks more complex. Now assertion talking about a specific software but to figure out which software reader needs to look through the list of software that initialized at the top of the test. Still it did not give direct and clear idea to the reader.

So I refactor more to increase the readability and have changed the test method name also.

[Test]
public void SearchOnActivationKeyShouldBeCaseSensitive()
{
    var db = new ApplicationDbContext();
    db.Softwares.RemoveRange(db.Softwares.ToList());

    var word = new Software { Name = "Word", ActivationKey = "AcaT" };
    var excel = new Software { Name = "Excel", ActivationKey = "acat" };
    db.Softwares.AddRange(new List<Software> { word, excel });
    db.SaveChanges();

    db.Softwares.Where(s => s.ActivationKey == "acat")
        .Should().Contain(excel)
        .And.NotContain(word);
}

Now it tells there are two software called word and excel and after the database query the result list should contain excel but not contain word.

In my arrange part I initialized two software. I only care about the activation key. but I am not interested about the other properties of software. So i used AutoFixture which is minimize the ‘Arrange’ phase of my unit tests.

[Test]
public void SearchOnActivationKeyShouldBeCaseSensitive()
{
    var db = new ApplicationDbContext();
    db.Softwares.RemoveRange(db.Softwares.ToList());

    var fixture = new Fixture();
    var word = fixture.Build<Software>().With(s => s.ActivationKey, "AcaT").Create();
    var excel = fixture.Build<Software>().With(s => s.ActivationKey, "acat").Create();
    db.Softwares.AddRange(new List<Software> { word, excel });
    db.SaveChanges();

    db.Softwares.Where(s => s.ActivationKey == "acat")
        .Should().Contain(excel)
        .And.NotContain(word);
}

To validate a logic single test will not be enough. In order to support that i did few more refactoring. I extracted ApplicationDbContext and Fixture initialization as instance variables and moved database clearing logic in to SetUp method which is executed before every Test in current TestFixture.

[TestFixture]
public class SoftwareTests
{
    readonly Fixture _fixture = new Fixture();
    readonly ApplicationDbContext _db = new ApplicationDbContext();

    [SetUp]
    public void SoftwareTestSetup()
    {
        _db.Clear();
    }

    [Test]
    public void SearchOnActivationKeyShouldBeCaseSensitive()
    {
        var word = _fixture.Build<Software>().With(s => s.ActivationKey, "AcaT").Create();
        var excel = _fixture.Build<Software>().With(s => s.ActivationKey, "acat").Create();

        _db.Softwares.AddRange(new List<Software> { word, excel });
        _db.SaveChanges();

        _db.Softwares.Where(s => s.ActivationKey == "acat").ToList()
            .Should().Contain(excel)
            .And.NotContain(word);
    }
}

public static class TestHelperExtensions
{
    public static void Clear(this ApplicationDbContext dbContext)
    {
        dbContext.Softwares.RemoveRange(dbContext.Softwares.ToList());
    }
}

Now I can use TestCase attribute which is provided by the NUnit, for try out different inputs.

[TestCase("AcaT", "acat")]
[TestCase("acat", "AcaT")]
public void ActivationKeysCaseSensitivityTest(string wordActivationKey,string excelActivationKey)
{
    var word = _fixture.Build<Software>().With(s => s.ActivationKey, wordActivationKey).Create();
    var excel = _fixture.Build<Software>().With(s => s.ActivationKey, excelActivationKey).Create();

    _db.Softwares.AddRange(new List<Software> { word, excel });
    _db.SaveChanges();

    _db.Softwares.Where(s => s.ActivationKey == excelActivationKey)
        .Should().Contain(excel)
        .And.NotContain(word);
}

That was my final output but the possibilities are endless and there is no predefined rules for this kind of refactoring. If we take a full step back, this is a another variation of the same test.

[Test]
public void ActivationKeysCaseSensitivityTest()
{
    var software = _fixture.Build<Software>().Create();

    _db.Softwares.Add(software);
    _db.SaveChanges();

    _db.Softwares.Where(s => s.ActivationKey == software.ActivationKey).Should().Contain(software);
    _db.Softwares.Where(s => s.ActivationKey == software.ActivationKey.ToUpper()).Should().NotContain(software);
}

If you compare my initial test and the refactored one. you can see the improvement of the readability by changing the little pieces of the code. I hope this have inspired to you to write more expressive and readable tests.

Advertisements

One thought on “Let’s polish tests until they shine – part 1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s