Categories
程式開發

手把手教你编写HTTP文件测试HTTP API


手把手教你编写HTTP文件测试HTTP API 1

本文最初发布于Renato Athaydes的个人网站,经原作者授权由InfoQ中文站翻译并分享。

目前,为HTTP API编写自动化测试实在有点太复杂了。一般来说,你可以通过两种方法来解决这个问题:

  • 使用一个HTTP客户端用你喜欢的编程语言编写测试。
  • 使用Postman、Swagger Inspector和老一些的SoapUI等GUI工具。

我认为这些方法都不够理想。进一步来说,我认为这里有另一种解决方案,它在大多数情况下都好用得多,但知道的人却很少:那就是直接在一个文本文件中编写HTTP,同时添加一些人性化的便利内容,带来更愉快的测试体验。

在这篇文章中,我会阐述当前方法中存在哪些问题,以及为什么HTTP文件是一个很好的替代方案。

编写HTTP API测试时面临的问题

用你喜欢的编程语言编写测试时,你有无限的灵活性。基本上,你想到什么就能做什么,因此就灵活性而言,程序化测试绝对是最佳选择。

但这同时也是程序化测试的问题所在:由于它们太通用了,因此代码很难阅读和维护。如果测试足够复杂,甚至连程序要测试的是什么东西都很难搞清楚。

这是很常见的状况,而且往往会带来一个问题:测试程序本身由谁来测试呢?你能保证测试失败是由API中的错误引起的吗?如果失败是因为测试程序中的错误引起的呢?

程序化测试也很难编写。由于某些原因,大多数语言的HTTP客户端都比较笨拙,难以使用。举个简单的例子,下面是大多数Java开发人员都必须处理的事情:

class GitHubUser {
 
    private String login;
 
    // standard getters and setters
}

public class MyTest {
    @Test
    public void
      givenUserExists_whenUserInformationIsRetrieved_thenRetrievedResourceIsCorrect()
      throws ClientProtocolException, IOException {
      
        // Given
        HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );
     
        // When
        HttpResponse response = HttpClientBuilder.create().build().execute( request );
     
        // Then
        GitHubUser resource = RetrieveUtil.retrieveResourceFromResponse(
          response, GitHubUser.class);
        assertThat( "eugenp", Matchers.is( resource.getLogin() ) );
    }

    public static  T retrieveResourceFromResponse(HttpResponse response, Class clazz) 
      throws IOException {
      
        String jsonFromResponse = EntityUtils.toString(response.getEntity());
        ObjectMapper mapper = new ObjectMapper()
          .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper.readValue(jsonFromResponse, clazz);
    }

}

上面的例子来自Baeldung网站

也许你认为这看起来还行,但我可不这么认为。我希望后文的分析能让你明白其实还有好得多的方法。

基于GUI的测试工具

基于GUI的测试工具所面临的问题大都与程序化测试相反:相比后者的高度灵活性而言,前者的限制太多,只能执行工具作者认为用户需要的那些功能。

他们总是尝试从用户那里抽象出“困难”的概念,因此在编写请求时,你可能会有一张带有许多“参数”的小桌子。这些是查询参数吗?还是表单参数?也许它们实际上是JSON主体的一部分?如果有人在测试你的HTTP API,但无法分辨出它们之间的区别,那么你可能应该思考一下,他们要做的测试是不是那么可靠了。

这些工具通常都支持“脚本”来克服它们的某些局限,但如果你用了太多脚本,结果可能会同时陷进两个坑里:抽象一大堆,同时难以理解的代码也数不清,这些代码指不定会做什么事情。

基于GUI的工具还有一个缺陷,那就是它们很难与CI管道集成,并且在大多数情况下,一旦你想以这种方式使用它们就得掏钱了(或者你只需要一两项高级功能也得升级到专业版)。

鉴于此,我认为它们非常适合探索性测试,但不适用于在CI中运行的自动化测试(或作为开发人员常用测试套件的一部分)。

替代方案:HTTP文件

本质上,HTTP文件是一个文件,你可以在其中手工编写HTTP请求,使用一些小的代码段对运行请求时收到的响应做出断言,并在同一文件中设置可由后续请求使用的变量。

有两种非常相似但并不相同的HTTP文件格式:

它们都允许开发人员编写HTTP请求,并加入诸如自动完成、输入时高亮显示错误,以及在已知内容类型时使用颜色高亮显示HTTP响应正文之类的细节。

你可以把它看作是在你喜欢的IDE中编写HTTP“代码”。

手把手教你编写HTTP文件测试HTTP API 2

