mirror of https://github.com/XS-MLVP/mlvp.git
214 lines
7.5 KiB
Markdown
214 lines
7.5 KiB
Markdown
# 如何使用测试环境接口进行驱动
|
||
|
||
## 如何同时调用多个驱动函数
|
||
|
||
当验证环境搭建完成后,可以通过验证环境提供的接口来编写测试用例。然而,通过普通的串行代码,往往无法完成两个驱动函数的同时调用。在多个接口需要同时驱动的情况下,这种情况变得尤为重要,toffee 为这种场景提供了简便的调用方式。
|
||
|
||
### 同时调用多个不同类别的驱动函数
|
||
|
||
例如目前的 Env 结构如下:
|
||
|
||
```
|
||
DualPortStackEnv
|
||
- port1_agent
|
||
- @driver_method push
|
||
- @driver_method pop
|
||
- port2_agent
|
||
- @driver_method push
|
||
- @driver_method pop
|
||
```
|
||
|
||
我们期望在测试用例中同时调用 `port1_agent` 和 `port2_agent` 的 `push` 函数,以便同时驱动两个接口。
|
||
|
||
在 toffee 中,可以通过 `Executor` 来完成。
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
def test_push(env):
|
||
async with Executor() as exec:
|
||
exec(env.port1_agent.push(1))
|
||
exec(env.port2_agent.push(2))
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
我们使用 `async with` 来创建一个 `Executor` 对象,并建立一个执行块,通过直接调用 `exec` 可以添加需要执行的驱动函数。当 `Executor` 对象退出作用域时,会将所有添加的驱动函数同时执行。`Executor` 会自动等待所有驱动函数执行完毕。
|
||
|
||
如果需要获取驱动函数的返回值,可以通过 `get_results` 方法来获取,`get_results` 会以字典的形式返回所有驱动函数的返回值,其中键为驱动函数的名称,值为一个列表,列表中存放了对应驱动函数的返回值。
|
||
|
||
### 同一驱动函数被多次调用
|
||
|
||
如果在在执行块中多次调用同一驱动函数,`Executor` 会自动将这些调用串行执行。
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
def test_push(env):
|
||
async with Executor() as exec:
|
||
for i in range(5):
|
||
exec(env.port1_agent.push(1))
|
||
exec(env.port2_agent.push(2))
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
例如上述代码中,`port1_agent.push` 会被调用 5 次,`port2_agent.push` 会被调用 1 次。由于 `port1_agent.push` 是同一驱动函数,`Executor` 会自动将这 10 次调用串行执行,其返回值会被依次存放在返回值列表中。通过,`port2_agent.push` 将会与 `port1_agent.push` 并行执行。
|
||
|
||
上述过程中,我们创建了这样一个调度过程:
|
||
|
||
```
|
||
------------------ current time --------------------
|
||
+---------------------+ +---------------------+
|
||
| group "agent1.push" | | group "agent2.push" |
|
||
| +-----------------+ | | +-----------------+ |
|
||
| | agent1.push | | | | agent2.push | |
|
||
| +-----------------+ | | +-----------------+ |
|
||
| +-----------------+ | +---------------------+
|
||
| | agent1.push | |
|
||
| +-----------------+ |
|
||
| +-----------------+ |
|
||
| | agent1.push | |
|
||
| +-----------------+ |
|
||
| +-----------------+ |
|
||
| | agent1.push | |
|
||
| +-----------------+ |
|
||
| +-----------------+ |
|
||
| | agent1.push | |
|
||
| +-----------------+ |
|
||
+---------------------+
|
||
------------------- Executor exit -------------------
|
||
```
|
||
|
||
Executor 根据两个驱动函数的函数名自动创建了两个调度组,并按照调用顺序将驱动函数添加到对应的调度组中。在调度组内部,驱动函数会按照添加的顺序依次执行。在调度组之间,驱动函数会并行执行。
|
||
|
||
调度组的默认名称为以 `.` 分隔的驱动函数路径名。
|
||
|
||
通过 `sche_group` 参数,你可以在执行函数时手动指定驱动函数调用时所属的调度组,例如
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
def test_push(env):
|
||
async with Executor() as exec:
|
||
for i in range(5):
|
||
exec(env.port1_agent.push(1), sche_group="group1")
|
||
exec(env.port2_agent.push(2), sche_group="group1")
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
这样一来,`port1_agent.push` 和 `port2_agent.push` 将会被按顺序添加到同一个调度组 `group1` 中,表现出串行执行的特性。同时 `get_results` 返回的字典中,`group1` 会作为键,其值为一个列表,列表中存放了 `group1` 中所有驱动函数的返回值。
|
||
|
||
### 将自定义函数加入 Executor
|
||
|
||
如果我们在一个自定义函数中调用了驱动函数或其他驱动函数,并希望自定义函数也可以通过 `Executor` 来调度,可以通过与添加驱动函数相同的方式来添加自定义函数。
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
async def multi_push_port1(env, times):
|
||
for i in range(times):
|
||
await env.port1_agent.push(1)
|
||
|
||
async def test_push(env):
|
||
async with Executor() as exec:
|
||
for i in range(2):
|
||
exec(multi_push_port1(env, 5))
|
||
exec(env.port2_agent.push(2))
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
此时,`multi_push_port1` 会被添加到 `Executor` 中,并创建以 `multi_push_port1` 为名称的调度组,并向其中添加两次调用。其会与 `port2_agent.push` 调度组并行执行。
|
||
|
||
我们也可以在自定义函数中使用 `Executor`,或调用其他自定义函数。这样一来,我们可以通过 `Executor` 完成任意复杂的调度。以下提供了若干个案例:
|
||
|
||
**案例一**
|
||
|
||
环境接口如下:
|
||
|
||
```
|
||
Env
|
||
- agent1
|
||
- @driver_method send
|
||
- agent2
|
||
- @driver_method send
|
||
```
|
||
|
||
两个 Agent 中的 `send` 函数各需要被并行调用 5 次,并且调用时需要发送上一次的返回结果,第一次发送时发送 0,两个函数调用相互独立。
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
async def send(agent):
|
||
result = 0
|
||
for i in range(5):
|
||
result = await agent.send(result)
|
||
|
||
async def test_send(env):
|
||
async with Executor() as exec:
|
||
exec(send(env.agent1), sche_group="agent1")
|
||
exec(send(env.agent2), sche_group="agent2")
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
**案例二**
|
||
|
||
环境接口如下:
|
||
|
||
```
|
||
env
|
||
- agent1
|
||
- @driver_method long_task
|
||
- agent2
|
||
- @driver_method task1
|
||
- @driver_method task2
|
||
```
|
||
|
||
task1 和 task2 需要并行执行,并且一次调用结束后需要同步,task1 和 task2 都需要调用 5 次,long_task 需要与 task1 和 task2 并行执行。
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
async def exec_once(env):
|
||
async with Executor() as exec:
|
||
exec(env.agent2.task1())
|
||
exec(env.agent2.task2())
|
||
|
||
async def test_case(env):
|
||
async with Executor() as exec:
|
||
for i in range(5):
|
||
exec(exec_once(env))
|
||
exec(env.agent1.long_task())
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
### 设置 Executor 的退出条件
|
||
|
||
Executor 会等待所有添加的驱动函数执行完毕后退出,但有时我们并不需要等待所有驱动函数执行完毕,可以通过在创建 Executor 时使用 `exit` 参数来设置退出条件。
|
||
|
||
`exit` 参数可以被设置为 `all`, `any` 或 `none` 三种值,分别表示所有调度组执行完毕后退出、任意一个调度组执行完毕后退出、不等待直接退出。
|
||
|
||
```python
|
||
from toffee import Executor
|
||
|
||
async def send_forever(agent):
|
||
result = 0
|
||
while True:
|
||
result = await agent.send(result)
|
||
|
||
async def test_send(env):
|
||
async with Executor(exit="any") as exec:
|
||
exec(send_forever(env.agent1))
|
||
exec(env.agent2.send(1))
|
||
|
||
print("result", exec.get_results())
|
||
```
|
||
|
||
例如上述代码中 `send_forever` 函数是一个无限循环的函数,将 `exit` 设置为 `any` 后,Executor 会在 `env.agent2.send` 函数执行完毕后退出,而不会等待 `send_forever` 函数执行完毕。
|
||
|
||
如果后续需要等待所有任务执行完毕,可以通过等待 `exec.wait_all` 来实现。
|