Skip to content

Writing Unit Tests

After creating the test plan, you are now ready to start developing your unit tests.

Unit Test Classes

Declaring a unit test class is similar to declaring a regular class, but it includes the [TestClass] attribute. The use of the [TestClass] attribute is what differentiates a test class from just a regular class.

C#
namespace ADEV.UnitTesting
{
    [TestClass]
    public class PersonTests
    {

    }
}

Danger

If your unit test class is missing the [TestClass] attribute, the Test Explorer will not recognize the class as containing unit tests.

Test Class Naming Convention

Unit test class identifiers are named by using the class name of the class you are testing followed by the word "Tests". The test class above is testing a class named Person, because the test class identifier is PersonTests.

Unit Test Methods

You will code unit test methods within a class with the [TestClass] attribute. As a general guideline, you will write one unit test method per test case in your test plan.

Unit test methods are declared and defined like other methods. The use of the [TestMethod] attribute denotes the method is a unit test method.

C#
namespace ADEV.UnitTesting
{
    [TestClass]
    public class PersonTests
    {
        [TestMethod]
        public void TestMethod1()
        {

        }
    }
}

Danger

If your unit test method is missing the [TestMethod] attribute, the Test Explorer will not recognize the method as a unit tests method.

Test Method Naming Convention

Unit test method identifiers will follow this general naming format:

C#
[TestMethod]
public void {1}_{2}_{3}()
{

}

Where each part of the test method identifier means:

  1. The name of the method being tested.
  2. The scenario under which it's being tested.
  3. The expected behavior when the scenario is invoked.

Example:

C#
namespace ADEV.UnitTesting
{
    [TestClass]
    public class PersonTests
    {
        [TestMethod]
        public void Constructor_NameIsNull_ThrowsException()
        {

        }
    }
}

Test Method Organization

Test methods in your test class should be ordered by the unit being tested. You will not want to organize your tests based on the outcome being tested.

AAA Pattern

The AAA (Arrange, Act, Assert) pattern is a common pattern for writing unit test methods.

  • The Arrange part of the unit test defines test data and initializes an object used for the test.
  • The Act part of the test invokes the unit being tested under the conditions of the test case.
  • The Assert part of the test verifies that the unit being tested behaved as expected.

One of the most important aspects of testing, especially coding unit tests, is readability. The AAA pattern clearly separates what is being tested from the arrange and assert parts of the test. Separating the statements for these parts of the tests highlights the dependencies to invoke the method being tested, how it's being called and what you are trying to verify.

Assert Class

The Assert class contains a collection of methods to determine the result of a test. If the condition being tested is not met, an AssertFailedException is thrown.

Common Assert Class Method

  • AreEqual(Object, Object) - Tests whether the specified objects are equal and throws an exception if the two objects are not equal.
  • AreNotEqual(Object, Object) - Tests whether the specified values are unequal and throws an exception if the two values are equal.
  • IsTrue(Boolean) - Tests whether the specified condition is true and throws an exception if the condition is false.
  • IsFalse(Boolean) - Tests whether the specified condition is false and throws an exception if the condition is true.
  • IsNull(Object) - Tests whether the specified object is null and throws an exception if it is not.
  • IsNotNull(Object) - Tests whether the specified object is non-null and throws an exception if it is null.
  • AreSame(Object, Object) - Tests whether the specified objects both refer to the same object and throws an exception if the two inputs do not refer to the same object.
  • AreNotSame(Object, Object) - Tests whether the specified objects refer to different objects and throws an exception if the two inputs refer to the same object.
  • ThrowsException<T>() - Tests whether the code specified by the delegate action throws exact given exception of type T (and not of derived type) and throws AssertFailedException if code does not throws exception or throws exception of type other than T. This method returns a reference to the exception the exception if one is thrown.

Note

All Assert methods will throw an AssertFailedException when the condition is not met. The result of the test is a fail.

Reflection

Many of the tests you will develop will involve verifying the state of an object. State is stored in private fields in the class. To verify test cases where the result deals with object state, you will need to use a concept called Reflection. Reflection is the ability to retrieve data that is normally not accessible at run-time.

In the testing you did prior to this topic, you would have normally called an accessor method to verify changes to state. This is no longer satisfactory, as one of your unit testing goals is to isolate the unit your are testing. This means you do not want to invoke other units of the class while testing a specific unit.

PrivateObject Class

The PrivateObject class represents a public "version" of an object. The class contains methods to accesses private fields, methods, and properties.

To gain access to an object's private members, construct an instance of PrivateObject, initializing it with a reference to the object you wish access its private members.

C#
Person person = new Person(name, amountOfMoney);

PrivateObject target = new PrivateObject(person);

Use the following methods of the PrivateObject class:

  • GetField(String) : Object - Returns the value of the specified field.
  • SetField(String, Object) : void - Sets the specified field to the specified value.
  • Invoke(String, Object[]) : Object - Invokes the specified method. The Object[] represents the arguments. Returns the result of the method.
C#
PrivateObject target = new PrivateObject(person);

decimal actual = (decimal)target.GetField("amountOfMoney");

SetField Method

The SetField method is often used during the arrange part of a unit test when the state cannot be initialized to setup the test using the constructor of the class.

Good:

C#
[TestMethod]
public void Withdraw_AmountGreaterThanZero_UpdatesBalance()
{
    // Arrange
    BankAccount account;

    // Initializes balance to zero
    account = new BankAccount();

    PrivateObject target;
    target = new PrivateObject(account);

    // Initialize balance
    target.SetField("balance", 1000)

    // Act
    // ...

    // Assert
    // ...
}

Bad:

C#
[TestMethod]
public void Withdraw_AmountGreaterThanZero_UpdatesBalance()
{
    // Arrange
    BankAccount account;

    // Initializes balance to zero
    account = new BankAccount();

    account.Balance = 1000;

    // Act
    // ...

    // Assert
    // ...
}

The sample code above is not ideal because it is invoking another unit in the class, potentially affecting the results of the unit being tested.

Testing Abstract Class

Due to the inability of directly instantiating instances of an abstract class, you can't directly unit test them. Therefore, inherited properties and methods are tested in a derived class. Fields of a class' base class can be accessed using the PrivateType class.

PrivateType Class

The PrivateType class is used like PrivateObject, and can also be used for testing Static Classes.

Best Practices For Writing Tests

Before you really dive into developing your unit tests, keep the following best practices in mind:

  1. Avoid logic in your tests. This includes selections and loops.
  2. A unit is tested in isolation of other units in the class (with the exception of the constructor).
  3. Always test constructor methods first.
  4. Complete all the tests for a unit before moving onto another unit.
  5. Group tests in your test class by unit, not by test type.
  6. Keep the code in your unit test method simple.
  7. Try to order your tests in order of dependency (when known or possible).
  8. Prefer helper methods to setup and teardown.
  9. Avoid multiple acts.
  10. No unit is too insignificant to test.

Test Method Examples

The following unit test method examples will follow the sample test plan. Not every test case will be demonstrated here, as some of the test cases will produce similar unit test method implementation.

Further Reading