顺便说一句:HTTP最初被设计为用作分布式对象系统的一个接口。它易于理解,在不需要任何高级工具帮助的情况下也能手工编写。

如果你以前没有看过HTTP文件,请参考VS Code REST Client文档中的一个小示例:

GET https://example.com/comments/1 HTTP/1.1

###

GET https://example.com/topics/1 HTTP/1.1

###

POST https://example.com/comments HTTP/1.1
content-type: application/json

{
    "name": "sample",
    "time": "Wed, 21 Oct 2015 18:27:50 GMT"
}

这非常简单。只要你稍微了解一点HTTP(如果你正在测试HTTP API,那你肯定没问题),就会知道这是在做什么。最大的好处是:这些代码可以在VS Code Editor和IntelliJ IDEA Ultimate(以及终端或CI,后文详解)中运行,因为它们的格式非常相似!
再对比一下基于GUI的工具,在GUI工具中,你对HTTP的了解几乎毫无用处,因为你必须学习另一种事物才行:

手把手教你编写HTTP文件测试HTTP API 3

不幸的是,Jetbrains尚未在社区(免费)版本中提供HTTP文件支持,但希望在VS Code的竞争推动下,他们会在未来某个时候补上这个功能(我不知道他们是否有这个计划,只是希望他们能做到)!

VS Code REST Client格式比Jetbrains格式的功能更多一些,但截至本文撰写时,只有Jetbrains格式支持测试。Jetbrains格式的另一项优势是,它有一个名为HTTP Request in Editor的规范,我认为这是一个很棒的起点。将来这一规范可以用作通用HTTP测试格式的基础,真是激动人心。

因此我将重点介绍Jetbrains HTTP文件格式,用来说明我们该如何将HTTP文件用于测试。

使用Request-in-Editor格式测试HTTP API

我想明确一点,我与Jetbrains没有任何利益关系,推广它的产品也不会获得任何商业利益。我与Jetbrains的唯一关系是我的雇主为所有开发人员(包括我自己)支付IDEA许可证的费用,因此我可能被视为“间接客户”。

我认为Request-in-Editor格式(它的确应该改个顺口的名字)确实是一种非常适合测试HTTP API的东西。

首先,它使用了HTTP RFC本身描述的基本HTTP消息格式,但额外添加了一些内容,使其更易于手工编写。

例如,一个简单的HTTP请求能用一个简单的URL表示:

http://date.jsontest.com/

运行此文件会对该URL发出单个GET请求。你在这里需要知道的是:

  • 如果省略方法指定,则GET是默认方法。
  • 如果省略协议版本,则HTTP/1.1是默认协议版本。
  • 从URL推断出必需的Host标头。

因此,通过网络发送的完整HTTP请求如下所示:

GET / HTTP/1.1
Host: date.jsontest.com

请注意,URL scheme http不属于请求本身,因为它仅建立传输协议;并且由于HTTP的历史原因,CRLF被用作换行符。

我不知道,为什么程序化HTTP客户端和GUI测试工具对测试人员太难处理了,并试图将其抽象化。对我来说,“真实的东西”看起来更顺眼一些。

作为测试的示例,让我们在HTTP文件中实现前文的Java示例中所示的测试:

GET https://api.github.com/users/eugenp
Accept: application/vnd.github.v3+json
User-Agent: renatoathaydes

> {%
client.test('Given User Exists, ' + 
            'When User Information Is Retrieved, ' +
            'Then Retrieved Resource Is Correct',
  function() {
    client.assert(response.body.login === 'eugenp');
  });
%}

就该这么简单。

请注意,原始的Java示例实际上并未设置AcceptUser-Agent标头。上面的示例这样做是因为GitHub建议对所有请求都这样做(并且User-Agent现在是必需的)。

这个示例中有一些值得注意的细节:

  • 在HTTP请求后立即在<{%%}标记之间编写用于测试响应的代码。
  • 确切地说,代码是用JavaScript,ECMAScript5.1编写的。
  • 有一些内置函数可用于测试。
  • 由于响应的内容类型,request.body会自动解析为JSON。

可在这些代码段中使用的JavaScript API都列在了在IntelliJ文档中,并且在IntelliJ IDEA Ultimate中编写代码时也可以内联显示(对于Jetbrains官方的呼吁:请在社区版中提供此功能!!这不是什么“企业级”功能!),并带有自动补全。

这种格式的另一项重要特性是,你能使用变量并在响应句柄脚本和环境文件中指定它们,这些文件是用JSON编写的。

