乐闻世界logo
搜索文章和话题

Yew 中如何进行测试,有哪些测试策略和工具?

2月19日 16:22

Yew 测试策略与实践

Yew 应用测试需要考虑 WebAssembly 环境和 Rust 的测试框架,本文介绍完整的测试策略。

单元测试

1. 组件单元测试

rust
use yew::prelude::*; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); #[function_component(SimpleComponent)] fn simple_component() -> Html { let count = use_state(|| 0); let increment = { let count = count.clone(); Callback::from(move |_| count.set(*count + 1)) }; html! { <div> <button onclick={increment}>{ "Increment" }</button> <span>{ *count }</span> </div> } } #[wasm_bindgen_test] async fn test_simple_component() { let result = yew::ServerRenderer::<SimpleComponent>::render().await; assert!(result.contains("Increment")); assert!(result.contains("0")); }

2. Hook 单元测试

rust
use yew::prelude::*; use yew_hooks::prelude::*; #[wasm_bindgen_test] async fn test_use_counter() { let result = yew::ServerRenderer::<CounterComponent>::render().await; assert!(result.contains("Counter: 0")); } #[function_component(CounterComponent)] fn counter_component() -> Html { let (count, set_count) = use_counter(0); html! { <div> <p>{ format!("Counter: {}", count) }</p> <button onclick={Callback::from(move |_| set_count(*count + 1))}> { "Increment" } </button> </div> } }

集成测试

1. 组件交互测试

rust
use yew::prelude::*; use wasm_bindgen_test::*; #[function_component(FormComponent)] fn form_component() -> Html { let username = use_state(|| String::new()); let submitted = use_state(|| false); let onsubmit = { let username = username.clone(); let submitted = submitted.clone(); Callback::from(move |e: SubmitEvent| { e.prevent_default(); if !username.is_empty() { submitted.set(true); } }) }; let oninput = { let username = username.clone(); Callback::from(move |e: InputEvent| { let input: HtmlInputElement = e.target_unchecked_into(); username.set(input.value()); }) }; html! { <form onsubmit={onsubmit}> <input type="text" value={(*username).clone()} oninput={oninput} placeholder="Username" /> <button type="submit">{ "Submit" }</button> { if *submitted { html! { <p>{ "Form submitted!" }</p> } } else { html! {} }} </form> } } #[wasm_bindgen_test] async fn test_form_submission() { let result = yew::ServerRenderer::<FormComponent>::render().await; assert!(result.contains("Username")); assert!(result.contains("Submit")); }

2. 异步组件测试

rust
use yew::prelude::*; use wasm_bindgen_futures::spawn_local; #[function_component(AsyncComponent)] fn async_component() -> Html { let data = use_state(|| String::new()); let loading = use_state(|| false); let fetch_data = { let data = data.clone(); let loading = loading.clone(); Callback::from(move |_| { loading.set(true); spawn_local(async move { gloo_timers::future::sleep(std::time::Duration::from_millis(100)).await; data.set("Async data loaded!".to_string()); loading.set(false); }); }) }; html! { <div> <button onclick={fetch_data} disabled={*loading}> { if *loading { "Loading..." } else { "Fetch Data" } } </button> <p>{ &*data }</p> </div> } } #[wasm_bindgen_test] async fn test_async_component() { let result = yew::ServerRenderer::<AsyncComponent>::render().await; assert!(result.contains("Fetch Data")); assert!(result.contains("Loading...")); }

端到端测试

1. 使用 yew-app 进行 E2E 测试

rust
use yew::prelude::*; use wasm_bindgen_test::*; #[function_component(App)] fn app() -> Html { let count = use_state(|| 0); let increment = { let count = count.clone(); Callback::from(move |_| count.set(*count + 1)) }; html! { <div id="app"> <h1>{ "Counter App" }</h1> <button id="increment-btn" onclick={increment}> { "Increment" } </button> <p id="count-display">{ *count }</p> </div> } } #[wasm_bindgen_test] async fn test_counter_increment() { // 渲染应用 yew::Renderer::<App>::with_root( gloo::utils::document().get_element_by_id("output").unwrap(), ).render(); // 模拟点击 let button = gloo::utils::document() .get_element_by_id("increment-btn") .unwrap(); let event = web_sys::MouseEvent::new("click").unwrap(); button.dispatch_event(&event).unwrap(); // 验证结果 let display = gloo::utils::document() .get_element_by_id("count-display") .unwrap(); assert_eq!(display.text_content().unwrap(), "1"); }

Mock 和 Stub

1. Mock HTTP 请求

rust
use yew::prelude::*; use gloo_net::http::Request; #[function_component(ApiComponent)] fn api_component() -> Html { let user = use_state(|| None::<String>); let loading = use_state(|| false); let fetch_user = { let user = user.clone(); let loading = loading.clone(); Callback::from(move |_| { loading.set(true); spawn_local(async move { match Request::get("/api/user").send().await { Ok(response) => { if response.ok() { if let Ok(data) = response.text().await { user.set(Some(data)); } } } Err(_) => {} } loading.set(false); }); }) }; html! { <div> <button onclick={fetch_user} disabled={*loading}> { if *loading { "Loading..." } else { "Fetch User" } } </button> { if let Some(ref u) = *user { html! { <p>{ u }</p> } } else { html! {} }} </div> } } #[cfg(test)] mod tests { use super::*; use wasm_bindgen_test::*; #[wasm_bindgen_test] async fn test_api_component_with_mock() { // 在测试中,可以使用 mock 服务器 // 这里简化为测试组件渲染 let result = yew::ServerRenderer::<ApiComponent>::render().await; assert!(result.contains("Fetch User")); } }

