Skip to content
NorscodeNorscode

Writing tests

ExampleBy the Norscode project

A self-test that fails loudly — and why you should test the boundaries instead of the middle.

The simplest test is a program that throws an error if something is wrong. You do not need a framework to get started.

funksjon krev(navn: tekst, faktisk: boolsk) {
    hvis faktisk {
        skriv("test(" + navn + ", ok)")
    } ellers {
        skriv("test(" + navn + ", FEIL)")
        kast "testen feilet: " + navn
    }
}

funksjon summer(tall: liste_heltall) -> heltall {
    la sum = 0
    for verdi i tall {
        sum = sum + verdi
    }
    returner sum
}

funksjon start() -> heltall {
    krev("tom_liste", summer([]) == 0)
    krev("ett_tall", summer([5]) == 5)
    krev("flere_tall", summer([1, 2, 3]) == 6)
    krev("negative", summer([-2, 2]) == 0)
    skriv("alle testene passerte")
    returner 0
}

What happens here

krev writes one line per assertion and throws if something does not hold. That gives you two things for free: you see which assertions were run, and the run stops with an error code on the first one that breaks. A build script notices it without you needing to interpret the output.

That each assertion has a name is more important than it looks. When the test fails three months from now, the name is the only thing you have to go on. krev("negative", ...) tells you at once what is broken; krev("test4", ...) sends you back to reading the code.

Test the boundaries, not the middle

This is the most important advice in the whole recipe.

That summer([1, 2, 3]) gives 6 is almost never where the error sits. That test would pass even with fairly wrong code. The errors live elsewhere:

  • The empty list. What does the function return when there is nothing to sum? Many implementations crash or return something random.
  • One element. The boundary between nothing and something is where loops often miscount.
  • Negative numbers. Assumptions that everything is positive are common and unspoken.
  • Just above and below a threshold. If the code has a boundary at a hundred, test 99, 100 and 101.

Write those tests first. The ordinary cases you can add afterwards.

Let the test fail first

A test that has never failed has proven nothing. It may be written so that it passes no matter what.

The habit is simple: break the code on purpose — swap a plus for a minus — and see that the test actually speaks up. Then you fix it back. It takes ten seconds and separates a test that protects you from one that just looks reassuring.

Run them

nc run test_summer.no

The standard library also has std.test and std.unittest when you want more structure, and std.mock_http for testing code that talks to the network without actually doing so. But the pattern above — a function that throws, and a list of named assertions — carries surprisingly far.

Related

Back to the overview