Skip to content
Katabench
Try free
← All docs

Test Writing track: catching planted bugs

5 min read

Every other track asks you to write code and proves it with tests. The Test Writing track hands you working code and asks for the tests. Your suite passes only when it stays green on the correct implementation and goes red on enough of the buggy versions the grader keeps hidden. A test that asserts nothing, or that would pass no matter what the code did, earns nothing here.

What you are given

The brief shows the class under test in full, with its behavior spelled out. The starter is a Tests class with one example test and a TODO:

public class Tests
{
    public void A_multiple_of_three_is_Fizz()
    {
        Assert.Equal("Fizz", new FizzBuzz().Convert(9));
    }

    // TODO: add tests for plain numbers, multiples of 5, and multiples of 15.
}

The rules of the harness are short:

  • Each public, parameterless method is one test. It passes if it returns without throwing and fails when an assertion throws. Name methods for the behavior they prove; the name is what you see in the results.
  • The class under test is already compiled with your file. Construct it and call it directly. There is no framework attribute to add and no test runner to configure.
  • A small assertion library is in scope without a using:
Assert.True(condition);          Assert.False(condition);
Assert.Equal(expected, actual);  Assert.NotEqual(a, b);
Assert.Null(x);                  Assert.NotNull(x);
Assert.Throws<SomeException>(() => ...);

Assert.Throws returns the exception, so you can assert on its message when the brief specifies one. Assert.True and Assert.False accept an optional reason string that appears in the failure.

How the suite is graded

Run executes your suite against the correct implementation only. It is the fast answer to "do my tests even pass on correct code?" and it does not touch the mutants. Submit runs the suite twice over:

  1. Against the correct implementation. Every test must pass. A test that fails on correct code is a bug in the test, and it is shown by name with its assertion message. A test that fails on the correct code cannot catch anything: only tests that pass on the correct code count when the mutants run.
  2. Against the mutants. Each mutant is a copy of the implementation with one planted bug: a dropped branch, a swapped label, an off-by-one, a wrong operator. A mutant is caught when at least one of your passing tests fails against it. The puzzle states how many of its mutants you must catch, for example four of five.

The Tests tab shows a Mutants caught section with one numbered row per mutant, marked caught or survived. The rows are deliberately anonymous: a survived mutant says only that a planted bug slipped past your suite, never what the bug is, because describing it would hand you the test you were meant to write. Use the brief's behavior list to work out which branch or boundary your tests do not pin down.

The scorecard's guidance when the grade fails is the whole diagnosis: either a test fails on the correct code, so fix the test, or too many mutants survived, so add tests that a buggy implementation would fail.

Writing tests that catch mutants

The suites that pass this track share a few habits.

Isolate each branch with an input only that branch can explain. For a rule with four outcomes, pick a value for each outcome that no other outcome would produce. A test for "multiple of 15" using 15 catches a mutant that drops the combined rule; a test using 30 also does; a test using 9 never will.

Assert exact values. Assert.NotNull(result) passes for almost any implementation. The mutant that returns the wrong string still returns a string. Compare against the expected value.

Cover the boundaries. Mutants love the edges: zero, the first value that crosses a threshold, the last value before it, an empty input, the maximum. If the brief mentions a cutoff, write a test on each side of it.

Test the failure path. When the brief says invalid input throws, Assert.Throws<T> is the only test that proves it, and a mutant that swallows the exception survives without it.

Keep one behavior per test. A test with ten assertions stops at the first failure and tells you one thing. Ten tests tell you ten things, and their names in the result read as a specification.

Do not test the implementation. You cannot see the mutants, and the grader does not care how the code is written. Tests that exercise observable behavior through the public method are the only ones that transfer.

Reveal, reset, and retry

When a mutant keeps surviving and you cannot see why, Reveal solution loads a model suite into the editor. It is a study tool: read which inputs it chooses and why, Reset, and rebuild your own suite from that reasoning. Revealing does not mark the puzzle solved and follows the reveal allowance described in Plans, limits & billing.

Where to start

Warm-ups on this track pin down small pure functions: a clamp, a leap-year rule, a letter-grade boundary. The harder puzzles hand you stateful subjects such as a cache, a rate limiter, a retry executor, or a transfer ledger, where the interesting bugs live in ordering and state transitions rather than in a single return value.

Browse the C# unit testing exercises for the full catalog, Tracks and System Design for how the track compares to the others, and the article on mutation testing in C# for the idea the grader is built on.

Get new puzzles and .NET tips in your inbox

A short note when fresh kata land, plus the C# and performance tricks behind the grading. No spam, unsubscribe anytime.