2. Mock WebSocket

rust
use yew::prelude::*; use gloo_net::websocket::Message; #[function_component(WebSocketComponent)] fn websocket_component() -> Html { let messages = use_state(|| Vec::<String>::new()); let connected = use_state(|| false); let connect = { let messages = messages.clone(); let connected = connected.clone(); Callback::from(move |_| { let ws = gloo_net::websocket::futures::WebSocket::open("ws://localhost:8080"); if let Ok(ws) = ws { let onmessage = { let messages = messages.clone(); Callback::from(move |msg: Message| { if let Message::Text(text) = msg { let mut msgs = (*messages).clone(); msgs.push(text); messages.set(msgs); } }) }; ws.set_onmessage(onmessage); connected.set(true); } }) }; html! { <div> <button onclick={connect} disabled={*connected}> { if *connected { "Connected" } else { "Connect" } } </button> <ul> { messages.iter().map(|msg| { html! { <li>{ msg }</li> } }).collect::<Html>() } </ul> </div> } }

测试覆盖率

1. 使用 tarpaulin 测试覆盖率

bash
# 安装 tarpaulin cargo install cargo-tarpaulin # 运行覆盖率测试 cargo tarpaulin --out Html --output-dir ./coverage # 针对 Wasm 测试 wasm-pack test --firefox --headless

2. 配置测试覆盖率

toml
# Cargo.toml [package] name = "yew-app" version = "0.1.0" edition = "2021" [dev-dependencies] wasm-bindgen-test = "0.3" gloo = "0.8" web-sys = { version = "0.3", features = ["console"] }

性能测试

1. 组件渲染性能测试

rust
use yew::prelude::*; use web_sys::Performance; #[function_component(PerformanceTestComponent)] fn performance_test_component() -> Html { let render_time = use_state(|| 0.0); let measure_render = { let render_time = render_time.clone(); Callback::from(move |_| { if let Ok(performance) = Performance::new() { let start = performance.now(); // 模拟渲染操作 let _ = std::hint::black_box(42); let end = performance.now(); render_time.set(end - start); } }) }; html! { <div> <button onclick={measure_render}>{ "Measure Render" }</button> <p>{ "Render time: " }{ *render_time }{ " ms" }</p> </div> } } #[wasm_bindgen_test] async fn test_render_performance() { let result = yew::ServerRenderer::<PerformanceTestComponent>::render().await; assert!(result.contains("Measure Render")); assert!(result.contains("Render time:")); }

测试最佳实践

1. 测试组织

rust
// tests/common/mod.rs pub mod common { use yew::prelude::*; pub fn setup_test_environment() { // 设置测试环境 } pub fn cleanup_test_environment() { // 清理测试环境 } } // tests/components/button_test.rs mod button_test { use super::common::*; use wasm_bindgen_test::*; #[wasm_bindgen_test] async fn test_button_render() { setup_test_environment(); let result = yew::ServerRenderer::<ButtonComponent>::render().await; assert!(result.contains("Click me")); cleanup_test_environment(); } }

2. 测试数据管理

rust
// tests/fixtures/mod.rs pub mod fixtures { pub fn get_test_user() -> User { User { id: 1, name: "Test User".to_string(), email: "test@example.com".to_string(), } } pub fn get_test_posts() -> Vec<Post> { vec![ Post { id: 1, title: "Test Post 1".to_string(), body: "Test content".to_string(), }, Post { id: 2, title: "Test Post 2".to_string(), body: "More test content".to_string(), }, ] } }

持续集成

1. GitHub Actions 配置

yaml
# .github/workflows/test.yml name: Test on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Run tests run: | cargo test --all wasm-pack test --firefox --headless - name: Run coverage run: cargo tarpaulin --out Xml

2. GitLab CI 配置

yaml
# .gitlab-ci.yml stages: - test test: stage: test image: rust:latest before_script: - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh script: - cargo test --all - wasm-pack test --firefox --headless - cargo tarpaulin --out Xml coverage: '/^\d+.\d+% coverage/' artifacts: reports: coverage_report: coverage_format: cobertura path: cobertura.xml

调试测试

1. 使用浏览器开发者工具

rust
use web_sys::console; #[wasm_bindgen_test] async fn test_with_debugging() { console::log_1(&"Starting test".into()); let result = yew::ServerRenderer::<TestComponent>::render().await; console::log_2( &"Render result:".into(), &result.into() ); assert!(result.contains("Expected content")); }

2. 使用 console_error_panic_hook

rust
use console_error_panic_hook; #[wasm_bindgen_test] async fn test_with_panic_hook() { console_error_panic_hook::set_once(); // 测试代码 let result = yew::ServerRenderer::<TestComponent>::render().await; assert!(result.contains("Expected content")); }

测试工具推荐

  1. wasm-bindgen-test: Wasm 环境测试框架
  2. yew-app: Yew 应用测试工具
  3. gloo: Web API 绑定和工具
  4. cargo-tarpaulin: 代码覆盖率工具
  5. web-sys: Web API 绑定

总结

Yew 测试需要结合 Rust 的测试框架和 WebAssembly 的特性。通过合理的测试策略、Mock 技术和持续集成配置,可以构建高质量的 Yew 应用。

标签:Yew