Cucumber-js: 放宽步骤定义的参数检查的选项

创建于 2016-02-12  ·  14评论  ·  资料来源: cucumber/cucumber-js

嗨,虽然我确实理解对 step 参数进行严格检查的价值,但我也觉得有必要将这种控制传递回用户。

例如,在我的例子中,我想围绕步骤实现函数实现通用包装函数,我想在步骤调用之前和之后添加一些通用处理逻辑。 对于这个通用包装器,我只需要访问传递的任何参数,因此需要访问参数数组而不是声明任何显式参数。 目前,黄瓜会阻止我这样做,因为它会严格检查参数。

我想建议向选项对象添加另一个配置参数,可能称为skipStrictParameterCheck 之类的东西,如果未设置,则假定为false。 这样,对于大多数常见用法,默认行为将是严格检查,但对于其他想要使用该框架围绕它构建更多内容的人来说,它使他们能够灵活地利用 JavaScript 的一些动态功能。

所有14条评论

我提出了同样的要求,见 #445 :)

@riaan53 ,是的,我确实看过那个,不幸的是,由于某些正确的原因,该请求被拒绝了。 虽然我不反对这种推理,但我确实认为作为构建更大框架的一部分,只要用户不滥用此功能,此功能就很重要,因此我建议将其设为严格可选的配置。

您正在谈论的这种通用处理逻辑是什么?

当然。 在一个用例中,我希望所有开发人员都能够在他们的步骤定义中使用表达式引用。 例如“当用户 ${admin} 登录系统时”。

现在,在这种情况下, ${admin} 可能会从 JSON 文件中解析,并且在实现步骤定义时,解析的责任将由开发人员承担。 但是,如果您真的查看这种属性解析,则可以通过开发人员根本不知道它的通用代码来完成。

为了实现这一点,我可以轻松地围绕开发人员步骤实现创建一个通用函数包装器,它接受 Cucumber 注入的原始参数,解析它们,然后将解析的值注入到实际的步骤实现中。

目前,我无法编写这样一个通用函数,因为 Cucumber 的验证将确保该函数具有正确数量的参数,在我的情况下,这不会是因为我的通用包装器函数不接受命名参数,而我将使用“参数”对象。

希望我能说得通。 在我们当前的项目中还有其他用例需要围绕步骤实现的通用包装函数

如果我们实现了 step 参数转换会解决您的问题: https :

啊,是的,可能会解决参考分辨率问题。 但是,由于其他原因,我仍然需要在步骤实现周围放置一个通用包装器函数。

例如,一个原因是,我使用带有 Cucumber 的 Protractor 作为框架,在 Protractor 中,必须在 WebDriver 控制流中注册自定义承诺,以便在继续下一步之前努力等待这些自定义承诺得到解决。

虽然 Cucumber 确实等待 promise 得到解决(如果从 step 实现返回),但它显然不是 WebDriver 感知的,而且大多数情况下,我们需要向每个 step 实现添加额外的代码以将返回的 promise 注册到 WebDriver。

这再次是通过通用函数包装器解决的一件非常容易的事情,Cucumber 必须放宽参数检查。

我现在遇到过几次这个问题。

最突出的是在每个Then步骤中实现一个retry助手:

cucumber.Then = function(match, callback) {
  const retryingCallback = (...args) => retry(async () => await callback(...args));
  cucumber.Then(match, retryingCallback);
};

我使用这个助手来处理最终一致后端上的时间问题。 本质上,它每 x 秒执行一次回调,直到经过 y 秒或回调通过。

可悲的是,这导致

function has 0 arguments, should have 1 (if synchronous or returning a promise) or 2 (if accepting a callback)

我现在使用的替代方法是在每个Then步骤中调用帮助程序,这会导致大量代码重复。

this.Then(/^I see that "([^"]*)" does not have a destination$/, async clientName => {
  return retry(async () => {
    const client = await homeView.clientByName(clientName);
    expect(client.destinationName).to.not.exist;
  });
});

另一种情况是一个更简单的情况,我想要一个登录帮助函数。

function loggedIn(username, func) {
  return (...args) => {
    await accounts.login(username);
    return func(...args)
  };
}

