你是一个开发者,你想避免发送一个坏的登录体验,所以你要写一些测试来确保你不会这样做。让我们快速看一下这样一个表单的例子。

const form = (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="username">Username</label>
<input id="username" className="username-field" />
</div>
<div>
<label htmlFor="password">Password</label>
<input id="password" type="password" className="password-field" />
</div>
<div>
<button type="submit" className="btn">
Login
</button>
</div>
</form>
)
现在,如果我们要测试这个表单,我们要填入用户名、密码,并提交表单。为了正确地做到这一点,我们需要渲染表单,并查询文档以找到并操作这些节点。下面是你可能会尝试做的事情,以实现这一点。
const usernameField = rootNode.querySelector('.username-field')
const passwordField = rootNode.querySelector('.password-field')
const submitButton = rootNode.querySelector('.btn')
问题就出在这里了。如果我们在 "登录 "按钮之前添加一个 "注册 "按钮,会发生什么?
const form = (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="username">Username</label>
<input id="username" className="username-field" />
</div>
<div>
<label htmlFor="password">Password</label>
<input id="password" type="password" className="password-field" />
</div>
<div>
<button type="submit" className="btn">
Sign up
</button>
<button type="submit" className="btn">
Login
</button>
</div>
</form>
)
呼,这将破坏我们的测试。但这很容易解决,对吗?
// change this:
const submitButton = rootNode.querySelector('.btn')
// to this:
const submitButton = rootNode.querySelectorAll('.btn')[1]
然后我们就可以开始了!好吧,如果我们开始使用CSS-in-JS来设计我们的表单,不再需要username-field 和password-field 的类名,我们应该删除这些吗?或者我们保留它们,因为我们的测试使用了它们?Hmmmmmmm.....🤔
那么,我们如何编写有弹性的选择器呢?
鉴于"你的测试越像你的软件的使用方式,它们就越能给你带来信心",我们应该考虑这样一个事实,即我们的用户并不关心我们的类名是什么。
所以,让我们想象一下,你的团队里有一个手动测试员,你正在为他们写说明,让他们为你测试页面。这些说明会怎么说呢?
- 获得具有类名的元素
username-field - ...
"等等,"他们说。"我怎么才能找到类名为username-field 的元素呢?"
"哦,只要打开你的devtools,然后..."
"但是我们的用户不会这么做。为什么我不直接找到标签上写着username 的字段呢?"
"哦,是的,好主意。"
这就是为什么测试库有这样的查询功能。这些查询帮助你以用户找到它们的同样方式找到元素。这些查询允许你通过它们的角色、标签、占位符、文本内容、显示值、alt文本、标题、测试ID来查找元素。
这实际上是按推荐顺序排列的。这些方法当然有取舍,但如果你为一个使用这些查询的手动测试员写出说明,它看起来会是这样的:
- 在输入框中输入一个假的用户名
username - 在标记的输入框中输入一个假的密码
password - 点击有文字的按钮
sign in
const usernameField = rootNode.getByRole('textbox', {name: /username/i})
const passwordField = rootNode.getByLabelText('password')
const submitButton = rootNode.getByRole('button', {name: /sign in/i})
这将有助于确保你在测试你的软件时尽可能地接近它的使用方式。给你的测试带来更多的价值。
data-testid 查询是怎么回事?
有时你无法通过其他任何查询可靠地选择一个元素。对于这些,建议使用data-testid (尽管你要先确保你没有忘记使用一个合适的role 属性或其他东西)。
很多人碰到这种情况,会想为什么我们不包括getByClassName 查询。我不喜欢在选择器中使用类名的原因是,通常我们认为类名是一种风格化事物的方式。所以当我们开始添加一堆不是为了这个目的的类名时,就会使我们甚至 更难知道这些类名是用来做什么的,以及我们什么时候可以删除类名。
而如果我们简单地试图重复使用我们已经只是用于造型的类名,那么我们就会遇到像上面的按钮那样的问题。而任何时候,当你重构或增加一个功能时,你必须改变你的测试,这就表明了测试的脆性。核心问题是,测试和源代码之间的关系过于隐蔽。如果我们使这种关系更加明确,我们就可以克服这个问题**。**
如果我们能给我们要选择的元素添加一些元数据,那就能解决这个问题。那么,你猜怎么着?实际上有一个现成的API来解决这个问题!它就是data- 属性!比如说:
function UsernameDisplay({user}) {
return <strong data-testid="username">{user.username}</strong>
}
然后我们的测试可以说:
const usernameEl = getByTestId('username')
这对于端到端的测试也是很好的。所以我建议你也把它用在这方面。然而,有些人向我表达了对将这些属性运送到生产中的担忧。如果那是你,请真正考虑一下这对你来说是否真的是个问题(因为说实话,它可能没有你想的那么大的问题)。如果你真的想这样做,你可以用以下方法编译这些属性babel-plugin-react-remove-properties.
结论
你会发现,以类似于你的软件使用方式的方式测试你的应用程序,使你的测试不仅对变化更有弹性,而且还为你提供更多的价值。如果你想了解更多这方面的信息,那么我建议你在我的博文《测试实施细节》中阅读更多。
我希望这对你有帮助祝您好运!