serenity-js - 如何使用两个参数制作任务?
问题描述
在 Serenity-js 书中,我们有一个只有一个参数的任务示例:
// spec/screenplay/tasks/add_a_todo_item.ts
import { PerformsTasks, Task } from 'serenity-js/protractor';
export class AddATodoItem implements Task {
static called(itemName: string) { // static method to improve the readability
return new AddATodoItem(itemName);
}
performAs(actor: PerformsTasks): PromiseLike<void> { // required by the Task interface
return actor.attemptsTo( // delegates the work to lower-level tasks
// todo: interact with the UI
);
}
constructor(private itemName: string) { // constructor assigning the name of the item
// to a private field
}
想象一下,您可以添加一个 TodoItem 应该完成的日期。我们会收到一个日期参数,比如“截止日期”。我不知道该怎么做。
第一个想法:
构造函数:
constructor(private itemName: string, private deadline: Date) {
}
performAs:只需添加交互以键入截止日期
我们将有第二个静态方法。并且可能会更改被调用的方法返回。
感谢您的解释。
解决方案
有几种方法可以做到这一点,具体取决于哪些参数是强制性的,哪些是可选的,以及您希望任务拥有多少个参数。
无参数
如果您有一个没有参数的任务,定义它的更简单方法是使用Task.where
工厂函数:
import { Task } from '@serenity-js/core';
const Login = () => Task.where(`#actor logs in`,
Click.on(SubmitButton),
);
这与使用下面的类样式定义几乎相同,但代码要少得多:
class Login extends Task {
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in`;
}
}
一个参数
您可以将上述方法用于应接收一个参数的任务:
const LoginAs = (username: string) => Task.where(`#actor logs in as ${ username }`,
Enter.theValue(username).into(UsernameField),
Click.on(SubmitButton),
);
或者,您也可以按如下方式实现:
const Login = {
as: (username: string) => Task.where(`#actor logs in as ${ username }`,
Enter.theValue(username).into(UsernameField),
Click.on(SubmitButton),
),
}
我发现第二个版本更优雅,更符合内置交互,如Click.on
,Enter.theValue
等,因为您将调用Login.as
而不是LoginAs
在您的演员流程中。
N个参数
如果有超过 1 个参数,但所有参数都是必需的,并且您只是追求优雅的 DSL,则可以将上述模式扩展如下:
const Login = {
as: (username: string) => ({
identifiedBy: (password: string) => Task.where(`#actor logs in as ${ username }`,
Enter.theValue(username).into(...),
Enter.theValue(password).into(...),
Click.on(SubmitButton),
}),
}
然后,您将调用上述任务:
actor.attemptsTo(
Login.as(username).identifiedBy(password),
);
这种设计不是特别灵活,因为它不允许您更改参数的顺序(即您不能说Login.identifiedBy(password).as(username)
)或使某些参数可选,但以相对较少的实现工作为您提供了一个好看的 DSL。
更大的灵活性
如果您需要更大的灵活性,例如在某些参数是可选的情况下,您可能会选择类样式定义和准构建器模式。(我说“准”是因为它不会改变对象,而是产生新对象)。
例如,假设虽然系统要求提供用户名,但密码可能是可选的。
class Login extends Task {
static as(username: string) {
return new Login(username);
}
identifiedBy(password: string {
return new Login(this.username, password);
}
constructor(
private readonly username: string,
private readonly password: string = '',
) {
super();
}
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Enter.theValue(username).into(...),
Enter.theValue(password).into(...),
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in as ${ this.username }`;
}
}
当然,您可以更进一步,将实例化任务的行为与任务本身分离,如果不同的任务足够不同以证明单独的实现是合理的,这很有用:
export class Login {
static as(username: string) {
return new LoginWithUsernameOnly(username);
}
}
class LoginWithUsernameOnly extends Task {
constructor(
private readonly username: string,
) {
super();
}
identifiedBy(password: string {
return new LoginWithUsernameAndPassword(this.username, password);
}
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Enter.theValue(username).into(...),
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in as ${ this.username }`;
}
}
class LoginWithUsernameAndPassword extends Task {
constructor(
private readonly username: string,
private readonly username: string,
) {
super();
}
performAs(actor: PerformsTasks) {
return actor.attemptsTo(
Enter.theValue(this.username).into(...),
Enter.theValue(this.password).into(...),
Click.on(SubmitButton),
);
}
toString() {
return `#actor logs in as ${ this.username }`;
}
}
上述两种实现都允许您将任务称为Login.as(username)
和Login.as(username).identifiedBy(password)
,但第一种实现使用空字符串的默认值作为密码,而第二种实现甚至不触及密码字段。
我希望这有帮助!
简
推荐阅读
- bash - 将终端拆分为不同的选项卡
- jenkins - 如何获取与 jira 修复版本相关的詹金斯构建 url?
- html - 悬停时更改颜色无法正常工作
- c++ - 当我声明类时它总是显示构建失败
- windows - 在 Windows 上加载设备驱动程序的过程是什么?
- python-3.x - 为什么这个循环重复每个字符串多次?
- python - 使用 os.path.basename 返回模式和编码以及文件名提取文件名
- json - 如何使用 Postgres 在根级别更新多个 json 字段?
- r - 用 geom_point / ggmap / plot 在海岸线附近绘制点
- dart - 在地图中返回结果给键,需要访问值