testing
Check your code with assertions that name what went wrong, and learn how they fit with test blocks, test files and osl test.
import "std:testing"std:testing has the assertions: small functions that return true when something holds and throw an AssertionError that names both sides when it doesn't. Use them in test files, and in test ( ... ) blocks when a bare comparison doesn't say enough.
Your first assertion#
An assertion that passes returns true. One that fails throws, which stops the program:
import "std:testing"
log testing.equal(2 + 2, 4)
log testing.assert("hello".contains("ell"))
log testing.notEqual("a", "b")Change 4 to 5 and run it again. The failure says expected 4 to equal 5.
Seeing a failure#
A failed assertion ends the program, so to look at one without stopping, wrap it in testing.throws. It runs a function, checks that the function throws, and gives you the error:
import "std:testing"
auto failure = testing.throws(def() ( testing.equal(1, 2) ))
log failure.kind
log failure.messageTry testing.equal({a: [1, 2]}, {a: [1, 3]}). Arrays and objects compare by content, and the message prints both whole.
Adding your own message#
Every assertion takes an optional last argument that replaces the default message. Write what you were checking, so a failure reads like a sentence:
import "std:testing"
auto failure = testing.throws(def() ( testing.assert(1 > 2, "a ticket needs a positive price") ))
log failure.messageFloating-point numbers#
0.1 + 0.2 is not exactly 0.3 in any language that uses floating point. testing.near(actual, expected, tolerance) accepts a small difference:
import "std:testing"
log testing.near(0.1 + 0.2, 0.3, 0.0001)
auto failure = testing.throws(def() ( testing.near(1, 2, 0.1) ))
log failure.messageYou compare average([1, 2, 2]) to 1.667. Which check is right?
Null checks#
testing.null(value) and testing.notNull(value) check for null:
import "std:testing"
log testing.null(null)
log testing.notNull(3)
log testing.throws(def() ( testing.notNull(null) )).messageCode that should fail#
testing.throws is also how you check that a function rejects bad input. It fails when the function returns normally:
import "std:testing"
def positive(int n) int (
if n <= 0 throw "must be positive"
return n
)
auto failure = testing.throws(def() ( positive(-1) ))
log failure.message
auto missed = testing.throws(def() ( testing.throws(def() -> 1) ))
log missed.kindFor functions that return a result, you don't need it: check .isErr() and .unwrapErr().kind, as the errors lesson shows.
testing.fail(message) throws unconditionally. Put it on a branch that must never run.
In test blocks and test files#
osl test runs two kinds of test. A test ( ... ) block checks every boolean line, and the assertions here are for when you want your own message:
import "std:testing"
def average(number[] values) number -> values.sum() / values.len
test (
testing.equal(average([2, 4]), 3, "average of two values")
testing.near(average([1, 2, 2]), 1.667, 0.001)
)A file ending in .test.osl is an ordinary program that osl test runs and fails if it throws:
import "std:testing"
testing.equal(1 + 1, 2)
log "all good"Test blocks are left out of osl run and osl compile, so they can't change a shipped program. See the testing lesson for blocks, loops in tests and osl test itself.
What does osl test do with a .test.osl file?
Reject a bad age
checkAge should throw for a negative age. Use testing.throws to catch the error from checkAge(-5), and print its message.
age can't be negative
API reference#
10 members, generated from the std:testing source of OSL 9.3.0.
Functions
testing.assert(boolean condition, any ...message) booleanassert checks that condition is true. Throws AssertionError ("expected condition to be true" unless message is given) when it is false.
testing.equal(any actual, any expected, any ...message) booleanequal checks that actual equals expected, comparing arrays and objects by content. Throws AssertionError naming both values when they differ.
testing.fail(string message) booleanfail throws AssertionError with message, for branches a test should never reach.
testing.isNull(any value, any ...message) booleanDeprecated. Deprecated: use testing.null(value) instead; is* names are predicates that never throw.
isNull checks that value is null and throws AssertionError when it isn't.
testing.near(number actual, number expected, number tolerance, any ...message) booleannear checks that actual is within tolerance of expected, for comparing floating-point results. Throws AssertionError when it isn't (or either number is NaN), and ValueError when tolerance is negative.
testing.notEqual(any actual, any expected, any ...message) booleannotEqual checks that actual differs from expected, comparing arrays and objects by content. Throws AssertionError when they are equal.
testing.notNull(any value, any ...message) booleannotNull checks that value is not null. Throws AssertionError when it is.
testing.null(any value, any ...message) booleannull checks that value is null. Throws AssertionError naming the value when it isn't.
testing.panics(any fn, any ...message) booleanDeprecated. Deprecated: use testing.throws(fn) instead, which also returns the error.
panics checks that fn throws and returns true.
testing.throws(any fn, any ...message) errorthrows calls fn with no arguments, checks that it throws, and returns the error it threw so its kind and message can be checked too. Throws AssertionError when fn returns normally and TypeError when fn isn't a function.