在Serenity的书中,我们有一个只有一个参数的任务示例:
// 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:只需添加交互以键入截止日期即可
我们会有第二种静态方法。并且可能会更改被调用的方法返回。
谢谢你的解释。
发布于 2020-01-18 00:26:26
有几种方法可以做到这一点,这取决于哪些参数是强制性的,哪些参数是可选的,以及您希望任务拥有多少参数。
无参数
如果您有一个没有参数的任务,那么定义它的更简单的方法是使用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参数
如果有一个以上的参数,但所有参数都是必需的,并且您只是在追求一个优雅的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),但是第一个实现对密码使用一个空字符串的默认值,而第二个实现甚至不触及密码字段。
我希望这能帮到你!
1月
https://stackoverflow.com/questions/58558314
复制相似问题