例如,你可以创建环境文件,这些文件必须称为http-client.env.jsonhttp-client.private.env.json(以保留机密数据),如下所示:

{
    "development": {
        "host": "localhost",
        "id-value": 12345,
        "username": "joe",
        "password": "123",
        "my-var": "my-dev-value"
    },

    "production": {
        "host": "example.com",
        "id-value": 6789,
        "username": "bob",
        "password": "345",
        "my-var": "my-prod-value"
    }
}

现在,你可以在HTTP文件中使用变量了:

GET http://{{host}}/api/json/get?id={{id-value}}
Authorization: Basic {{username}} {{password}}
Content-Type: application/json

{
"key": {{my-var}}
}

你还可以使用JavaScript设置变量:

client.globals.set('my-var', 100);

运行Request-in-Editor文件

显然,以Request-in-Editor格式运行HTTP文件的最简单方法是在IntelliJ IDEA Ultimate中运行。

运行时,它看起来就像是在IDE中运行单元测试一样:

手把手教你编写HTTP文件测试HTTP API 4

不幸的是,Jetbrains似乎没有制作一个独立的可执行文件来运行此类文件……但是我认为这种格式确实很酷,应该有一个可以让任何人以最小开销运行的runner实现,帮助你编写更好的HTTP。即便你没有许可证,API也会在命令行或CI中测试并执行它们。

因此,我编写了一个HTTP文件runner作为一个Java库,这是我自己的RawHTTP 项目(称为rawhttp-req-in-edit的一部分。

我还向RawHTTP CLI添加了run命令,这样你就可以执行能在IntelliJ中运行的任何HTTP文件。

现在要运行HTTP文件时,只需从终端运行以下命令:

rawhttp run my.http

如果要指定环境,请使用-e选项传递其名称:

rawhttp run my.http -e development

这些代码都是开源的,并在Apache2.0许可下发布!
我希望这有助于HTTP文件的普及,并希望这种方法能在业内流行开来,因为俗话说:我们应该使用合适的工具来完成工作!在我看来,HTTP文件应该是完成这一重要任务的最佳工具。

HTTP文件的缺点

尽管HTTP文件很棒,但我认为仍然需要解决一些问题才能让它们变得更好。

JavaScript响应处理程序

对于这种小型脚本来说,JS并不是一个糟糕的选项,但如果能有其他方法就更好了。

考虑到Java支持运行多种语言,这一缺陷应该很容易解决。

如果有兴趣,我可能会在自己的runner实现中添加对其他语言的支持。如果你有兴趣,请创建一个GitHub问题

另一方面,Jetbrains之所以为此使用了过时的ECMA版本,似乎只是因为他们受到了限制。我的猜测是,他们正在使用已过时的Nashorn引擎来运行JS代码。在更现代的引擎中(例如GraalVM的Polyglot框架),使用现代JavaScript和其他语言应该是非常容易的。

缺乏声明性断言

就像用HTTP编写HTTP比用其他任何一种编程语言编写HTTP都更顺手一样,用HTTP特定的断言语言编写声明性断言可能也比用JS代码来写更合适一些。

例如,我一直希望能够编写如下内容:

GET /resources/id-1
Host: mywebsite.com

### Expected Response
HTTP/1.1 200 .*
Content-Type: application/json
Content-Length: > 20

{{ body.id == 'id-1' ... }}

如果能编写这样的测试就太好了!我们只需要一种易于编写和理解的,在大多数情况下足够灵活,特定于HTTP的模式语言……就算它有什么缺陷,我们仍然可以回退到脚本上。
不过,我实在没时间和精力做这种事情了,如果有人有意愿的话请随时与我联系!我可能会提出一些想法并提供一些帮助!

实现中的错误

IntelliJ对HTTP文件的支持似乎还是Beta阶段一样……其中有一些明显的错误,当你开始编写更高级的文件时可能就会遇到它们。

希望随着这种格式的流行,官方会付出更多的努力来更好地支持它。

结论

我们测试HTTP API的方式应该比现在常用的方法更简单一些,我希望这篇文章至少能让一些人相信HTTP文件是一个好主意。

我希望本文能引起大家的兴趣,推动业界对此类文件制定标准,并在将来让VS Code、IntelliJ IDEA和其他工具都汇聚为一种格式,因为这将让所有人受益。

最后,虽然现在的情况还不够理想,但你也可以同时使用VS Code和IntelliJ编写HTTP文件,并且如果需要,可以使用RawHTTP的Java库或其CLI在CI中运行HTTP文件。

英文原文:

Writing HTTP files to test HTTP APIs