为什么我要构建Smartest测试运行器?Playwright风格fixture注入实战
BLUF 摘要
本文探讨作者为何构建Smartest——一款受Playwright Test启发的Ruby测试运行器,重点介绍其采用pytest风格、具备显式依赖与自动清理的fixture注入机制,旨在优化Rails中的浏览器测试。
Why I Built Another Ruby Test Runner Inspired by Playwright Test
Ruby 已经拥有了优秀的测试工具。
如果你现在正在构建 Rails 应用,你很可能使用以下组合之一:
- RSpec + Capybara
- Minitest + Capybara
- Rails system tests
这些工具已经成熟、久经考验且被广泛使用。
So the natural question is:
所以自然的问题是:
Why create yet another test runner?
为什么要再创造一个测试运行器?
这正是 Playwright 团队在创建 Playwright Test 时问自己的问题。
在启发这个项目的 Playwright 视频中,演讲者表示他们原本并不想再创建一个 JavaScript 测试框架。他们尝试过 Mocha 和 Jest 等现有框架,但那些框架从历史上看都是为单元测试设计的。端到端测试面临一系列不同的问题:跨浏览器执行、并行化、浏览器端隔离以及高度灵活的 fixture 机制。
这个解释让我茅塞顿开。
因为 Ruby on Rails 的浏览器测试也有类似的问题。
RSpec 和 Minitest 是优秀的通用 Ruby 测试框架。Capybara 是出色的浏览器自动化 DSL。但当前主流的 Rails 浏览器测试风格并不是在借鉴 Playwright Test 之后设计的。它仍然感觉像是单元测试时代的架构,然后在上面叠加了浏览器自动化。
Playwright Test 表明,浏览器测试值得拥有一个围绕浏览器测试本身设计的测试运行器。
这就是 Smartest 背后的理念。
The Inspiration: Playwright Test Was Not Just Another Runner
Playwright Test 不仅仅是浏览器自动化的一个封装。
它是一个为端到端测试的真实约束而设计的测试运行器:
- Parallel execution
- Browser-context isolation
- Web-first assertions
- Fixtures inspired by pytest
重要的不仅仅是“Playwright 控制浏览器”。
The important point is:
重要的点是:
Playwright Test changed the test runner because the target domain changed.
Playwright Test 改变了测试运行器,因为目标领域发生了变化。
浏览器测试不仅仅是加了浏览器的单元测试。
它们需要自己的设置模型。
The Fixture Idea I Wanted in Ruby
pytest 中最好的想法之一就是 fixture 注入。
在 pytest 中,测试可以通过名称来请求它所需的东西:
def test_get_me(logged_in_client):
response = logged_in_client.get("/me")
assert response.status_code == 200
测试主体并没有说明如何创建 logged_in_client。
fixture 系统知道如何解析它。
Fixture 可以依赖其他 fixture:
@pytest.fixture
def logged_in_client(client, user):
client.login(user)
return client
这对于浏览器测试非常有用,因为浏览器测试通常涉及分层设置:
| Layer | Description |
|---|---|
| App server | Application under test |
| Browser runtime | Browser process |
| Browser context | Isolated session |
| Page | Single page instance |
| User | Test user data |
| Login state | Authenticated session |
| Seeded data | Pre-populated database |
| Cleanup | Teardown after test |
当然,RSpec 和 Minitest 也能实现所有这些。
但依赖关系图往往是隐式的。
使用 RSpec,我们可能会写出类似这样的代码:
let(:server) { start_server }
let(:client) { Client.new(base_url: server.url) }
let(:user) { create_user }
let(:logged_in_client) do
client.login(user)
client
end
it "GET /me" do
response = logged_in_client.get("/me")
expect(response.status).to eq(200)
end
This works.
这能工作。
但 fixture 的依赖关系图隐藏在方法调用中。
I wanted this instead:
而我希望的是这样的:
test("GET /me") do |logged_in_client:|
response = logged_in_client.get("/me")
expect(response.status).to eq(200)
end
以及这样的 fixture 定义:
class AppFixture < Smartest::Fixture
fixture :client do |server:|
Client.new(base_url: server.url)
end
fixture :logged_in_client do |client:, user:|
client.login(user)
client
end
end
重要的设计决策是关键字参数。
test("GET /me") do |logged_in_client:|
这使测试的依赖关系变得明确。
And:
并且:
fixture :logged_in_client do |client:, user:|
这使 fixture 的依赖关系变得明确。
没有位置参数的顺序问题。
没有隐式的 let 依赖。
没有单独的 with: [:server] 声明。
依赖的声明和使用融合在一个地方:Ruby 的方法签名。
Smartest: A Small Keyword-Fixture-First Ruby Test Runner
Smartest 是一个小型 Ruby 测试运行器,采用关键字 fixture 优先的设计。
目标不是立即取代所有 Ruby 测试。
目标是探索如果将 pytest fixture 和 Playwright Test 的经验应用到 Ruby 浏览器测试中,Ruby 测试会是什么感觉。
一个普通的 Smartest 测试看起来像这样:
test("factorial") do
expect(1 * 2 * 3).to eq(6)
end
一个由 fixture 驱动的测试看起来像这样:
test("GET /me") do |logged_in_client:|
response = logged_in_client.get("/me")
expect(response.status).to eq(200)
end
Smartest 围绕三个理念设计:
测试应在顶层可读。
Fixture 依赖应显式声明。
清理代码应仅在需要时编写……
(Note: The original article continues, but this rewrite focuses on the introduction, key concepts, and main analysis. For a complete guide, please refer to the official documentation.)
Summary: A Comparison of Smartest with Traditional Ruby Test Frameworks
| Feature | RSpec / Minitest (with Capybara) | Smartest |
|---|---|---|
| Fixture injection | Implicit via let or before blocks |
Explicit via keyword arguments in test method signature |
| Dependency graph | Hidden (resolved at runtime via scope) | Visible in the method signature of both tests and fixtures |
| Fixture hierarchy | Manual setup via nested describe or before |
Automatic dependency resolution between fixtures |
| Parallel execution | Possible with extra gems (e.g., parallel_tests) |
Designed with Playwright-inspired parallelism in mind |
| Cleanup | after blocks or let! with instance variables |
Built-in cleanup block inside fixture definitions |
| Browser-specific features | Via Capybara (auto-wait, driver abstraction) | Direct integration with Playwright (via Ruby bindings) |
| Learning curve | Low (familiar to Ruby devs) | Low to medium (new keyword-fixture paradigm) |
Smartest 并非旨在取代所有场景下的 RSpec 或 Minitest。它旨在通过采用在 JavaScript(Playwright)和 Python(pytest)生态系统中已被证明成功的理念,为 浏览器测试 提供更好的体验。
常见问题(FAQ)
为什么已经有了RSpec和Minitest,还要构建新的Ruby测试运行器?
因为现有框架为单元测试设计,而浏览器测试需要跨浏览器执行、并行化和灵活fixture等特性,Smartest专为浏览器测试设计。
Smartest的fixture注入和传统的Ruby测试框架有什么不同?
Smartest采用pytest风格fixture注入,测试通过名称显式依赖并自动解析,支持fixture间依赖和清理,比RSpec的let/let!更清晰。
Smartest如何受Playwright Test启发?
Playwright Test证明浏览器测试需要专门的测试运行器,Smartest借鉴其设计理念,包括面向端到端测试的fixture模型和更好的隔离性。
版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。
文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容仅供参考,请以官方信息为准。
若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。 也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。



