Karate 使用空手道netty模拟复杂端点的最佳实践

Karate 使用空手道netty模拟复杂端点的最佳实践,karate,Karate,在空手道netty中构建复杂功能的最佳实践/建议是什么 我们需要编写一个相对复杂的模拟服务,以避免打击较低地区的集成合作伙伴。我们有大约8个端点,包括搜索、查找和结账漏斗后期的附加信息。我们需要做一些事情,比如对于id=7,返回这个有效载荷,但是对于id=8,返回那个有效载荷。空手道网很好地支持了这一切!然而,我们还有一个额外的要求,就是要紧密地反映错误行为——例如缺少auth头、负载的错误形状等等。通过这种条件/异常处理,我们很难在空手道netty中找到一个适合我们的范例 为了解释我的意思,假

在空手道netty中构建复杂功能的最佳实践/建议是什么

我们需要编写一个相对复杂的模拟服务,以避免打击较低地区的集成合作伙伴。我们有大约8个端点,包括搜索、查找和结账漏斗后期的附加信息。我们需要做一些事情,比如对于id=7,返回这个有效载荷,但是对于id=8,返回那个有效载荷。空手道网很好地支持了这一切!然而,我们还有一个额外的要求,就是要紧密地反映错误行为——例如缺少auth头、负载的错误形状等等。通过这种条件/异常处理,我们很难在空手道netty中找到一个适合我们的范例

为了解释我的意思,假设一个端点需要auth头、valid request.foo和valid request.bar。你可以这样写:

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !headerContains('Auth', 'secret')
  * call read('403.feature')

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !request.foo
  * call read('422.feature') { missing: 'foo' }

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !request.bar
  * call read('422.feature') { missing: 'bar' }

Scenario: pathMatches('ourEndpoint') && methodIs('post') && headerContains('Auth', 'secret') && request.foo && request.bar
  * call read('200.feature')
* def abortWithResponse = 
"""
  function(responseStatus, response) { 
    karate.set('response', response);
    karate.set('responseStatus', responseStatus);
    karate.abort();
  }
"""

Scenario: pathMatches('ourEndpoint') && methodIs('post')
  * if (!headerContains('Auth', 'secret')) abortWithResponse(403, read('403response.json'));
  * if (!request.foo) abortWithResponse(422, build422Response({missing: ['foo']));
  * if (!request.bar) abortWithResponse(422, build422Response({missing: ['bar']));
  * call read('200.feature')
对于这样的小示例,这是很好的,但是对于复杂端点,这种方法的问题是需要进行大量验证,您必须两次指定所有条件—一次在错误情况下,一次在200情况下。当一个端点有10种不同的场景时,这就不是最理想的了——尤其是200的情况变得非常笨拙。如果有类似于karate.abort()的东西,而不是中止一个场景,而是中止一个完整的功能,那就太好了——karate.abortFeature(),如果你愿意的话。然后,您只需指定一次错误条件,并简化200案例的条件,以简单地处理对该端点的每个以前未出错的请求,例如:

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !headerContains('Auth', 'secret')
  * call read('403.feature')
  * karate.abortFeature()

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !request.foo
  * call read('422.feature') { missing: 'foo' }
  * karate.abortFeature()

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !request.bar
  * call read('422.feature') { missing: 'bar' }
  * karate.abortFeature()

Scenario: pathMatches('ourEndpoint') && methodIs('post')
  * call read('200.feature')
如果有某种方法可以支持多个级别的过滤,那也很好;然后,我们可以拥有每个端点的功能文件,如下所示:

Feature: pathMatches('ourEndpoint') && methodIs('post')
  Scenario: !headerContains('Auth', 'secret')
    * call read('403.feature')
    * karate.abortFeature()

  Scenario: !request.foo
    * call read('422.feature') { missing: 'foo' }
    * karate.abortFeature()

  Scenario: !request.bar
    * call read('422.feature') { missing: 'bar' }
    * karate.abortFeature()

  Scenario:
    * call read('200.feature')
缺少上述内容,我们将所有条件和异常流拉入一个场景中,如下所示:

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !headerContains('Auth', 'secret')
  * call read('403.feature')

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !request.foo
  * call read('422.feature') { missing: 'foo' }

Scenario: pathMatches('ourEndpoint') && methodIs('post') && !request.bar
  * call read('422.feature') { missing: 'bar' }

Scenario: pathMatches('ourEndpoint') && methodIs('post') && headerContains('Auth', 'secret') && request.foo && request.bar
  * call read('200.feature')
* def abortWithResponse = 
"""
  function(responseStatus, response) { 
    karate.set('response', response);
    karate.set('responseStatus', responseStatus);
    karate.abort();
  }
"""

Scenario: pathMatches('ourEndpoint') && methodIs('post')
  * if (!headerContains('Auth', 'secret')) abortWithResponse(403, read('403response.json'));
  * if (!request.foo) abortWithResponse(422, build422Response({missing: ['foo']));
  * if (!request.bar) abortWithResponse(422, build422Response({missing: ['bar']));
  * call read('200.feature')
这是可行的,但感觉相当不雅,难以阅读。是否有人在使用不同的模式来实现这些目标?如果不是,添加karate.abortFeature()是否有用


提前谢谢

我实际上认为您的最后一个示例(单个场景)非常优雅:)

Fwiw,我的团队所做的通常是相反的——我们的一些模拟相当长,因为我们不介意包括一百万个场景,每个场景都有自己的一点变化(这个请求唯一的错误是这个特定的头,因此这是我们对该问题的非常特殊的响应),主要是因为用空手道编写服务器端场景非常容易

然而,我们倾向于利用一个或三个后台函数来处理一个请求必须通过的所有验证,以获得一个200。例如,我们可能需要在有效负载中看到5到10件东西。在这种情况下,我们可能会定义一些“payloadValidator”函数:

    * def payloadValidator =
      """
        function() {
          return karate.match("request contains{ id: '123', localTime: '045940', localDate: '1216', ref: 'A0A0' }").pass
        }
      """

…然后,我们只需在200响应场景的表达式中包含“payloadValidator()”。然后,下面的所有内容都可以一次专注于负载的一个特定问题(可以在一个或多个场景中利用IF语句之类的内容,如您所示,或者在多个场景中分散…您的选择).

正如另一个答案所说,我认为您的最后一个示例看起来不错,您可以使用这里建议的方法,即编写一个单一的、通用的、可重用的
validateRequest
函数,该函数将中止和/或返回错误消息

空手道的方法很有趣,因为你可以选择将你的服务器端边缘案例分解为
场景
-s,或者倾向于“真正的”服务器端模式,在那里你处理
获取/部分/资源
,然后使用(或重复使用)逻辑来处理边缘或错误案例

很高兴你已经想到你可以在任何时候做
karate.set('response',blah)
,这给了你很大的灵活性来有条件地返回错误消息或错误代码

也就是说,因为我个人认为在极端情况下,使用“适当”JavaScript的能力会很好,所以添加了另一种编写API模拟的方法。你可以找到一个例子。不幸的是,目前还没有文档,但我现在正在制作一个图像:

要查看这项工作,请克隆
develope
分支,并将
demo.ServerRunner
作为JUnit测试运行

看到这一点后,可能会发生两件事:

  • 相比之下,您可能更喜欢空手道模拟的现有“低代码”方式,而且它可能更容易理解和维护
  • 您看到了使用“纯JS”来表示模拟的价值,并且会对进一步探索这一点感兴趣,并提出更改或增强建议

我喜欢这个。如果需要,您甚至可以选择返回JSON而不是布尔值,以提供自定义错误消息。