1

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).

2 Answers 2

12

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.

4
  • Similar to the comment below. Using AAA, how would you test that myObj.CurrentDateTime isn't null? Would you create a new test method just for this?
    – user460667
    Apr 4, 2012 at 10:35
  • You wouldn't need to. 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.
    – StuperUser
    Apr 4, 2012 at 10:42
  • You are also using TestInitalize as ClassInitialize and making the order of the tests important. A test should not depend on another test. The way to make sure that myObj.CurrentDateTime isn't null for a 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. Still one assert per test.
    – StuperUser
    Apr 4, 2012 at 10:45
  • @user460667 I've updated a lot of useful information in this answer.
    – StuperUser
    Apr 4, 2012 at 10:53
1

you should call the method you are going to test in TestMethod. the TestInitialize function sets everything up to avoid code duplication: it is called each time before a TestMethod function so you start from a blank slate

public class void MyTest {
     MyObj myObj;
      [TestInitialize]
     public void MyTestInitialize()
     {
         this.myObj = new MyObj();

      }

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

     [TestMethod]
     public void IsInvalidName()
     {
         MyMethod(myObj, false);
         Assert.AreNotEqual("Hello World", this.myObj.Name);
     }



} 
4
  • OK, but would you also have an assert in both of those methods to test something like Assert.IsNotNull(myObj.CurrentDateTime)? This I would break the 'single assert per test' rule I have updated my example.
    – user460667
    Apr 4, 2012 at 10:25
  • A good point, worth bearing in mind that while TestInitialize runs once per test, ClassInitialize is run once per test run (of the TestClass), this means that the myObj used in each test is the same. This means that pass/fails of the second test isn't isolated from the behaviour of the first test. For something this simple it doesn't matter, but can cause massive issues if tests earlier in a run initialise a state or mutate the object in a way that later tests depend on.
    – StuperUser
    Apr 4, 2012 at 10:28
  • @user460667 you can add a string argument to the assert and display which test failed (or look at the stacktrace if there is any) Apr 4, 2012 at 10:36
  • +1 Each method is a separate test; the class contains a suite of tests. You should have at least as many asserts as you have tests in the test suite. Note that the one assert per test idea isn't hard and fast -- the idea is just to keep each test focussed on one thing.
    – Caleb
    Apr 4, 2012 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.