为服务器集成测试保持外部依赖项更新

问题描述

tl;dr:为 Java 应用程序的存根网络调用维护(生成并保持最新)固定装置的最佳方法是什么?

我想单独测试服务器应用程序 - 特别是,我想排除所有(或至少大部分)对外部服务的 http 调用。这应该使测试运行得更快,更不容易受到外部故障(服务不可用等)的影响,并使我们能够更好地控制超时或 5xx 错误等测试场景(更不用说允许我们针对仍在设计中的 API 进行开发/发展)。

这样做的一个主要障碍是找到一种很好的、​​健壮的方式来生成和维护设备。理想情况下,我想对外部依赖项进行真正的调用,记录请求和响应,然后保留存根,以便它们可以用于标准的、非代理运行的测试。理想情况下,我希望这两个操作使用相同的测试代码,这样我就不必维护一个单独的测试套件来负责维护设备(这将主要复制实际测试的代码)。此外,在第一次生成存根后,我可能想对它们进行一些手动修改(例如,修复一些匹配器、在请求中将值注入响应等)。

最后一部分,这是真正棘手的部分:对于存根维护,我希望能够按需在代理模式下运行测试,并且 如果现有存根以有意义的方式不同,则更新它们来自新录制的存根。 “有意义的方式”意味着消息的内容/结构应该保持不变(例如,请求中的时间戳字段,我希望每次调用都不同)。

我已经能够使用 wiremock 弄清楚其中的大部分内容,并且查看 MockServer 似乎它支持内容wiremock 一样多,甚至更多。然而,问题出现在最后一部分,似乎他们都没有处理得特别好。下面我概述了我到目前为止所做的事情,以及我在哪些方面遇到了困难。

