aboutsummaryrefslogtreecommitdiff
path: root/docs/_zh_CN/pages/14-testing.md
blob: af9d077b0680f24a25e6e12f281e82e55299c74d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<h1 align="center">测试你的程序</h1>
<p align="center">
    为 Chain 和 Renderer 编写单元测试
</p>

管线模型附带的一个好处就是 **可测试性**。

Chain 只是一个接收输入、返回输出的函数,Renderer 也只是接收输入、写入内容的函数 —— 没有全局状态的黑魔法,测试起来很直接。

## 测试 Renderer

Renderer 是最容易测试的——调用函数,断言返回结果:

```rust
@@@pack!(ResultName = String);
// 返回 String 而不是 ()
#[renderer]
fn render_name(name: ResultName) -> String {
    r_println!("Hello, {}!", *name);
}
 
#[test]
fn test_render_name() {
    let result = render_name(ResultName::new("Alice".to_string()));
    assert_eq!(result, "Hello, Alice!\n");
}
```
 
注意到 Renderer 的返回值改成了 `-> String`——`#[renderer]` 会把 `RenderResult` 自动转换成你指定的返回类型(默认是 `()`)。返回 `String` 后你就可以直接断言输出内容了。

## 测试 Chain

测试 Chain 稍微复杂一点,因为它的返回值是 `Next`(实际是 `impl Into<ChainProcess<ThisProgram>>`)。需要用框架提供的断言宏:

```rust
@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
@@@dispatcher!("hello", CMDHello => EntryHello);
@@@pack!(ResultName = String);
@@@pack!(ErrorNoName = ());
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
@@@    let name = args.inner.first().cloned().unwrap_or_default();
@@@    if name.is_empty() {
@@@        ErrorNoName::default().to_render()
@@@    } else {
@@@        ResultName::new(name).to_render()
@@@    }
@@@}
#[test]
fn test_handle_hello_with_name() {
    let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into();
    // 断言这是一个渲染结果(不是继续 chain)
    assert_render_result!(chain_process);
    // 断言 member_id 是 ResultName
    assert_member_id!(chain_process, ResultName);
    // 解包出内部值
    let result_name = unpack_chain_process!(chain_process, ResultName);
    assert_eq!(result_name.inner, "Alice");
}
```
 
三个测试宏的作用:

| 宏                      | 功能                                          |
| ----------------------- | --------------------------------------------- |
| `assert_render_result!` | 断言 Chain 返回的是渲染路径(而非继续 chain) |
| `assert_member_id!`     | 断言返回值的成员 ID 是某个类型                |
| `unpack_chain_process!` | 从 ChainProcess 中解包出原始类型              |

## 用 entry! 宏构造数据

如果启用了 `extra_macros`,可以用 `entry!` 快速构造 Entry:

```rust
// Features: ["extra_macros"]
 
@@@use mingling::{assert_member_id, unpack_chain_process};
@@@use mingling::macros::entry;
@@@dispatcher!("hello", CMDHello => EntryHello);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
@@@    let name = args.inner.first().cloned().unwrap_or_default();
@@@    ResultName::new(name).to_render()
@@@}
#[test]
fn test_with_entry_macro() {
    // entry! 从字符串字面量构造 Entry
    let entry = entry!("--name", "Alice");
    let chain_process = handle_hello(entry).into();
    let result_name = unpack_chain_process!(chain_process, ResultName);
    assert_eq!(result_name.inner, "Alice");
}
```
 
## 测试资源注入

如果 Chain 使用了资源,测试时需要提供资源实例:

```rust
@@@use mingling::{assert_render_result, unpack_chain_process};
@@@#[derive(Default, Clone)]
@@@struct ResPrefix(String);
@@@dispatcher!("hello", CMDHello => EntryHello);
@@@pack!(ResultGreeting = String);
@@@
#[chain]
fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next {
    let name = args.inner.first().cloned().unwrap_or_default();
    ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render()
}
 
#[test]
fn test_handle_with_resource() {
    // 资源需要在测试中手动传入
    let result = handle_hello(
        EntryHello::new(vec!["World".to_string()]),
        &ResPrefix("Hello".to_string()),
    );
    let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram);
    assert_eq!(greeting.inner, "Hello, World");
}
```
 
管线模型让测试变得简单:每个 Chain 和 Renderer 都是相对独立的函数,构造输入、断言输出即可。

<p align="center" style="font-size: 0.85em; color: clear;">
    Written by @Weicao-CatilGrass
</p>