C#: testing values and exceptions with a small harness
Check results, and verify that bad input throws the expected exception type.
using System;
class Program {
static int failures = 0;
static int Percent(string text) {
int value = int.Parse(text);
if (value < 0 || value > 100) {
throw new ArgumentOutOfRangeException("text", "percent out of range");
}
return value;
}
static void AssertEqual(string name, int expected, int actual) {
if (expected == actual) {
Console.WriteLine("PASS " + name);
} else {
failures++;
Console.WriteLine("FAIL " + name + ": expected " + expected + " but was " + actual);
}
}
static void AssertThrows<T>(string name, Action code) where T : Exception {
try {
code();
failures++;
Console.WriteLine("FAIL " + name + ": nothing was thrown");
} catch (T) {
Console.WriteLine("PASS " + name);
}
}
static void Main() {
AssertEqual("parses 42", 42, Percent("42"));
AssertEqual("parses 0", 0, Percent("0"));
AssertThrows<ArgumentOutOfRangeException>("rejects 101", () => Percent("101"));
AssertThrows<FormatException>("rejects text", () => Percent("abc"));
Console.WriteLine("failures: " + failures);
}
}PASS parses 42 PASS parses 0 PASS rejects 101 PASS rejects text failures: 0
Explanation
A unit test calls a small piece of code with chosen input and checks the output or the error. Frameworks such as xUnit, NUnit, and MSTest do this at scale. The core is small: a function that compares an expected value with an actual one and reports the result.
Testing error handling needs a way to say that code must throw. The AssertThrows method takes the code as an Action delegate and catches the exception type given as a generic argument. If the code throws nothing, or a different exception, the test fails. Good tests cover normal values, boundaries, and error cases.
Explore the dry run
using System; class Program { static int failures = 0; static int Percent(string text) { int value = int.Parse(text); if (value < 0 || value > 100) { throw new ArgumentOutOfRangeException("text", "percent out of range"); } return value; } static void AssertEqual(string name, int expected, int actual) { if (expected == actual) { Console.WriteLine("PASS " + name); } else { failures++; Console.WriteLine("FAIL " + name + ": expected " + expected + " but was " + actual); } } static void AssertThrows<T>(string name, Action code) where T : Exception { try { code(); failures++; Console.WriteLine("FAIL " + name + ": nothing was thrown"); } catch (T) { Console.WriteLine("PASS " + name); } } static void Main() { AssertEqual("parses 42", 42, Percent("42")); AssertEqual("parses 0", 0, Percent("0")); AssertThrows<ArgumentOutOfRangeException>("rejects 101", () => Percent("101")); AssertThrows<FormatException>("rejects text", () => Percent("abc")); Console.WriteLine("failures: " + failures); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| A generic helper: T is the exception type that must be thrown. | — | — | |
| A normal case. | — | PASS parses 42 | |
| 101 is out of range, so the expected exception is thrown and caught. | — | PASS parses 42 PASS parses 0 PASS rejects 101 | |
| int.Parse rejects text with a FormatException. | — | PASS parses 42 PASS parses 0 PASS rejects 101 PASS rejects text | |
| No check failed. | failures = 0 | PASS parses 42 PASS parses 0 PASS rejects 101 PASS rejects text failures: 0 |
Cost and common mistakes
Each check is O(1) plus the cost of the code under test.
- A catch for the wrong exception type lets a different error escape, which also fails the test, as it should.
- A test that cannot fail is worthless. Break the code on purpose once to confirm that the test notices.
- int.TryParse avoids exceptions for ordinary bad input in production code; exceptions are for exceptional cases.
Try it yourself
Add a boundary test for 100.
Reveal answer and reasoning
AssertEqual("parses 100", 100, Percent("100")); 100 is the largest accepted value.