Skip to content

Instantly share code, notes, and snippets.

@wilkie
Last active December 21, 2015 01:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wilkie/6227782 to your computer and use it in GitHub Desktop.
Save wilkie/6227782 to your computer and use it in GitHub Desktop.
Rust testing micro-framework based off of typical behavior testing frameworks that's a little rough around the edges, but a good example of the power of rust macros.
rustc test.rs && ./test

Syntax

describe!("module", {
  test!("function", {
    should!("do something", {
      must!(foo(1, 2) eq 5);
    })
  })
})

The describe macro establishes a context for a module with test macros defining each function. Tests are defined by should macros that should describe what behavior is being reflected by the function.

The must macros are assertions. The order should be: must!(computed eq expected);. For example, to test the function foo: must!(foo() eq 5); There are special considerations for floating point: must!(pi() near 3.1415); which tests that the given value is within 0.00001 to account for natural floating point error. You can specify the amount of tolerance: must!(pi() near 3.1415 within 0.001);

Output

Successful output will look like this:

output screenshot

A failure will look like this:

output screenshot

It will report the expected result and the given result. No frills.

// An example of using the framework
// licensed: public domain or cc0
use tester::*;
mod tester;
fn foo(a: float, b: float) -> float {
a + b
}
describe!("test", {
test!("foo", {
should!("add two numbers", {
let a = 5.0;
let b = 5.0;
must!(foo(a, b) eq 10.0);
})
should!("add a positive and negative number", {
let a = 5.0;
let b = -5.0;
must!(foo(a, b) eq 0.0);
})
should!("add a negative and positive number", {
let a = -5.0;
let b = 5.0;
must!(foo(a, b) eq 0.0);
})
should!("add two negative numbers", {
let a = -5.0;
let b = -5.0;
must!(foo(a, b) eq -10.0);
})
should!("add two fractions", {
let a = 1.25;
let b = 3.13;
must!(foo(a, b) near 4.38);
})
})
})
// rust testing micro-framework
// licensed: public domain or cc0
#[macro_escape];
pub use std::uint;
pub use std::io;
pub fn report_failure(expected: ~str, actual: ~str) {
io::print(fmt!("\n\x1b[31;1mFail\x1b[39;0m - %s vs %s", actual, expected));
}
trait TestInput {
fn compare(&self, b: &Self) -> bool;
fn compare_near(&self, b: &Self, tolerance: Self) -> bool;
fn fail(&self, b: Self);
}
macro_rules! impl_test_input(
($t:ty) => {
impl TestInput for $t {
pub fn compare(&self, b: &$t) -> bool {
*self == *b
}
pub fn compare_near(&self, b: &$t, _tolerance: $t) -> bool {
*self == *b
}
pub fn fail(&self, b: $t) {
report_failure(self.to_str(), b.to_str());
}
}
}
)
macro_rules! impl_test_float(
($t:ty) => {
impl TestInput for $t {
pub fn compare(&self, b: &$t) -> bool {
*self == *b
}
pub fn compare_near(&self, b: &$t, tolerance: $t) -> bool {
(*self > b - tolerance) && (*self < b + tolerance)
}
pub fn fail(&self, b: $t) {
report_failure(self.to_str(), b.to_str());
}
}
}
)
macro_rules! impl_test_char(
($t:ty) => {
impl TestInput for $t {
pub fn compare(&self, b: &$t) -> bool {
*self == *b
}
pub fn compare_near(&self, b: &$t, _tolerance: $t) -> bool {
*self == *b
}
pub fn fail(&self, b: $t) {
report_failure(fmt!("%c", *self), fmt!("%c", b))
}
}
}
)
impl_test_input!(~str)
impl_test_input!(u8)
impl_test_input!(u16)
impl_test_input!(u32)
impl_test_input!(u64)
impl_test_input!(uint)
impl_test_input!(i8)
impl_test_input!(i16)
impl_test_input!(i32)
impl_test_input!(i64)
impl_test_input!(int)
impl_test_input!(bool)
impl_test_char!(char)
impl_test_float!(float)
impl_test_float!(f32)
impl_test_float!(f64)
pub fn perform_test<T:TestInput>(a:T, b:T, negate: bool) -> bool {
if (a.compare(&b) ^ negate) {
true
}
else {
a.fail(b);
false
}
}
pub fn perform_near<T:TestInput>(a:T, b:T, tolerance:T, negate:bool) -> bool {
if (a.compare_near(&b, tolerance) ^ negate) {
true
}
else {
a.fail(b);
false
}
}
macro_rules! describe(
($prompt:expr, $func:expr) => (
fn main() {
let module_name = $prompt;
let mut _current_test = "<invalid>";
let mut _indent = 0;
let mut _tests:uint = 0;
let mut _fails:uint = 0;
let mut _successes:uint = 0;
io::println(module_name);
$func;
let assertions = _successes + _fails;
io::println(fmt!("\n%u tests %u assertions %u failures", _tests, assertions, _fails));
}
)
)
macro_rules! test(
($prompt:expr, $func:expr) => ({
_current_test = $prompt;
_indent += 1;
do uint::range_step(0, _indent, 1) |_| {
io::print(" ");
true
};
io::println(_current_test);
$func;
_indent -= 1;
})
)
macro_rules! must(
($a:expr eq $b:expr) => (
if (perform_test($a, $b, false)) { _successes += 1; } else { _failure = true; _fails += 1; }
);
($a:expr near $b:expr) => (
if (perform_near($a, $b, 0.00001, false)) { _successes += 1; } else { _failure = true; _fails += 1; }
);
($a:expr near $b:expr within $t:expr) => (
if (perform_near($a, $b, $t, false)) { _successes += 1; } else { _failure = true; _fails += 1; }
);
)
macro_rules! wont(
($a:expr eq $b:expr) => (
if (perform_test($a, $b, true)) { _successes += 1; } else { _failure = true; _fails += 1; }
);
($a:expr near $b:expr) => (
if (perform_near($a, $b, 0.00001, true)) { _successes += 1; } else { _failure = true; _fails += 1; }
);
($a:expr near $b:expr within $t:expr) => (
if (perform_near($a, $b, $t, true)) { _successes += 1; } else { _failure = true; _fails += 1; }
);
)
macro_rules! should(
($prompt:expr, $func:expr) => ({
let mut _failure = false;
_tests += 1;
_indent += 1;
do uint::range_step(0, _indent, 1) |_| {
io::print(" ");
true
};
io::print("should ");
io::print($prompt);
$func;
if (!_failure) {
io::print(" - ");
io::println("\x1b[32;1mPass\x1b[39;0m");
}
else {
io::println("");
}
_indent -= 1;
})
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment