如何使用两个参数创建任务?
How do I make a Task with two parameters?
在 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 应该完成的日期。我们会收到一个日期参数,比如 'deadline'。我不知道该怎么做。
第一想法:
构造函数:
constructor(private itemName: string, private deadline: Date) {
}
performAs:只需添加交互以输入截止日期
我们会有第二个静态方法。
并且可能调用的方法 return 会被更改。
感谢您的解释。
有几种方法可以做到这一点,具体取决于哪些参数是必需的,哪些是可选的,以及您希望任务拥有多少参数。
无参数
如果您有一个没有参数的任务,定义它的更简单方法是使用 Task.where
factory function:
import { Task } from '@serenity-js/core';
const Login = () => Task.where(`#actor logs in`,
Click.on(SubmitButton),
);
这 几乎 与使用下面的 class 样式定义相同,但代码少得多:
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 样式定义和准 builder pattern。 (我说 "quasi" 因为它不会改变对象,而是产生新的对象)。
例如,假设系统要求提供用户名,而密码可能是可选的。
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)
,但是第一个实现使用空字符串的默认值作为密码,第二个实现则不甚至不要触摸密码字段。
希望对您有所帮助!
一月
在 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 应该完成的日期。我们会收到一个日期参数,比如 'deadline'。我不知道该怎么做。
第一想法:
构造函数:
constructor(private itemName: string, private deadline: Date) {
}
performAs:只需添加交互以输入截止日期
我们会有第二个静态方法。 并且可能调用的方法 return 会被更改。
感谢您的解释。
有几种方法可以做到这一点,具体取决于哪些参数是必需的,哪些是可选的,以及您希望任务拥有多少参数。
无参数
如果您有一个没有参数的任务,定义它的更简单方法是使用 Task.where
factory function:
import { Task } from '@serenity-js/core';
const Login = () => Task.where(`#actor logs in`,
Click.on(SubmitButton),
);
这 几乎 与使用下面的 class 样式定义相同,但代码少得多:
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 样式定义和准 builder pattern。 (我说 "quasi" 因为它不会改变对象,而是产生新的对象)。
例如,假设系统要求提供用户名,而密码可能是可选的。
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)
,但是第一个实现使用空字符串的默认值作为密码,第二个实现则不甚至不要触摸密码字段。
希望对您有所帮助!
一月