this.Then(/^someone logged in as "([^"]*)" sees a destination named "([^"]*)"$/, loggedIn(assert.destinationExists));

这也可以为我节省大量的代码重复。

最后,我希望在某个时候添加一个运行我所有验收测试的套件,但在每次Then回调之前重新启动服务器(以确保服务器重新启动不会搞砸)。 再次,大量重复。

PS重试助手:

const patience = 250;
const interval = 5;

function delay(time) {
  return new Promise(function (fulfill) {
    setTimeout(fulfill, time);
  });
}

async function attempt(start, func) {
  const attemptDate = new Date();
  try {
    return await func();
  } catch (errr) {
    const timeElapsed = attemptDate.getTime() - start.getTime();
    if (timeElapsed < patience) {
      await delay(interval);
      return await attempt(start, func);
    } else {
      throw errr;
    }
  }
}

export async function retry(func) {
  const start = new Date();
  return await attempt(start, func);
}

_编辑_

试图破解它:

function splat(func) {
  return (one, two, three, four, five, six, seven, eight, nine, ten) => {
    if (typeof ten !== 'undefined') {
      return func(one, two, three, four, five, six, seven, eight, nine, ten);
    } else if (typeof nine !== 'undefined') {
      return func(one, two, three, four, five, six, seven, eight, nine);
    } else if (typeof eight !== 'undefined') {
      return func(one, two, three, four, five, six, seven, eight);
    } else if (typeof seven !== 'undefined') {
      return func(one, two, three, four, five, six, seven);
    } else if (typeof six !== 'undefined') {
      return func(one, two, three, four, five, six);
    } else if (typeof five !== 'undefined') {
      return func(one, two, three, four, five);
    } else if (typeof four !== 'undefined') {
      return func(one, two, three, four);
    } else if (typeof three !== 'undefined') {
      return func(one, two, three);
    } else if (typeof two !== 'undefined') {
      return func(one, two);
    } else if (typeof one !== 'undefined') {
      return func(one);
    } else {
      return func();
    }
  };
}

cucumber.Then = function(match, callback) {
  const retryingCallback = splat((...args) => retry(async () => await callback(...args)));
  cucumber.Then(match, retryingCallback);
};

function has 10 arguments, should have 1 (if synchronous or returning a promise) or 2 (if accepting a callback)

让我变成一只悲伤的熊猫

是一个包装函数以保留长度的示例。 如果只有一个微小的节点模块可以为您执行此操作,那就太好了。

谢谢你的建议! 如果您指定每个步骤定义的参数数量,那会起作用,对吗?

例如。

this.Then(/^someone logged in as "([^"]*)" sees a destination named "([^"]*)"$/, createProxy(loggedIn(assert.destinationExists), 2));

对我来说最紧迫的问题是无法为多个步骤定义添加通用中间件。 如果我把它变成一个允许中间件注册的对象,然后告诉它每个步骤定义的参数数量,像createProxy这样的东西可能会起作用。 (仔细看我的第一个例子,你会发现我不能直接使用createProxy ,因为retry函数会包装它。它应该是相反的,但是createProxy不知道每个回调的参数数量)

与能够关闭错误相比,仍然感觉非常尴尬。 :清白的:

我认为您可以使用该函数的变体,而不是传入proxyLength您只需传入您正在包装的函数并使用function.length

很好的建议,谢谢!

我有类似以下的工作:

cucumber.Then = function(match, callback) {
  cucumber.Then(match, retryProxy(callback));
};

function retryProxy(func) {
  const numberOfArgs = func.length;
  switch (numberOfArgs) {
    case 0: return () => retry(func);
    case 1: return (a) => retry(func, a);
    case 2: return (a, b) => retry(func, a, b);
    case 3: return (a, b, c) => retry(func, a, b, c);
    case 4: return (a, b, c, d) => retry(func, a, b, c, d);
    case 5: return (a, b, c, d, e) => retry(func, a, b, c, d, e);
  }
}

它没有解决的两件事是登录帮助程序案例和允许默认参数,但我可以解决这两个问题。

很高兴我现在可以添加中间件而无需调整我的步骤定义!

@thomasvanlankveld正如你所知,我发现了这个库,它包装了一个函数来给它一个特定的函数长度: https :

@sushil-rxr 你能不能在你的通用函数包装器中保留原始函数长度?

2.0.0-rc.1您现在可以添加通用函数包装器。 它还具有保留原始函数长度的内置功能。

由于关闭后没有任何近期活动,因此该线程已自动锁定。 请为相关错误打开一个新问题。

此页面是否有帮助?
0 / 5 - 0 等级