Rust 创建测试设置

Sau*_*abh 3 unit-testing rust

我正在通过单元测试学习 Rust,下面的代码是这个问题的解决方案,稍作修改。

struct User {
    name: String,
    age: u8,
    weight: f32
}

impl User {
    fn new(name: String, age: u8, weight: f32) -> Self {
        User {name: name, age: age, weight: weight}
    }

    fn get_name(&self) -> &str {
        &self.name
    }

    fn get_age(&self) -> u8 {
        self.age
    }

    fn get_weight(&self) -> f32 {
        self.weight
    }

    fn set_name(&mut self, new_name: String) {
        self.name = new_name
    }

    fn set_age(&mut self, new_age: u8) {
        self.age = new_age
    }

    fn set_weight(&mut self, new_weight: f32) {
        self.weight = new_weight
    }
}


#[test]
fn test_user_get_name() {
    let user = User::new(String::from("Bob"), 20, 150.23);
    assert_eq!(user.get_name(), "Bob")
}

#[test]
fn test_user_set_name() {
    let mut user = User::new(String::from("John"), 24, 150.23);
    user.set_name("Jane".to_string());    // no performance cost compared to String::from
    assert_eq!(user.get_name(), "Jane")
}

#[test]
fn test_user_get_age() {
    let user = User::new(String::from("Bob"), 20, 150.23);
    assert_eq!(user.get_age(), 20)
}

#[test]
fn test_user_set_age() {
    let mut user = User::new(String::from("Bob"), 20, 150.23);
    user.set_age(25);
    assert_eq!(user.get_age(), 25)
}

#[test]
fn test_user_get_weight() {
    let user = User::new(String::from("Bob"), 20, 150.23);
    assert_eq!(user.get_weight(), 150.23)
}

#[test]
fn test_user_set_weight() {
    let mut user = User::new(String::from("Bob"), 20, 150.23);
    user.set_weight(160.8);
    assert_eq!(user.get_weight(), 160.8)
}
Run Code Online (Sandbox Code Playgroud)

我可以执行测试并且所有测试都通过:

rustc --test health_stats.rs --verbose -o health_stats_test
./health_stats_test
Run Code Online (Sandbox Code Playgroud)

如何创建setup()具有可变对象的函数,例如:

let mut user = User::new(String::from("Bob"), 20, 150.23);
Run Code Online (Sandbox Code Playgroud)

这样我就可以在测试中调用getset方法而无需重复代码?

Fin*_*nis 7

首先,一些小挑剔:

  • 您的测试函数被编译到您的发布版本中。使用Rust 书中描述的模式来布局您的测试。
  • 我制作了你struct和你所有的impls pub,否则它们毫无意义,而且我不喜欢这些警告:)

那么,抛开这个问题,我们来实际讨论一下测试。

setupRust 中没有用于 test /的内置机制teardown,但您自己实现非常容易。您只需编写一个简单的setup()函数,该函数返回一个上下文对象,并drop提供拆卸的实现。

您可以创建用户并将其存储在上下文对象中。此外,您可以运行任何类型的副作用。我将在这里通过打印一些消息来演示这一点。

setup在这里,这就是我实现/机制的方式teardown

pub struct User {
    name: String,
    age: u8,
    weight: f32,
}

impl User {
    pub fn new(name: String, age: u8, weight: f32) -> Self {
        User {
            name: name,
            age: age,
            weight: weight,
        }
    }

    pub fn get_name(&self) -> &str {
        &self.name
    }

    pub fn get_age(&self) -> u8 {
        self.age
    }

    pub fn get_weight(&self) -> f32 {
        self.weight
    }

    pub fn set_name(&mut self, new_name: String) {
        self.name = new_name
    }

    pub fn set_age(&mut self, new_age: u8) {
        self.age = new_age
    }

    pub fn set_weight(&mut self, new_weight: f32) {
        self.weight = new_weight
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct TestContext {
        user: User,
    }

    impl Drop for TestContext {
        fn drop(&mut self) {
            println!("Test teardown ...");
        }
    }

    fn setup() -> TestContext {
        println!("Test setup ...");

        TestContext {
            user: User::new(String::from("Bob"), 20, 150.23),
        }
    }

    #[test]
    fn test_user_get_name() {
        let ctx = setup();
        assert_eq!(ctx.user.get_name(), "Bob")
    }

    #[test]
    fn test_user_set_name() {
        let mut ctx = setup();
        ctx.user.set_name("Jane".to_string()); // no performance cost compared to String::from
        assert_eq!(ctx.user.get_name(), "Jane")
    }

    #[test]
    fn test_user_get_age() {
        let ctx = setup();
        assert_eq!(ctx.user.get_age(), 20)
    }

    #[test]
    fn test_user_set_age() {
        let mut ctx = setup();
        ctx.user.set_age(25);
        assert_eq!(ctx.user.get_age(), 25)
    }

    #[test]
    fn test_user_get_weight() {
        let ctx = setup();
        assert_eq!(ctx.user.get_weight(), 150.23)
    }

    #[test]
    fn test_user_set_weight() {
        let mut ctx = setup();
        ctx.user.set_weight(160.8);
        assert_eq!(ctx.user.get_weight(), 160.8)
    }
}
Run Code Online (Sandbox Code Playgroud)
> cargo test
running 6 tests
test tests::test_user_get_age ... ok
test tests::test_user_get_name ... ok
test tests::test_user_get_weight ... ok
test tests::test_user_set_age ... ok
test tests::test_user_set_name ... ok
test tests::test_user_set_weight ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Run Code Online (Sandbox Code Playgroud)

如果添加--nocapture,您可以看到安装/拆卸消息的输出println

> cargo test -- --nocapture
running 6 tests
Test setup ...
Test teardown ...
Test setup ...
Test teardown ...
Test setup ...
Test teardown ...
Test setup ...
Test teardown ...
test tests::test_user_get_age ... okTest setup ...
Test setup ...
Test teardown ...
Test teardown ...

test tests::test_user_get_name ... ok
test tests::test_user_get_weight ... ok
test tests::test_user_set_age ... ok
test tests::test_user_set_weight ... ok
test tests::test_user_set_name ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Run Code Online (Sandbox Code Playgroud)

重要:副作用

默认情况下,cargo test在多个线程上并行运行测试。如果您的setup/teardown方法具有非线程安全的副作用,您可能需要使用以下标志强制执行单线程测试--test-threads 1

> cargo test -- --nocapture --test-threads 1
running 6 tests
test tests::test_user_get_age ... Test setup ...
Test teardown ...
ok
test tests::test_user_get_name ... Test setup ...
Test teardown ...
ok
test tests::test_user_get_weight ... Test setup ...
Test teardown ...
ok
test tests::test_user_set_age ... Test setup ...
Test teardown ...
ok
test tests::test_user_set_name ... Test setup ...
Test teardown ...
ok
test tests::test_user_set_weight ... Test setup ...
Test teardown ...
ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Run Code Online (Sandbox Code Playgroud)