Step 1 of 6
Your first test
So far you've checked your programs by running them and reading the output. That works once. A test is a small function that checks some code automatically, so you can rerun every check in a second, every time anything changes. Professional Rust code is full of them, and cargo test runs them all.
Checks you already know: assert!
The assert macros panic when a check fails, and do nothing when it passes:
assert!(condition)panics if the condition isfalse.assert_eq!(left, right)panics if the two values are not equal, and shows both.assert_ne!(left, right)panics if they are equal.
fn ticket_price(age: u32) -> u32 {
if age < 12 { 500 } else { 1200 }
}
fn main() {
assert_eq!(ticket_price(8), 500);
assert_eq!(ticket_price(40), 1200);
assert!(ticket_price(11) < ticket_price(12));
assert_ne!(ticket_price(0), 0);
println!("all checks passed");
}
all checks passed
Turning checks into tests
Checks in main are clumsy: the first failure stops everything, and they run every time the program runs. Real tests live in their own functions, marked with the #[test] attribute:
fn ticket_price(age: u32) -> u32 {
if age < 12 { 500 } else { 1200 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn children_pay_500() {
assert_eq!(ticket_price(8), 500);
}
#[test]
fn adults_pay_1200() {
assert_eq!(ticket_price(40), 1200);
}
}
$ cargo test
Compiling tickets v0.1.0 (/home/you/tickets)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.42s
Running unittests src/main.rs (target/debug/deps/tickets-1f2e3d4c5b6a7980)
running 2 tests
test tests::adults_pay_1200 ... ok
test tests::children_pay_500 ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Piece by piece:
#[test]marks a function as a test. A test passes if it returns normally and fails if it panics, which is exactly what a failingassert!does.#[cfg(test)]means "compile this module only when testing".cargo buildandcargo runleave it out, so test code never ends up in your program.mod testsis a child module, so it starts withuse super::*;to bring the parent's functions (including private ones) into scope.cargo testcompiles your code in test mode. Instead of running yourmain, Rust builds a test runner: a program with its ownmainthat calls every#[test]function, each one separately, and reports which passed. One failing test doesn't stop the others.- Name tests like sentences about behavior (
children_pay_500): when one fails, the name tells you what broke.
Test exercises on this site
In test exercises you write #[test] functions, and Check runs cargo test. Your code doesn't need a fn main, because the test runner brings its own. Then the site does something extra: it plants bugs, called mutants, in the code you're testing (one at a time) and runs your tests again. Each mutant must make at least one of your tests fail. A test suite that passes no matter what the code does isn't protecting anything.
Common mistakes
- Forgetting
#[test]: the function is just a helper and never runs.cargo testreportsrunning 0 tests. - Forgetting
use super::*;:error[E0425]: cannot find function `ticket_price` in this scope. - Tests that never fail: a test with no assertion, or one that checks the code against itself (
assert_eq!(ticket_price(8), ticket_price(8))), passes even when the code is wrong.
Your task
ticket_price has three prices: children under 12 pay 500, seniors aged 65 and over pay 700, everyone else pays 1200. Write three tests in the tests module, one for each kind of visitor. Don't change ticket_price.
Challenge 2
is_valid_username accepts names of 3 to 12 characters (counting characters, not bytes) made only of lowercase letters, digits and _. Write at least 3 tests that check valid and invalid names. Use assert! for names that should pass and assert!(!...) for names that should fail.
Challenge 3
cart_total adds up the prices in a cart (in cents) and adds a 499 cent shipping fee, but shipping is free when the items cost 5000 or more. An empty cart costs 0 (no shipping). Write at least 3 tests, including one for the empty cart. Use assert_eq!. There's no tests module in the starter this time: write it yourself.