【问题标题】:async testing in rust - how to fail a test from a side thread or by panicrust 中的异步测试 - 如何通过侧线程或恐慌使测试失败
【发布时间】:2020-11-01 15:23:08
【问题描述】:

我的库产生了一个支线,我正在尝试为它编写单元测试。 如果图书馆/模拟恐慌,我想通过测试。

我在嘲笑mockall

我的 lib 代码类似于:

    #[cfg_attr(test, automock)]
    trait T1 { fn foo(&self, val: String) }
    #[cfg_attr(test, automock)]
    trait T2 { fn bar(&self, val: String) }
    #[cfg_attr(test, automock)]
    trait T3 { fn fa(&self, val: String) }
    
    struct MyLib<A: T1 + Send + Sync, B:T2 + Send + Sync, C:T3 + Send + Sync> {f1: Arc<A>, f2: Arc<B>, f3: Arc<C>};
    
    impl My_Lib {
      fn do_something (&self) {
        let f1 = Arc::clone(&self.f1);
        let f2 = Arc::clone(&self.f2);
        let f3 = Arc::clone(&self.f3);
        thread::Builder::new().name("lib_thread".to_string()).spawn(move || {
            // does something in an infinite loop with lots of logic and calls out to the traits which I am mocking in the tests
            loop {
               // some logic
               // ....
               f1.foo("something happened here which is interesting so check in test if it occured".to_string());
               // more logic
               // ...
               f2.bar("more interesting things happened here".to_string());
               // more logic
               // ...
               f3.fa("abc".to_string());
               thread::sleep(interval)
            }
        });
      }
    }
    
    #[test]
    fn test_lib() {
       let mut t1 = Mock_T1::new();
       t1.expect_foo().return_const(()); // all sorts of mocks...
       // all sorts of fancy logic
    
       let mut c = Mock_T3::new();
       c.expect_fa().withf(|x: String| { 
    // some complex assertion here which failed and it was called on "lib_thread"
          // i.e.
          assert!(x == "some interesting value which should be here!"); 
          assert(false); // should make the test fail but instead get a panic on the thread
// thread 'lib_thread' panicked at 'MockT3::fa: No matching expectation found
          // test is still running and never completes
       }
       //block the test from exiting until I want to finish
       std::thread::park();
    }

可重现的案例:

    fn should_fail() {
        std::thread::spawn(|| {
           loop {
               assert!(false); // test should complete here
               std::thread::sleep(Duration::from_secs(1));
           }
        });
    }

    #[test]
    fn fail() {
        should_fail();
        std::thread::park();
    }

【问题讨论】:

  • 如果出现恐慌,测试应该会失败。你能提供一个最小的可重现的例子吗?
  • ``` fn should_fail() { std::thread::spawn(|| { loop { assert!(false); std::thread::sleep(Duration::from_secs(1) ); } }); } #[test] fn fail() { should_fail(); std::thread::park(); } ```
  • std::thread::park(); 是导致测试挂起的原因,因为线程已停止。你写这个有什么具体原因吗?
  • 替换为 std::thread::park_timeout(Duration::from_secs(10));
  • 预计会失败

标签: rust mocking asynctest


【解决方案1】:

测试工具只检查它产生的线程是否恐慌,你必须将你自己的线程内的恐慌传播到调用测试的线程。为此,您可以将JoinHandle 返回到由thread::spawn 创建的线程:

fn should_fail() -> std::thread::JoinHandle<()> {
    std::thread::spawn(|| {
        loop {
            assert!(false);
            std::thread::sleep(Duration::from_secs(1));
        }
    })
}

然后您可以在句柄上调用.join()JoinHandle::join() 等待相关线程完成。如果子线程发生紧急情况,则返回Err,并将参数提供给panic!。这样,panic 就会传播到主线程:

#[test]
fn test() {
    let join_handle = should_fail();
    // unwrap will panic if `should_fail` panics
    join_handle.join().unwrap();
}

您可能不想仅出于测试目的而返回JoinHandle。然而JoinHandle 远不止这些。事实上,there was even discussion 关于标记它#[must_use]!这是一篇关于Joining your threads 的精彩帖子。


还有其他几种方法可以在不阻塞的情况下等待线程完成,例如使用通道,或讨论here 的引用计数器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    相关资源
    最近更新 更多