测试框架
面向 pytest、Node.js 和 .NET 测试的可选辅助工具:手机夹具、失败报告中的最近命令、每个失败测试的截图和界面树,以及为每个并行进程分配一部手机。
本页为译文。如与英文版不一致,以英文版为准。 English
任何测试运行器都可以直接调用 Droidline。本页的辅助工具是可选的,补上大家通常要自己写的部分:把手机交给每个测试的夹具、测试发给手机的命令记录,以及测试失败那一刻的界面。不打开就不会加载任何东西。
pytest
只在一次运行中打开插件,或者在 conftest.py 中为整个项目打开:
pytest -p droidline.pytest_plugin
# conftest.py
pytest_plugins = ["droidline.pytest_plugin"]
然后使用 phone 夹具:
def test_log_in(phone):
phone.launch("com.example.shop")
phone.input("id", "email", "knife@example.com")
phone.touch("text", "登录")
assert phone.exists("id", "greeting")
| 选项 | 作用 |
|---|---|
--droidline-device NAME | 要测试的手机。不指定时使用 DROIDLINE_DEVICE,再没有则使用唯一在线的手机。 |
--droidline-lease | 在整个会话期间租用一部空闲手机。配合 pytest -n 4(pytest-xdist)时,每个进程各得一部手机,没有空闲手机时最多等待 30 秒。 |
--droidline-artifacts DIR | 每个失败的测试都把手机的截图、界面树和命令记录保存到 DIR/<测试>/。 |
使用 phone 的测试失败时,报告中会多出 droidline commands 一节,列出它最近发出的请求,让你看到出错的那一步:
---------------------------- droidline commands ----------------------------
14:02:11 launch {"package": "com.example.shop"} -> ok (812 ms)
14:02:13 touch {"by": "text", "value": "登录"} -> NOT_FOUND (10013 ms)
使用 --droidline-artifacts 时,文件夹中会有 commands.txt、screen.json 和 screen.png。每个部分能保存就保存:手机离线时仍会留下命令记录;在 Android 9 和 10 上,截图需要屏幕录制权限。
插件还提供 droidline_client,整个会话共用一个到服务器的连接。
其他 Python 测试运行器
同样的辅助函数不依赖 pytest:
from droidline.testing import format_history, save_failure
try:
run_steps(d)
except Exception:
save_failure(d, "artifacts/checkout") # 截图、界面树和命令记录
raise
print(format_history(d.history[-10:])) # 以文本输出最近十个请求
Node.js
辅助函数在 droidline/testing 中。在运行器告诉你测试失败的地方调用 saveFailure。使用内置的 node:test 时:
import { after, test } from "node:test";
import { connect } from "droidline";
import { saveFailure } from "droidline/testing";
const d = await connect();
after(() => d.close());
// 运行测试主体,抛出异常时保留现场。
async function onPhone(t, body) {
try {
await body();
} catch (err) {
await saveFailure(d, `artifacts/${t.name}`);
throw err;
}
}
test("登录", (t) =>
onPhone(t, async () => {
await d.launch("com.example.shop");
await d.touch("text", "登录");
}));
在 Vitest 或 Jest 中,从你自己的失败处理钩子里调用 saveFailure(d, folder)。formatHistory(d.history) 以文本形式返回命令记录。
.NET
.NET 没有插件包。Droidline 包中的 Testing 类提供同样的辅助函数,在你的测试框架中直接调用即可。使用 xUnit 时:
using System.Runtime.CompilerServices;
using Droidline;
using Xunit;
// 同一个类中的所有测试共用一部手机。
public sealed class PhoneFixture : IAsyncLifetime
{
public Device Phone { get; private set; } = null!;
public async Task InitializeAsync() => Phone = await DroidlineClient.ConnectAsync();
public async Task DisposeAsync() => await Phone.DisposeAsync();
}
public class ShopTests : IClassFixture<PhoneFixture>
{
private readonly Device d;
public ShopTests(PhoneFixture fixture) => d = fixture.Phone;
// 运行测试主体,抛出异常时保留现场。
private async Task OnPhone(Func<Task> body, [CallerMemberName] string test = "")
{
try
{
await body();
}
catch
{
await Testing.SaveFailureAsync(d, $"artifacts/{test}");
throw;
}
}
[Fact]
public Task LogIn() => OnPhone(async () =>
{
await d.LaunchAsync("com.example.shop");
await d.TouchAsync("text", "登录");
});
}
[CallerMemberName] 会填入测试方法的名字,所以每个失败的测试都有自己的文件夹。在 NUnit 或 MSTest 中,用同样的方式包裹测试主体即可。Testing.FormatHistory(d.History) 以文本形式返回命令记录,例如可以写到 xUnit 的 ITestOutputHelper 中。
xUnit 会并行运行各个测试类。要让每个类使用各自的手机,在 InitializeAsync 中用 DroidlineClient.LeaseAsync(wait: 60) 代替 ConnectAsync();释放手机对象时会归还手机。
命令记录
每个客户端都会记住最近 200 个请求。d.history 按从旧到新的顺序列出发给这部手机的请求,每条包含 cmd、params、ok、error 和 ms。在 .NET 中是 d.History,每条包含 Cmd、Params、Ok、Error 和 Ms。截图这类很长的值只保留长度。记录只存在于你的进程中,不会发送到任何地方。
同时使用多部手机
并行运行测试,并用 lease 给每个进程分配一部手机:使用 pytest -n 4 --droidline-lease,或在你自己的准备步骤中调用 lease()(.NET 中为 DroidlineClient.LeaseAsync())。参见在脚本之间共享手机。