Testing your code

A program that works on one example may still be wrong. Testing means checking it on purpose, on many inputs, especially the awkward ones.

assert

assert(condition) from <assert.h> does nothing if the condition is true. If it is false, it stops the program and prints the file, the line and the condition that failed.

#include <assert.h>
#include <stdio.h>

int maxOf(const int a[], int n) {
    int best = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > best) best = a[i];
    }
    return best;
}

int main(void) {
    int normal[] = {3, 9, 2};
    int one[] = {5};
    int negative[] = {-4, -2, -7};
    assert(maxOf(normal, 3) == 9);
    assert(maxOf(one, 1) == 5);
    assert(maxOf(negative, 3) == -2);
    printf("all tests passed\n");
    return 0;
}
Open in compiler

Each assert is one test: a call with a known right answer. The tests cover the normal case, an array of one element, and all-negative numbers.

Edge cases

Most bugs hide at the edges. For every function, ask what happens with:

  • zero, one and two items, and the largest input allowed;
  • negative numbers, and zero itself;
  • equal values, already sorted input and reversed input;
  • an empty string, or a line with spaces at the end.
#include <assert.h>
#include <stdio.h>

int maxOf(const int a[], int n) {
    int best = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] > best) best = a[i];
    }
    return best;
}

int main(void) {
    int normal[] = {3, 9, 2};
    int one[] = {5};
    int negative[] = {-4, -2, -7};
    assert(maxOf(normal, 3) == 9);
    assert(maxOf(one, 1) == 5);
    assert(maxOf(negative, 3) == -2);
    printf("all tests passed\n");
    return 0;
}
Open in compiler

This version starts best at 0, not at a[0]. The first two tests pass. The all-negative test fails: no number is bigger than 0, so it returns 0. Press Visualize to see the failed assert. A test that has never failed has never told you anything, so it is good practice to check that a test can fail.

Testing a whole program

For a program that reads input and prints output, keep each test as two files: the input and the expected output. Then let the shell check them all:

for t in tests/*.in; do
    ./app < "$t" > got.txt
    diff -q got.txt "${t%.in}.out" > /dev/null || echo "FAIL $t"
done

diff prints nothing when two files are the same, and shows the differing lines when they are not. This is exactly what the StepVioz judge and contest judges do with your submissions.

Asserts are for bugs, not for bad input. Compiling with -DNDEBUG removes every assert, so never put code that must run inside one, like assert(scanf("%d", &n) == 1).

Quick check

1. What does assert(x > 0) do when x is 5?
2. Which input is most likely to find a bug in a 'largest number' function?
3. diff got.txt expected.txt prints nothing. What does that mean?
My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.