ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

rust使用trait实现junit setUp tearDown run

rust使用trait实现junit setUp tearDown run Rust 没有 JUnit 那种Before/After注解但可以用trait定义测试生命周期再用一个运行器统一执行setup() → 准备测试上下文 run(ctx) → 执行测试逻辑 teardown()→ 清理资源1. 定义TestCasetraitpub trait TestCase { type Context; /// 对应 JUnit 的 setUp fn set_up() - Self::Context; /// 对应 JUnit 的 test / run fn run(ctx: Self::Context); /// 对应 JUnit 的 tearDown fn tear_down(ctx: Self::Context); }这里把测试上下文Context作为关联类型set_up创建它run使用它tear_down负责清理。2. 实现一个具体的测试用例struct DatabaseTest; struct DbContext { connection_id: u32, } impl TestCase for DatabaseTest { type Context DbContext; fn set_up() - Self::Context { println!(setUp: 初始化数据库连接); DbContext { connection_id: 1 } } fn run(ctx: Self::Context) { println!(run: 执行测试使用连接 id {}, ctx.connection_id); assert_eq!(ctx.connection_id, 1); } fn tear_down(ctx: Self::Context) { println!(tearDown: 关闭数据库连接 id {}, ctx.connection_id); } }3. 写一个类似 JUnit 的测试运行器pub fn run_testT: TestCase() { let ctx T::set_up(); // 即使 run 发生 panic也尽量保证 tear_down 被调用 let result std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { T::run(ctx); })); T::tear_down(ctx); if let Err(err) result { std::panic::resume_unwind(err); } }catch_unwind的作用是如果run里断言失败或 panic仍然先执行tear_down再把 panic 抛出去。4. 在测试中使用#[cfg(test)] mod tests { use super::*; #[test] fn test_database() { run_test::DatabaseTest(); } }运行后大致输出setUp: 初始化数据库连接 run: 执行测试使用连接 id 1 tearDown: 关闭数据库连接 id 15. 更贴近 JUnit 的写法把setUp/tearDown拆成 trait 方法如果你希望更像 JUnit可以这样设计pub trait JUnitLikeTest { type Context; fn set_up(mut self) - Self::Context; fn run(self, ctx: Self::Context); fn tear_down(mut self, ctx: Self::Context); }示例struct UserServiceTest { log: VecString, } struct UserContext { user_id: u64, } impl JUnitLikeTest for UserServiceTest { type Context UserContext; fn set_up(mut self) - Self::Context { self.log.push(set_up.into()); println!(setUp); UserContext { user_id: 100 } } fn run(self, ctx: Self::Context) { println!(run: user_id {}, ctx.user_id); assert_eq!(ctx.user_id, 100); } fn tear_down(mut self, ctx: Self::Context) { self.log.push(tear_down.into()); println!(tearDown: user_id {}, ctx.user_id); } }运行器pub fn run_junit_likeT: JUnitLikeTest(mut test: T) { let ctx test.set_up(); let result std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { test.run(ctx); })); test.tear_down(ctx); if let Err(err) result { std::panic::resume_unwind(err); } }测试#[cfg(test)] mod tests { use super::*; #[test] fn test_user_service() { run_junit_like(UserServiceTest { log: vec![] }); } }6. 如果清理逻辑和资源绑定推荐用DropRust 更惯用的方式是让资源在离开作用域时自动清理struct TestResource { name: String, } impl TestResource { fn new(name: str) - Self { println!(setUp: create {}, name); Self { name: name.to_string(), } } } impl Drop for TestResource { fn drop(mut self) { println!(tearDown: drop {}, self.name); } }测试#[cfg(test)] mod tests { use super::*; #[test] fn test_resource() { let _resource TestResource::new(db); // 测试逻辑 println!(run test); } }当_resource离开作用域时drop会自动执行相当于tearDown。推荐选择需求推荐方式想完全模拟 JUnit 的setUp / run / tearDown自定义trait 运行器只想给测试准备上下文trait返回Context资源清理主要是关闭连接、删文件等实现Drop想在真实测试中复用现成机制可了解test-context这类 crate如果你只是想学习 Rust 的trait设计用第一种TestCasetrait 方案最合适如果是写真实项目测试通常更推荐结合Drop或测试上下文库。要不要我帮你整理一份test-contextcrate 的对比示例可以看看和纯 trait 实现相比用 crate 能省去多少样板代码。
返回列表