Testing introduction
Cargo provides a test runner, cargo test
which runs functions annotated with #[test]
. Lets create a test which checks that get_db_create_if_missing
does not crash if called twice, src/tests.rs:
#![allow(unused_variables)]
fn main() {
#[cfg(test)]
mod database {
use crate::*;
use std::fs::remove_file;
#[test]
fn creating_database_twice_should_not_fail() {
get_db_create_if_missing("test.sqlite");
get_db_create_if_missing("test.sqlite");
remove_file("test.sqlite").unwrap();
}
}
}
Here, #[cfg(test)]
tells Rust that the module should only compile when compiling tests. mod database
is a grouping for the database tests.
Running the test:
~/g/b/sakkosekk $ cargo test
Compiling sakkosekk v0.1.0 (/Users/arve/git/build-your-own-couchdb/sakkosekk)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running target/debug/deps/sakkosekk-5d572632b2e9bfcc
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running 0 tests? We need to add mod tests
to src/main.rs, such that the tests
module is found:
use rusqlite::{named_params, Connection, Error as SqliteError, Row};
use std::path::Path;
mod tests;
fn main() {
let db = get_db_create_if_missing("database.sqlite");
...
Really run the test:
~/g/b/sakkosekk $ cargo test
Compiling sakkosekk v0.1.0 (/Users/arve/git/build-your-own-couchdb/sakkosekk)
Finished dev [unoptimized + debuginfo] target(s) in 1.85s
Running target/debug/deps/sakkosekk-5d572632b2e9bfcc
running 1 test
test tests::database::creating_database_twice_should_not_fail ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Insertion
Naivly adding an insertion test:
#![allow(unused_variables)]
fn main() {
#[cfg(test)]
mod database {
use crate::*;
use std::fs::remove_file;
const TEST_DB_FILENAME: &str = "test.sqlite";
#[test]
fn creating_database_twice_should_not_fail() {
get_db_create_if_missing(TEST_DB_FILENAME);
get_db_create_if_missing(TEST_DB_FILENAME);
clean_up();
}
#[test]
fn insertion() {
let db = get_db_create_if_missing(TEST_DB_FILENAME);
let document = Document {
id: String::from("asdf"),
revision: 0,
hash: vec![0u8],
data: String::from(r#"{ "a": 1, "b": 123 }"#),
};
document.insert(&db).expect("Unable to insert document.");
clean_up();
}
fn clean_up() {
remove_file(TEST_DB_FILENAME).unwrap();
}
}
}
This will fail:
~/g/b/sakkosekk (master|✚2…) $ cargo test
Compiling sakkosekk v0.1.0 (/Users/arve/git/build-your-own-couchdb/sakkosekk)
Finished dev [unoptimized + debuginfo] target(s) in 1.14s
Running target/debug/deps/sakkosekk-5d572632b2e9bfcc
running 2 tests
test tests::database::insertion ... ok
test tests::database::creating_database_twice_should_not_fail ... FAILED
failures:
---- tests::database::creating_database_twice_should_not_fail stdout ----
thread 'tests::database::creating_database_twice_should_not_fail' panicked at 'Unable to create documents table.: SqliteFailure(Error { code: Unknown, extended_code: 1 }, Some("table documents already exists"))', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
failures:
tests::database::creating_database_twice_should_not_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
error: test failed, to rerun pass '--bin sakkosekk'
The test creating_database_twice_should_not_fail
fails, as tests runs in parallel. We'll use a helper function with
that:
- takes a filename and a test function,
- creates a database connection,
- runs the given test function with the created database connection,
- and removes the database file.
Using the function should look like:
#![allow(unused_variables)]
fn main() {
with("filename.sqlite", |db| {
// use db connection
});
// with will clean up / remove the database
}
with
implementation:
#![allow(unused_variables)]
fn main() {
fn with<F>(filename: &str, test: F)
where
F: Fn(Connection) -> (),
{
let db = get_db_create_if_missing(filename);
test(db);
remove_file(filename).unwrap();
}
}
Note: An alternative with
is to implement Drop
for our own Connection
-type.
The tests rewritten to use with
:
#![allow(unused_variables)]
fn main() {
#[test]
fn creating_database_twice_should_not_fail() {
with("creating_twice.sqlite", |_| {
get_db_create_if_missing("creating_twice.sqlite");
});
}
#[test]
fn insertion() {
with("insertion.sqlite", |db| {
let document = Document {
id: String::from("asdf"),
revision: 0,
hash: vec![0u8],
data: String::from(r#"{ "a": 1, "b": 123 }"#),
};
document.insert(&db).expect("Unable to insert document.");
});
}
}
Note that the tests use different filenames for the database.
Running the tests does not fail:
~/g/b/sakkosekk $ cargo test
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running target/debug/deps/sakkosekk-5d572632b2e9bfcc
running 2 tests
test tests::database::creating_database_twice_should_not_fail ... ok
test tests::database::insertion ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Next up: Tests that are expected to fail, like inserting a document with the same identifier twice.