Aiming for Jarvis, Creating D.A.N.I.

Tutorial 13: Unit testing (or, How to catch your bugs before your users do)

If you've been following these tutorials, you've written some code. Maybe it even runs. But let's be honest—it’s probably held together by hope and the fact that you haven't touched the edge cases yet. That’s where testing comes in. It’s not just "best practice"; it’s the only thing standing between you and the inevitable midnight panic when something breaks in production.

The testing module: Trust, but verify

In Skink, we keep our tests separate from the main logic. If your file is math.skink, your tests go in math_test.skink. It keeps the production code clean and your test code focused.

// math_test.skink
module math_test

import "testing"

pub fn TestAddition() {
    // The assertion: does 2+2 actually equal 4?
    if 2 + 2 != 4 {
        Errorf("The universe is broken: addition failed")
    }
}

Running the gauntlet

Running your tests is a one-liner. The compiler goes out, hunts down any public function starting with Test, and runs them through the wringer.

SKINK_HOME=/path/to/skink-lang skink test math_test.skink

If everything passes, you get a clean bill of health. If something fails, Skink will let you know exactly which test tripped the wire. It’s like having a very blunt, very honest assistant.

A complete example

Don't just test the "happy path"—test the stuff that makes you nervous.

module calc_test

import "testing"

fn add(a: int, b: int) -> int {
    return a + b
}

pub fn TestAdd() {
    result := add(2, 3)
    if result != 5 {
        Errorf("Addition math is hard: expected 5, got {result}")
    }
}

pub fn TestAddNegative() {
    // Testing edge cases is where the real work happens
    if add(-1, -1) != -2 {
        Errorf("Negative numbers are people too!")
    }
}

A note on global state

If your code relies on global variables (and we've all been there, don't feel ashamed), make sure your tests don't pollute each other. Use Reset() to give every test a fresh start. It’s the closest thing to a "clean slate" you'll find in software.

Exercises

  1. The Max Check: Go back to the max function from Tutorial 4 and write a full test suite for it. Did you catch all the edge cases?
  2. The "Oops" Test: Write a test that you know will fail. It’s surprisingly satisfying to watch the compiler confirm that your code is, indeed, broken.
  3. The Global Reset: If you're feeling brave, write a test that modifies a global variable and use Reset() to make sure it doesn't leak into the next test.

Next

When you're ready to get fancy with dynamic rules and data-driven logic, we'll talk about Rulesets and dynamic rules.