Tests
Running a program and reading what it prints tells you whether it works today. A test is a small piece of code that does the checking for you, so you can check again after every change without reading anything.
assert
Section titled “assert”assert checks that something is true. If it is, nothing happens. If it isn’t, the program stops and
shows what failed:
const score = -2assert score >= 0, "score must not be negative"print("never printed")tests.em:2:8: AssertionError: assertion failed: `score >= 0` assert score >= 0, "score must not be negative" ^^^^^^^^^^score must not be negativeThe message after the comma is optional, and is shown when the check fails. When the check is an ==,
Emerald shows both sides, as you’ll see below.
Test functions
Section titled “Test functions”A test is a function marked @test. It takes no parameters, gives back nothing, and uses assert to
check a result:
func clamp(value: Int, low: Int, high: Int): Int { return low if value < low return high if value > high return value}
@testfunc clamps_high_values() { assert clamp(15, 0, 10) == 10}
@testfunc clamps_low_values() { assert clamp(-3, 0, 10) == 0}
@testfunc keeps_values_in_range() { assert clamp(5, 0, 10) == 5}
print("the program is running")emerald test tests.em runs every test in the file, and reports:
3 tests passed.The print at the end didn’t run. emerald test uses the file’s functions and types, but not the
statements that make up the program, so testing never starts the program itself. emerald run still
runs the program as usual, and skips the tests.
Name each test for what it checks. When one fails, its name is the first thing you read.
When a test fails
Section titled “When a test fails”Suppose the last test expected the wrong answer:
func clamp(value: Int, low: Int, high: Int): Int { return low if value < low return high if value > high return value}
@testfunc clamps_high_values() { assert clamp(15, 0, 10) == 10}
@testfunc keeps_values_in_range() { assert clamp(5, 0, 10) == 6}tests.em:14:12: test `keeps_values_in_range` failed: AssertionError: assertion failed: `clamp(5, 0, 10) == 6` assert clamp(5, 0, 10) == 6 ^^^^^^^^^^^^^^^^^^^^Left was 5; right was 6.in `keeps_values_in_range`, called at tests.em:13:62 tests, 1 failed.The report names the test, shows the check, and gives both sides of the ==. Whether the mistake is in
the code or in the test, you can see it at once. A failing test doesn’t stop the others: every test
runs, and the last line counts them.
A test also fails if an error is raised inside it and not caught, such as an index outside a list.
Testing that an error is raised
Section titled “Testing that an error is raised”Sometimes the right result is an error. Call the code inside try, and put assert false after the
call, so the test fails if the call returns normally instead:
class InvalidScore extends Error {}
func check(score: Int) { raise InvalidScore("Score cannot be negative") if score < 0}
@testfunc rejects_negative_scores() { try { check(-5) assert false, "expected an InvalidScore error" } catch error: InvalidScore { assert error.message == "Score cannot be negative" }}1 test passed.Tests in a project
Section titled “Tests in a project”In a project of several files, emerald test main.em finds the tests in every file of the project, so
tests can live beside the code they check, or in files of their own.
Projects and namespaces explains how a project is put together.
What you’ve learned
Section titled “What you’ve learned”assert conditionstops with a report when the condition is false;, "message"explains why.- A function marked
@test, with no parameters and no result, is a test. emerald testruns every test, without running the program’s own statements.- A failing
==shows both sides, and every test runs even when one fails. try,assert false, andcatchtogether test that an error is raised.
Next, projects and namespaces: growing a program beyond one file.