Writing Tests
Name each test for its scenario, write it in three parts (Arrange, Act, Assert), check results with MSTest's Assert methods, and run one test for many inputs with DataRow.
Apprentice · Unit Testing · C# · Updated
After this lesson you can
- Write a test in three parts, Arrange, Act and Assert, named for its scenario
- Check results with Assert methods such as AreEqual, IsTrue, IsFalse and IsNull
- Run one test for several inputs with [DataRow]
Key takeaways
- Name each test for its scenario, as
MethodName_WhenThisIsTrue_ExpectedResult, such asChangeChannel_WhenOff_KeepsTheChannel. - Arrange sets up the starting state, Act calls the one method being tested, and Assert checks both the return value and the object's properties.
Asserthas a method for each kind of check, such asAreEqual,IsTrue,IsFalseandIsNull. When a check fails, the test fails.- In
Assert.AreEqual, put the expected value first and the actual value second, and add a message that explains the requirement. [DataRow]runs one test once for each input, and each run counts as a test of its own.
Each row of your scenario table becomes one test method. Here's how to write them so they're easy to read, and so a failure tells you exactly what broke.
Name the scenario
A test's name should read like the scenario. This course names tests in three parts, separated by underscores:
MethodName_WhenThisIsTrue_ExpectedResult
ChannelUp_WhenOn_GoesUpOneChannel
ChannelUp_AtTheHighestChannel_GoesBackToTheLowest
ChangeChannel_WhenOff_KeepsTheChannel
Long names are fine. When one of a hundred tests fails, its name is the first thing you see, and ChangeChannel_WhenOff_KeepsTheChannel tells you far more than Test7.
Arrange, Act, Assert
Every test has three parts, in this order:
| Part | What it does |
|---|---|
| Arrange | Sets up the scenario's starting state |
| Act | Calls the one method being tested |
| Assert | Checks the result: the return value, and the object's properties |
[TestMethod]
public void ChannelUp_WhenOn_GoesUpOneChannel()
{
// Arrange
Television tv = new Television();
tv.TurnOn();
// Act
int newChannel = tv.ChannelUp();
// Assert
Assert.AreEqual(4, newChannel, "ChannelUp returns the new channel.");
Assert.AreEqual(4, tv.CurrentChannel, "The TV is now on the new channel.");
}
Notice the Assert part checks both the returned value and the property. A method that returns 4 but leaves the TV on channel 3 would pass the first check and fail the second.
Another scenario, starting from a different state:
[TestMethod]
public void ChannelUp_AtTheHighestChannel_GoesBackToTheLowest()
{
// Arrange
Television tv = new Television();
tv.TurnOn();
tv.ChangeChannel(15);
// Act
int newChannel = tv.ChannelUp();
// Assert
Assert.AreEqual(3, newChannel);
}
The Assert class
MSTest's Assert class has a method for each kind of check. If the check fails, the test fails, and the runner shows why.
| Method | Passes when |
|---|---|
Assert.AreEqual(expected, actual) |
The two values are equal |
Assert.AreNotEqual(notExpected, actual) |
The two values are different |
Assert.IsTrue(condition) |
The condition is true |
Assert.IsFalse(condition) |
The condition is false |
Assert.IsNull(value) |
The value is null |
Assert.IsNotNull(value) |
The value isn't null |
Assert.Contains(part, text) |
The text contains the part |
Two habits make failures easy to read:
- Expected first, actual second, in
AreEqual. The failure message labels them that way. - Add a message that explains the requirement. It's printed when the test fails.
Here's a test that expects the wrong thing, and what the runner says:
Assert.AreEqual(5, tv.CurrentChannel, "Because the TV started on channel 3.");
Failed ChannelUp_WhenOn_GoesUpOneChannel
Error Message:
Assert.AreEqual failed. Expected:<5>. Actual:<4>. 'expected' expression: '5', 'actual' expression: 'tv.CurrentChannel'. Because the TV started on channel 3.
Many inputs, one test: DataRow
Some scenarios are the same test with different inputs. Instead of copying the test, give it parameters and one [DataRow] per input:
[TestMethod]
[DataRow(3)]
[DataRow(7)]
[DataRow(15)]
public void ChangeChannel_ToAChannelThatExists_ChangesIt(int channel)
{
// Arrange
Television tv = new Television();
tv.TurnOn();
// Act
bool changed = tv.ChangeChannel(channel);
// Assert
Assert.IsTrue(changed);
Assert.AreEqual(channel, tv.CurrentChannel);
}
The runner runs it once per DataRow, and counts each run as a test of its own, so this method counts as three tests. A DataRow can hold several values, one per parameter: [DataRow("banana", 3)] for (string text, int expected).
Check yourself
3 questions to try on the lesson page.
So what
A good test reads like a sentence: its name says the scenario, Arrange sets it up, Act does one thing, and Assert checks what the requirement promised. Write them that way, and a failing test tells you what's wrong before you open the code.