C# Unit Testing – Should a Separate Class Be Used Per Test

ctestingunit testing

Taking the following simple method, how would you suggest I write a unit test for it (I am using MSTest however concepts are similar in other tools).

public void MyMethod(MyObject myObj, bool validInput)
{    
 if(!validInput)
 {
  // Do nothing
 }
 else
 {
  // Update the object
  myObj.CurrentDateTime = DateTime.Now;
  myObj.Name = "Hello World";
 }
}

If I try and follow the rule of one assert per test, my logic would be that I should have a Class Initialise method which executes the method and then individual tests which check each property on myobj.

public class void MyTest
{
    MyObj myObj;

    [TestInitialize]
    public void MyTestInitialize()
    {
        this.myObj = new MyObj();
        MyMethod(myObj, true);
    }

    [TestMethod]
    public void IsValidName()
    {
        Assert.AreEqual("Hello World", this.myObj.Name);
    }

    [TestMethod]
    public void IsDateNotNull()
    {
        Assert.IsNotNull(this.myObj.CurrentDateTime);
    }
}

Where I am confused is around the TestInitialize. If I execute the method under TestInitialize, I would need separate classes per variation of parameter inputs. Is this correct? This would leave me with a huge number of files in my project (unless I have multiple classes per file).

Best Answer

You need to look at the "Arrange Act Assert" pattern.

For each test you:

Arrange the Code Under Test and any dependent variables.
Act by calling the method on the Code Under Test.
Assert what you need to ensure the test passes (this should be one thing per test).

In this case you will use:

[TestMethod]
public void MyMethod_CalledWithValidName_HasHelloWorldName()
{
    //Arrange
    bool isValid = true;
    MyObj objectParameter = new MyObj();
    ClassUnderTest objectUnderTest = new ClassUnderTest();

    //Act
    objectUnderTest.MyMethod(objectParameter, isValid);

    //Assert
    string expectedName = "Hello World";
    string actualName = this.myObj.Name;
    Assert.AreEqual(expectedName, actualName);
}

This allows you to see exactly what you are testing and by copying this test and changing the variables, quickly write additional tests for other conditions.

Notice the MethodUnderTest_Condition_ExpectedResult naming pattern for the test.


To actually answer your question:

If I execute the method under TestInitialize, I would need seperate classes per variation of parameter inputs.

Why? TestInitialize is to set up the environment needed for every test in that class and run before each test. In my experience these tend to be relatively small since different methods you are testing have different dependencies and should be isolated enough that you won't need to set up too much the same for each test. If you need to vary the parameter inputs for each test, then it doesn't belong in TestInitialize, it belongs in the Arrange part of your test.

In this class you have a test against a method on an unspecified class (MyMethod), and a test against myObj. These tests should be in different classes.

You are confusing TestInitalize (setup before each test) as ClassInitialize (setup before all tests in that class).

ClassInitialize can be abused and can make the order of the tests important. A test should not depend on another test. You can end up with passes/fails based on the order of test execution, which in some cases can't be specified, and cause breaks when tests are updated or removed or run in isolation.

The way to make sure that myObj.CurrentDateTime isn't null when testing your unspecified class is to set it, you won't be asserting anything on that myObj in those tests. You can assert against them in the test class for myObj. One test class per object with one assert per test.

It also appears that MyMethod is static. This breaks test isolation by meaning that when the code under test runs with works with other production code. This turns this test into an integration test (because the code under test and the static class are both being run during the test); this is bad because a failure in the static class can cause failures in the code under test in methods which are perfectly functional.

I've covered a lot of different aspects of testing in this answer based on a lot of different principles. Grab the fantastic The Art Of Unit Testing and read it cover to cover. It's a great, easy read and is one of the books I'd grab (along with Code Complete) if the office caught fire.

Related Topic