Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing: Self-contained tests vs code duplication (DRY)

I'm making my first steps with unit testing and am unsure about two paradigms which seem to contradict themselves on unit tests, which is:

  • Every single unit test should be self-contained and not depend on others.
  • Don't repeat yourself.

To be more concrete, I've got an importer which I want to test. The Importer has a "Import" function, taking raw data (e.g. out of a CSV) and returning an object of a certain kind which also will be stored into a database through ORM (LinqToSQL in this case).

Now I want to test several things, e.g. that the returned object returned is not null, that it's mandatory fields are not null or empty and that it's attributes got the correct values. I wrote 3 unit tests for this. Should each test import and get the job or does this belong into a general setup-logic? On the other hand, believing this blog post, the latter would be a bad idea as far as my understanding goes. Also, wouldn't this violate the self-containment?

My class looks like this:

[TestFixture]
public class ImportJob
{
    private TransactionScope scope;
    private CsvImporter csvImporter;

    private readonly string[] row = { "" };

    public ImportJob()
    {
        CsvReader reader = new CsvReader(new StreamReader(
                    @"C:\SomePath\unit_test.csv", Encoding.Default),
                    false, ';');
        reader.MissingFieldAction = MissingFieldAction.ReplaceByEmpty;

        int fieldCount = reader.FieldCount;
        row = new string[fieldCount];

        reader.ReadNextRecord();
        reader.CopyCurrentRecordTo(row);
    }

    [SetUp]
    public void SetUp()
    {
        scope = new TransactionScope();
        csvImporter = new CsvImporter();
    }

    [TearDown]
    public void TearDown()
    {
        scope.Dispose();
    }

    [Test]
    public void ImportJob_IsNotNull()
    {
        Job j = csvImporter.ImportJob(row);

        Assert.IsNotNull(j);
    }

    [Test]
    public void ImportJob_MandatoryFields_AreNotNull()
    {
        Job j = csvImporter.ImportJob(row);

        Assert.IsNotNull(j.Customer);
        Assert.IsNotNull(j.DateCreated);
        Assert.IsNotNull(j.OrderNo);
    }

    [Test]
    public void ImportJob_MandatoryFields_AreValid()
    {
        Job j = csvImporter.ImportJob(row);
        Customer c = csvImporter.GetCustomer("01-01234567");

        Assert.AreEqual(j.Customer, c);
        Assert.That(j.DateCreated.Date == DateTime.Now.Date);
        Assert.That(j.OrderNo == row[(int)Csv.RechNmrPruef]);

    }

    // etc. ...
}

As can be seen, I'm doing the line Job j = csvImporter.ImportJob(row); in every unit test, as they should be self-contained. But this does violate the DRY principle and may possibly cause performance issues some day.

What's the best practice in this case?

like image 409
Michael Klement Avatar asked Jul 07 '09 06:07

Michael Klement


2 Answers

Your test classes are no different from usual classes, and should be treated as such: all good practices (DRY, code reuse, etc.) should apply there as well.

like image 67
Anton Gogolev Avatar answered Sep 21 '22 10:09

Anton Gogolev


That depends on how much of your scenario that's common to your test. In the blog post you refered to the main complaint was that the SetUp method did different setup for the three tests and that can't be considered best practise. In your case you've got the same setup for each test/scenario and then you should use a shared SetUp instead of duplicating the code in each test. If you later on find that there are more tests that does not share this setup or requires a different setup shared between a set of tests then refactor those test to a new test case class. You could also have shared setup methods that's not marked with [SetUp] but gets called in the beginning of each test that needs them:

[Test]
public void SomeTest()
{
   setupSomeSharedState();
   ...
}

A way of finding the right mix could be to start off without a SetUp method and when you find that you're duplicating code for test setup then refactor to a shared method.

like image 21
henrik Avatar answered Sep 22 '22 10:09

henrik