到目前为止的方法(使用 wiremock

首先,我有一个简单的 API /files,它有一个我想测试的 POST 端点。端点将请求对象作为输入,其中包含有关文件的一些元数据,并返回一个 JSON 正文,其中包含文件的标识符和用于上传文件的预签名 URL。

作为请求身份验证的一部分,我们调用外部身份验证服务以断言用户(由 cookie 标识)已登录获取有关用户的一些信息。

因此,代码看起来像这样(使用 JUnit 5、wiremock 和 REST-assured)(在其他地方,我将代码中的身份验证服务器请求重定向到 localhost:9090)。 :

@SpringBoottest(webEnvironment = SpringBoottest.WebEnvironment.RANDOM_PORT)
public class FilesApiTest {

    @LocalServerPort
    public int port;

    private wiremockServer wiremockServer;

    @BeforeEach
    public void setup() {
        RestAssured.port = port;
        wiremockServer = new wiremockServer(9090);
        wiremockServer.start();
        wiremock.configureFor(9090); // not sure why it doesn't work without this
    }

    @AfterEach
    public void takeDown() {
        wiremockServer.stop();
    }

    @Test
    public void test_files_create() throws Exception {
        given()
            .body(CreateFileRequest.builder()
                                   .fileName("fileName")
                                   .fileSize(1000L)
                                   .build())
            .cookie("authid","648e5a0d-4fd8-4465-9a76-90d3f4c85142")
            .contentType(ContentType.JSON)
        .when()
            .post(FilesController.PATH)
        .then()
            .body("fileGuid",hasLength(22),"uploadFileLocation",containsstring("https://"));
    }
}

好的开始,但是如果没有映射文件/stubFor 调用,身份验证请求就会失败,因为我们实际上并没有存根任何东西。因此,我通过将所有请求代理到外部 API 并记录/保留映射来生成一些存根文件

@Test
public void test_files_create() throws Exception {
    wiremockServer.startRecording("http://otherservice.com");
        
    given()
        ... // omitted for brevity
        
    wiremockServer.stopRecording();
}

现在我的 resources/mappings 文件夹中有一个映射文件。它看起来像这样:

{
  "id": "6a9e5e07-74b7-46d8-9c52-822e6b261518","insertionIndex": 1,"name": "login","persistent": true,"request": {
    "url": "/login","method": "POST","bodyPatterns": [{
      "equalToJson" : "{\"authid\":\"648e5a0d-4fd8-4465-9a76-90d3f4c85142\"}","ignoreArrayOrder" : true,"ignoreExtraElements" : true
    }]
  },"response": {
    "status": 200,"body": "{\"Uuid\":\"04a81164-91d7-4552-9afe-c7c15c1b7d0b\",\"authid\":\"648e5a0d-4fd8-4465-9a76-90d3f4c85142\"}","headers": {
      "Date": "Mon,19 Jul 2021 18:58:59 GMT","Content-Type": "application/json; charset=utf-8","Cache-Control": "no-cache","Pragma": "no-cache","Strict-Transport-Security": "max-age=16000000; includeSubDomains; preload;","x-frame-options": "SAMEORIGIN"
    }
  },"uuid": "0e0874c4-3ebd-435b-824c-0a7113664fdc"
}

修改了请求正文模式,因为传入的 cookie 是灵活的:

"bodyPatterns": [
  {
    "matchesJsonPath": "$.authid"
  }
]

并且我在请求 authid 中将响应正文修改为模板,因为它始终只输出输入的内容

"body": "{\"Uuid\":\"04a81164-91d7-4552-9afe-c7c15c1b7d0b\",\"authid\":\"{{jsonPath request.body '$.authid'}}\"}",

现在映射足够灵活,可以处理它应该处理的所有请求。此时,我有两个问题需要解决

  1. 我需要一种轻松配置录制/代理的方法,以便我可以打开和关闭它(需要更新存根时打开,正常运行测试时关闭
  2. 第二次运行当前测试会生成第二个映射文件,所以我需要找到一种方法来更新现有文件

一个可能可以通过几种方式解决。我采用了一种简单的方法,即注入布尔属性(由属性文件的 env 变量提供)并在启用代理的情况下运行记录。

@SpringBoottest(webEnvironment = SpringBoottest.WebEnvironment.RANDOM_PORT)
public class FilesApiTest {

    @Value("${wiremock.proxying}")
    public boolean proxyingEnabled;

    // omitted

    @Test
    public void test_files_create() throws Exception {
        if (proxyingEnabled) {
            wiremockServer.startRecording("http://otherservice.com")
        }

        given()
           // omitted

        if (proxyingEnabled) {
            wiremockServer.stopRecording();
        }
    }
}

非常基本的修复,但目前适用于我的用例。

第二个问题很难解决。到目前为止,我的一般方法认禁用持久映射,在记录停止后拉出现有映射,然后进行比较。如果发现差异,请使用 wiremockServer.editStubMapping() 用差异更新现有存根映射。但这感觉非常复杂,并且手挥动了实际困难的部分 - 做某种轻松的等于比较。目前,如果我直接比较这两个映射,会有一些差异:

  1. $.uuid 不同(由 wiremock 随机生成
  2. $.id 不同(由 wiremock 随机生成
  3. $.insertionIndex 是不同的(不太确定这有什么用)
  4. $.response.headers.Date 不同(请求的当前日期时间)
  5. $.request.bodyPatterns[0] 不同(因为它是手动修改的)
  6. $.response.body.authid 是不同的(因为抓取现有的映射不会注入任何东西,所以它是 jsonPath 注入器文字与实际 id 的比较。我想理想地将主体与所做的所有替换进行比较)立>

这是相当多的“预期差异”,其中一些是针对此请求的(尤其是 5 和 6)。所以我留下了如何在两个中等复杂对象之间进行松散比较的问题。不仅如此,我还需要制定一个足够健壮和干净的解决方案,以便我可以轻松地为每个 API 调用实现它。这可能是可行的,但感觉非常令人生畏,而且相当笨拙。特别是因为我的服务调用了一堆不同的服务,每个服务的比较可能略有不同。

我想相信有一种更简单的方法可以做到这一点,或者我在某个时候误入歧途,我完全错误地处理了这个问题。在这一点上,我真的很愿意接受任何形式的建议,因为我正在认真考虑,如果这会让人头疼的话,就放弃剔除外部依赖项。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...