vlambda博客
学习文章列表

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

Chapter 6. Testing and Documenting Node.js Microservices

到目前为止,我们所做的只是开发微服务并围绕构建软件组件的过程讨论框架。现在是测试所有这些的时候了。测试是验证已构建软件的活动。验证是一个非常广泛的术语。在本章中,我们将学习如何测试微服务,不仅从功能的角度来看,还将学习如何测试应用程序的性能,以及与不同模块的集成等其他方面。我们还将使用 Node.js 构建一个代理,以帮助我们检查服务的输入和输出,以便我们可以验证我们设计的内容是否实际发生,并再次确保语言的多功能性,例如 JavaScript ,以快速原型功能。

如今,通过 A/B 测试发布功能也是一种趋势,我们只为特定类型的用户启用功能,然后我们收集指标以查看系统更改的执行情况。在本章中,我们将构建一个微服务,让我们能够以可控的方式推出功能。

另一方面,我们将记录我们的应用程序,不幸的是,这是传统软件开发中被遗忘的活动:我还没有找到一家公司的文档可以 100% 地捕获新开发人员所需的信息。

我们将在本章中介绍以下主题:

  • 功能测试:在本节中,我们将学习如何测试微服务以及什么是好的测试策略。我们还将学习一个名为 Postman 的工具来手动测试我们的 API,以及使用 Node.js 构建代理来监视我们的连接。

  • 记录微服务:我们将学习如何使用 Swagger 使用开放 API 标准记录我们的微服务。我们还将使用开源工具从 YAML 定义中生成代码。

Functional testing


测试通常是一项耗时的活动,在构建软件时没有得到所有必要的关注。

想想一家公司是如何发展的:

  1. 有人想出一个主意。

  2. 一些工程师/产品人员构建系统。

  3. 公司进入市场。

没有时间测试超过最低要求的手动测试。尤其是,当有人在 Internet 上读到正确的测试可能会占用您 40% 的开发时间时,常识又一次失效了。

自动化很好,单元、集成和端到端测试是自动化的一种形式。通过让计算机测试我们的软件,我们大大减少了验证软件所需的人力。

想想软件是如何开发的。尽管我们公司喜欢声称我们是敏捷的,但事实是每个软件项目都有一定程度的迭代开发,而测试是一种每个周期的一部分,但通常,它被忽略以支持提供新功能。

通过自动化大部分(或大部分)测试,我们可以节省资金,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

成本和迭代

如果做得对,测试实际上可以节省成本,关键是做得对,这并不总是那么容易。多少测试是太多测试?我们应该覆盖应用程序的每个角落吗?我们真的需要深度性能测试吗?

这些问题通常会导致不同的意见流,有趣的是没有单一的事实来源。这取决于您的系统的性质。

在本章中,我们将学习一组广泛的测试技术,这并不意味着我们应该将所有这些技术都包含在我们的测试计划中,但至少我们会了解测试方法。

在过去的七 年中,Ruby on Rails 创造了向 a 的巨大趋势新范式,称为 测试驱动开发 (TDD),直到现在,大多数新的开发平台都是在考虑 TDD 的情况下构建的。

就个人而言,我不是 TDD 的狂热采用者,但我喜欢接受好的部分。在开发之前规划测试有助于创建具有适当内聚水平的模块并定义清晰且易于测试的界面。在本章中,我们不会深入介绍 TDD,但我们会多次提及它并解释如何将公开的技术应用到 TDD 测试计划中。

The pyramid of automated testing

如何制定您的测试计划是一个棘手的问题。不管你做什么,你总是会觉得这是完全错误的

在深入了解 之前,让我们从功能的角度定义我们将要处理的不同类型的测试,它们应该是什么专为。

Unit tests

单元测试是一种测试,它覆盖应用程序的各个部分,而无需考虑到与不同模块的集成。它也被称为白盒测试,因为目的是覆盖和验证尽可能多的分支。

通常,衡量我们测试质量的方法是测试覆盖率,它以百分比来衡量。如果我们的代码跨越十个分支并且我们的测试覆盖了七个分支,那么我们的代码覆盖率为 70%。这很好地表明了我们的测试覆盖率有多可靠。但是,这可能会产生误导,因为测试可能存在缺陷,或者即使所有分支都经过测试,不同的输入也会导致测试未捕获的不同输出。

在单元测试中,由于我们不与其他模块交互,我们将大量使用模拟和存根来模拟来自第三方的响应——派对系统和 控制流程以到达所需的分支。

Integration tests

集成测试,正如名字所暗示的,是旨在验证我们的模块在应用环境中的集成。它们的目的不是测试我们代码的分支,而是业务部门,我们将在其中将数据保存到 数据库中,调用第三方 Web 服务或我们架构的其他微服务。

这些测试是检查我们的服务是否按预期运行的完美工具,有时可能难以维护(通常情况下)。

在我多年的经验中,我还没有找到一家可以正确完成集成测试的公司,这有很多原因,如下表所述:

  • 一些公司认为集成测试很昂贵(确实如此),因为它需要额外的资源(例如数据库和额外的机器)

  • 其他一些公司试图仅通过单元测试来涵盖所有业务案例,这取决于业务案例,可能会起作用,但这远非理想,因为单元测试会做出假设(模拟),这可能会让我们对我们的测试套件产生错误的信心

  • 有时,集成测试用于验证代码分支,就像它们是单元测试一样,这很耗时,因为您需要制定环境以使集成测试命中所需的分支

无论您想变得多么聪明,集成测试都是您想做的事情,因为它是我们软件中防止集成错误发布到生产中的第一个真正障碍。

End-to-end tests

在这里,我们将演示 我们的应用程序确实有效。在集成测试中,我们在代码级别调用服务。这意味着我们需要构建服务的上下文,然后发出调用。

端到端测试的不同之处在于,在端到端测试中,我们实际上完全部署了我们的应用程序并发出执行所需的调用目标代码。然而,很多时候,工程师可以决定将两种类型的测试(集成和端到端测试)捆绑在一起,因为现代框架允许我们像集成测试一样快速运行 E2E 测试。

作为集成测试,端到端测试的目标不是测试应用程序的所有路径,而是测试用例。

在端到端测试中,我们可以找到几种不同的测试模式(范式),如下所示:

  • 我们可以测试我们的 API 发出 JSON 请求(或其他类型的请求)

  • 我们可以使用 Selenium 来测试我们的 UI 来模拟 DOM 上的点击

  • 我们可以使用一种称为行为驱动开发BDD) 测试,其中 用例映射到我们应用程序中的操作(点击 UI、API 中的请求等)并执行构建应用程序的用例

端到端测试通常非常脆弱,而且很容易被破坏。根据我们的应用程序,我们可能会对这些测试感到放松,因为成本价值比非常低,但我仍然建议让其中一些测试至少涵盖最基本和最基本的流程。

How much testing is too much?

诸如以下 之类的问题并不容易回答,尤其是在快节奏的企业中,例如初创公司:

  • 我们有太多的集成测试吗?

  • 我们是否应该以 100% 的单元测试覆盖率为目标?

  • 如果 Selenium 测试无缘无故地每隔一天中断一次,为什么还要打扰它们呢?

总有妥协。测试覆盖率与所消耗的时间,而且,这些问题没有简单而单一的答案。

这些年来我发现的唯一有用的指导方针是测试界所说的测试金字塔,即如下图所示。如果你想一下,在你之前工作的项目中,你总共做了多少测试?其中有多少百分比是集成测试和单元测试?端到端测试呢?

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

测试金字塔

前面的金字塔显示了这些问题的答案。在一个健康的测试计划中,我们应该有很多单元测试:一些集成测试和很少的 E2E 测试。

原因很简单,大部分问题都可以在单元测试中发现。访问我们代码的不同分支将验证我们应用程序中几乎每个功能案例的功能,因此在我们的测试计划中包含大量它们是有意义的。根据我的经验,在一个平衡的测试计划中,我们大约 70% 的测试应该是单元测试。但是,在面向微服务的架构中,尤其是使用诸如 Node.js 之类的动态语言时,这个数字很容易下降,并且在我们的测试中仍然有效.其背后的原因是 Node.js 允许您非常快速地编写集成测试,以便我们可以通过集成测试来替换一些单元测试。

Tip

测试是一个有据可查的复杂过程。试图超越现有的方法可能会导致难以维护和难以信任的测试套件。

集成测试负责捕捉集成问题,如下所示:

  • 我们的代码可以调用短信网关吗?

  • 与数据库的连接是否正常?

  • HTTP 标头是从我们的服务发送的吗?

同样,根据我的经验,大约 20% 的 测试应该是集成测试;专注于依赖第三方模块的正面流量和一些负面流量。

当涉及到端到端测试时,它们应该非常有限,并且只测试应用程序的主要流程,而不涉及太多细节。这些细节应该已经被单元和集成测试捕获,在发生故障时很容易修复。但是,这里有一个问题:在 Node.js 中测试微服务时,90% 的时间,集成和 E2E 测试可以是一回事。由于 Node.js 的动态特性,我们可以从集成的角度(整个服务器运行)测试其余的 API,但实际上,我们还将测试我们的代码在与其他模块集成时的行为方式。我们将在本章后面看到一个例子。

Testing microservices in Node.js

Node.js 是一种令人印象深刻的语言。 围绕开发的任何一个方面的库的数量是惊人的。无论你想在 Node.js 中实现的任务多么奇怪,总会有一个 npm 模块。

关于测试,Node.js 有一组非常强大的库,但其中两个特别受欢迎:Mocha

它们几乎是应用测试的 行业标准,并且维护得非常好,并且 升级。

另一个有趣的库叫做 Sinon.JS,它用于模拟,间谍和存根方法。我们将在接下来的部分中回到这些概念,但是这个库基本上是用来模拟与第三方的集成而不与他们交互。

Chai

该库是一个 BDD/TDD 断言库,可以与任何其他库结合使用以创建 高质量测试。

断言是一个代码语句,将被执行或抛出错误,停止测试并将其标记为失败:

5 should be equal to A

当变量 A 包含值 时,上述语句将 正确5 。这是编写易于理解的测试的非常强大的工具,尤其是使用 Chai,我们可以使用以下三个不同的接口访问断言:

  • 应该

  • 期望

  • 断言

归根结底,可以使用单个接口检查每个条件,但是库为我们提供了如此丰富的接口这一事实促进了测试的冗长性,以便编写干净、简单和可维护的测试。

让我们安装库:

npm install chai

这将产生以下输出:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

这意味着 Chai 依赖于 assertion-errortype-detectdeep-eql 。如您所见,这是一个很好的迹象,表明我们将能够通过简单的指令检查复杂的语句,例如对象中的深度相等或类型匹配。

像 Chai 这样的测试库不是我们应用程序的直接依赖,而是开发依赖。我们需要它们来开发应用程序,但不应将它们运送到生产环境中。这是重构我们的 package.json 并在 devDependencies 依赖标签中添加 Chai 的一个很好的理由,如下所示:

{
  "name": "chai-test",
  "version": "1.0.0",
  "description": "A test script",
  "main": "chai.js",
  "dependencies": {
  },
  "devDependencies": {
    "chai": "*"
  },
  "author": "David Gonzalez",
  "license": "ISC"
}

这将阻止我们的软件运送到 Chai 等生产库中,这与我们应用程序的 操作无关。

一旦我们安装了 Chai,我们就可以开始使用这些界面了。

BDD-style interfaces

Chai 带有两种 风格的 BDD 接口。使用哪一个是一个偏好问题,但我个人的建议是使用让你在任何情况下都感觉更舒服的那个。

让我们从应该 接口开始。这是一个 BDD 风格的接口,使用类似于自然语言的东西,我们可以创建断言来决定我们的测试是成功还是失败:

myVar.should.be.a('string')

为了能够像之前那样构建句子,我们需要在我们的程序中导入 should 模块:

var chai = require('chai');

chai.should();

var foo = "Hello world";
console.log(foo);

foo.should.equal('Hello world');

虽然它看起来有点像黑魔法,但在测试我们的代码时确实很方便,因为我们使用类似于自然语言的东西来确保我们的代码符合某些标准:foo 应该be equal to 'Hello world' 可以直接翻译到我们的测试。

Chai 提供的第二个 BDD 风格的接口是 expect。尽管它与 should 非常相似,但它更改了一些语法以设置结果必须满足的期望。

让我们看看下面的例子:

var expect = require('chai').expect;

var foo = "Hello world";

expect(foo).to.equal("Hello world");

如您所见,风格非常相似:一个流畅的界面,可以让我们检查是否满足测试成功的条件,但是如果不满足条件会怎样?

让我们执行一个在 条件之一中失败的简单 Node.js 程序:

var expect = require('chai').expect;
var animals = ['cat', 'dog', 'parrot'];
expect(animals).to.have.length(4);

现在,让我们执行前面的脚本,假设您已经安装了 Chai:

code/node_modules/chai/lib/chai/assertion.js:107
      throw new AssertionError(msg, {
            ^
AssertionError: expected [ 'cat', 'dog', 'parrot' ] to have a length of 4 but got 3
    at Object.<anonymous> (/Users/dgonzalez/Dropbox/Microservices with Node/Writing Bundle/Chapter 6/code/chai.js:24:25)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

抛出异常并且测试失败。如果所有条件都得到验证,则不会引发异常并且测试会成功。

如您所见,我们可以通过 expectshould 接口将许多自然语言单词用于我们的测试.完整列表可在 Chai 文档中找到 (http:// chaijs.com/api/bdd/#-include-value-),但让我们在以下列表中解释一些最有趣的:

  • not:这个词用于否定链中的断言。例如, expect("some string").to.not.equal("Other String") 将通过。

  • deep:这个词是所有集合中最有趣的词之一。它用于深度比较对象,这是进行完全相等比较的最快方法。例如,如果 fooexpect(foo).to.deep.equal({name: "David"}) 将成功是一个 JavaScript 对象,具有一个名为 name 的属性,该属性具有 "David" 字符串值。

  • any/all:这用于检查字典或对象是否包含给定列表中的任何键,以便 < code class="literal">expect(foo).to.have.any.keys("name", "surname") 如果 foo 包含任何给定的键,并且 expect(foo).to.have.all.keys("name", "surname") 只有在它拥有所有键的情况下才会成功。

  • ok:这是一个有趣的。您可能知道,JavaScript 有一些陷阱,其中之一就是表达式的真/假评估。使用 ok,我们可以抽象出所有的混乱并做一些类似于 到下面的表达式列表:

    • expect('everything').to.be.ok: 'everything' 是一个字符串,它将被评估为 < code class="literal">ok

    • expect(undefined).to.not.be.ok: undefined 在 JavaScript 世界里是不行的,所以这个断言会成功

  • above:这是一个非常有用的词,用于检查数组或集合是否包含超过某个阈值的多个元素,如下:expect( [1,2,3]).to.have.length.above(2)

如您所见,流畅断言的 Chai API 非常丰富,使我们能够编写非常易于维护的描述性测试。

现在,您可能会问自己,为什么同一界面的两种风格几乎相同?好吧,它们在功能上是一样的,但是,看看细节:

  • expect 为您的可链接语言提供了一个起点

  • should 扩展 Object.prototype 签名以将可链接语言添加到 JavaScript 中的每个对象

从 Node.js 的角度来看,它们都很好,尽管 should 正在检测 Object 的原型可能是对使用它有点偏执的原因,因为它具有侵入性。

Assertions interface

assertions 接口匹配最常见的老式测试断言库。在这种风格中,我们需要具体说明我们想要测试的内容,并且不存在流畅的表达式链接这样的事情:

var assert = require('chai').assert;
var myStringVar = 'Here is my string';
// No message:
assert.typeOf(myStringVar, 'string');
// With message:
assert.typeOf(myStringVar, 'string', 'myStringVar is not string type.');
// Asserting on length:
assert.lengthOf(myStringVar, 17);

如果您已经使用过任何 任何语言的现有测试库。

Mocha

在我的意见中,Mocha 是我在职业生涯中使用过的最方便的测试框架之一。它遵循行为驱动 开发测试 (BDDT),其中测试描述应用程序的用例并使用来自另一个库的断言来验证执行代码的结果。

虽然听起来有点复杂,但确保从功能和技术角度涵盖我们的代码确实很方便,因为我们将镜像用于将应用程序构建到验证它们的自动化测试中的需求。

让我们从一个简单的例子开始。 Mocha 与任何其他库都有一点不同,因为它定义了自己的 领域特定语言 ( DSL),需要使用 Mocha 而不是 Node.js 执行。它是语言的扩展。

首先我们需要在系统中安装 Mocha:

npm install mocha -g

这将产生类似于下图的输出:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

从现在开始,我们的系统中有一个新命令:mocha

下一步是使用 Mocha 编写测试:

function rollDice() {
  return Math.floor(Math.random() * 6) + 1;
}

require('chai').should();
var expect = require('chai').expect;

describe('When a customer rolls a dice', function(){

  it('should return an integer number', function() {
    expect(rollDice()).to.be.an('number');
  });

  it('should get a number below 7', function(){
    rollDice().should.be.below(7);
  });

  it('should get a number bigger than 0', function(){
    rollDice().should.be.above(0);
  });

  it('should not be null', function() {
    expect(rollDice()).to.not.be.null;
  });

  it('should not be undefined', function() {
    expect(rollDice()).to.not.be.undefined;
  });
});

前面的例子很简单。掷骰子并返回从 16 的整数的函数。现在我们需要考虑一下用例和需求:

  • 数字必须是整数

  • 这个整数必须小于 7

  • 它必须大于 0,骰子没有负数

  • 函数不能返回 null

  • 函数不能返回 undefined

这几乎涵盖了关于在 Node.js 中掷骰子的所有极端案例。我们正在做的是描述我们当然想要测试的情况,以便在不破坏现有功能的情况下安全地对软件进行更改。

这五个用例是与之前编写的测试的精确映射:

  • 我们描述的情况当客户掷骰子时

  • 条件得到验证它应该返回一个整数

让我们运行之前的测试并检查结果:

mocha tests.js

这应该返回类似于以下屏幕截图的内容:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

如您所见,Mocha 会返回一份关于测试情况的​​综合报告。在这种情况下,它们都通过了,所以我们不需要担心问题。

让我们强制一些测试失败:

function rollDice() {
  return -1 * Math.floor(Math.random() * 6) + 1;
}

require('chai').should();
var expect = require('chai').expect;

describe('When a customer rolls a dice', function(){

  it('should return an integer number', function() {
    expect(rollDice()).to.be.an('number');
  });

  it('should get a number below 7', function(){
    rollDice().should.be.below(7);
  });

  it('should get a number bigger than 0', function(){
    rollDice().should.be.above(0);
  });

  it('should not be null', function() {
    expect(rollDice()).to.not.be.null;
  });

  it('should not be undefined', function() {
    expect(rollDice()).to.not.be.undefined;
  });
});

不小心,有人在 rollDice() 函数中插入了一个代码片段,这使得 函数返回一个不符合某些要求的号码。让我们再次运行 Mocha,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

现在,我们可以看到报告返回一个错误:该方法返回 -4,它应该总是返回一个大于 0

此外,使用 Mocha 和 Chai 在 Node.js 中进行此类测试的好处之一是时间。测试运行非常快,因此如果我们有问题,很容易收到反馈。前面的套件在 10ms 内运行。

Sinon.JS – a mocking framework

前两章专注于断言函数返回值的条件,但是当我们的函数没有返回任何值时会发生什么?唯一正确的测量是检查该方法是否被调用。另外,如果我们的某个模块正在调用第三方 Web 服务,但我们不希望我们的测试调用远程服务器怎么办?

为了回答这些问题,我们有两个概念工具,称为 mocks 和 spies,Node.js 有一个完美的库来实现它们:Sinon.JS。

首先安装它,如下:

npm install sinon

上述命令应产生以下输出:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

现在让我们通过一个例子来解释它是如何工作的:

function calculateHypotenuse(x, y, callback) {
  callback(null, Math.sqrt(x*x + y*x));
}

calculateHypotenuse(3, 3, function(err, result){
  console.log(result);
});

这个简单的脚本计算三角形的斜边,给定三角形其他两侧的长度。我们要进行的测试之一是回调执行并提供正确的参数列表。我们需要完成这样的任务是 Sinon.JS 所谓的间谍:

var sinon = require('sinon');

require('chai').should();

function calculateHypotenuse(x, y, callback) {
  callback(null, Math.sqrt(x*x + y*x));
}

describe("When the user calculates the hypotenuse", function(){
  it("should execute the callback passed as argument", function() {
    var callback = sinon.spy();
    calculateHypotenuse(3, 3, callback);
    callback.called.should.be.true;
  });
});

再次,我们使用 Mocha 运行脚本和 Chai 通过 should 接口验证测试中的结果,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

前面脚本中的重要行是:

var callback = sinon.spy();

在这里,我们正在创建 spy 并将其作为回调注入到函数中。 Sinon.JS创建的这个函数实际上不仅仅是一个函数,而是一个包含一些有趣信息点的完整对象。 Sinon.JS 利用 JavaScript 的动态特性做到了这一点。通过使用 console.log() 将其转储到控制台中,您实际上可以查看该对象中的内容。

Sinon.JS 中另一个非常强大的工具是存根。 存根与模拟非常相似(在 JavaScript 中的实际 效果相同)并允许我们伪造函数来模拟所需的返回:

var sinon = require('sinon');
var expect = require('chai').expect;

function rollDice() {
  return -1 * Math.floor(Math.random() * 6) + 1;
}
describe("When rollDice gets called", function() {
  it("Math#random should be called with no arguments", function() {
    sinon.stub(Math, "random");
    rollDice();
    console.log(Math.random.calledWith());
  });
})

在这种情况下,我们对 Math#random 方法进行了存根,这导致该方法成为某种重载的空函数(它不会发出 get 调用),它记录统计信息它被称为什么或如何被称为。

前面的代码有一个问题:我们从来没有恢复 random() 方法,这是非常危险的。它有一个巨大的副作用,因为其他测试会将 Math#random 方法视为存根,而不是原始方法,它可能导致我们根据无效信息。

为了防止这种情况,我们需要使用before()after() 方法:

var sinon = require('sinon');
var expect = require('chai').expect;

var sinon = require('sinon');
var expect = require('chai').expect;

function rollDice() {
  return -1 * Math.floor(Math.random() * 6) + 1;
}
describe("When rollDice gets called", function() {
  
  it("Math#random should be called with no arguments", function() {
    sinon.stub(Math, "random");
    rollDice();
    console.log(Math.random.calledWith());
  });
after(function(){
    Math.random.restore();
  });
});

如果您注意突出显示的代码,我们会告诉 Sinon.JS 恢复在 it 块之一中存根的原始方法,这样如果另一个 describe 块使用 http.get,我们 不会见存根,但原来的方法。

Tip

before()after() 方法对于设置和结束测试上下文非常有帮助。但是,您需要注意它们的执行范围,因为它可能导致测试交互。

摩卡前后有几种口味:

  • before(callback):在当前作用域之前执行(在前面代码中describe块的开头)

  • after(callback):在当前作用域之后执行(在前面代码中 describe 块的末尾)

  • beforeEach(callback):在当前作用域中每个元素的开头执行(在前面示例中的每个 it 之前)

  • afterEach(callback):在当前作用域中的每个元素的末尾执行(在前面示例中的每个 it 之后)

Sinon.JS 中另一个有趣的特性是时间操作。一些测试需要执行周期性任务或在事件发生的特定时间后响应。使用 Sinon.JS,我们可以将时间指定为我们测试的参数之一:

var sinon = require('sinon');
var expect = require('chai').expect

function areWeThereYet(callback) {
  
  setTimeout(function() {
    callback.apply(this);
  }, 10);
  
}

var clock;

before(function(){
  clock = sinon.useFakeTimers();
});

it("callback gets called after 10ms", function () {
  var callback = sinon.spy();
  var throttled = areWeThereYet(callback);

  areWeThereYet(callback);

  clock.tick(9);
  expect(callback.notCalled).to.be.true;
  
  clock.tick(1);
  expect(callback.notCalled).to.be.false;
});

after(function(){
  clock.restore();
});

如您所见,我们现在可以控制测试时间。

Testing a real microservice

现在,是时候测试一个真正的微服务了,以便全面了解整个测试套件。

我们的微服务要使用Express,它会将输入文本过滤到删除搜索引擎所称的停用词少于三个字符的单词和被禁止的单词

让我们看看代码:

var _ = require('lodash');
var express = require('express');

var bannedWords = ["kitten", "puppy", "parrot"];

function removeStopWords (text, callback) {
  var words = text.split(' ');
  var validWords = [];
  _(words).forEach(function(word, index) {
    var addWord = true;
    
    if (word.length < 3) {
      addWord = false;
    }
    
    if(addWord && bannedWords.indexOf(word) > -1) {
      addWord = false;
    }
    
    if (addWord) {
      validWords.push(word); 
    }
    
    // Last iteration:
    if (index == (words.length - 1)) {
      callback(null, validWords.join(" "));
    }
  });
}
var app = express();

app.get('/filter', function(req, res) {
  removeStopWords(req.query.text, function(err, response){
    res.send(response);
  });
});

app.listen(3000, function() {
  console.log("app started in port 3000");
});

如您所见, 服务非常小,因此它是解释如何编写单元、集成和 E2E 测试的完美示例。在这种情况下,正如我们之前所说,E2E 和集成测试将与通过 REST API 测试服务完全一样,将等同于从端到端的角度测试系统,而且我们的组件集成在系统中。鉴于此,如果我们要添加 UI,我们必须将集成测试从 E2E 中分离出来以确保质量。

TDD – Test-driven development

我们的服务已经完成,正在工作。但是,现在我们想对其进行单元测试,却发现了一些问题:

  • 我们要进行单元测试的函数在 .js 主文件之外是不可见的

  • 服务器代码与功能代码紧密耦合,内聚性差

在这里,TDD 来拯救;我们应该经常问自己“在编写软件时我将如何测试这个功能?”这并不意味着我们应该为了特定的测试目的而修改我们的软件,但是如果您在测试程序的一部分时遇到问题,您很可能应该考虑内聚和耦合,因为这是一个很好的指示 <一个 id="id466" class="indexterm"> 的问题。让我们看一下以下文件:

var _ = require('lodash');
var express = require('express');

module.exports = function(options) {
  bannedWords = [];
  if (typeof options !== 'undefined') {
    console.log(options);
    bannedWords = options.bannedWords || [];
  }

  return function bannedWords(text, callback) {
    var words = text.split(' ');
    var validWords = [];
    _(words).forEach(function(word, index) {
      var addWord = true;
      
      if (word.length < 3) {
        addWord = false;
      }
      
      if(addWord && bannedWords.indexOf(word) > -1) {
        addWord = false;
      }
      
      if (addWord) {
        validWords.push(word); 
      }
      
      // Last iteration:
      if (index == (words.length - 1)) {
        callback(null, validWords.join(" "));
      }
    });
  }
}

这个文件是一个模块,在我看来,它是高度可重用的并且具有良好的内聚性:

  • 我们可以在任何地方导入它(甚至在浏览器中)

  • 创建模块时可以注入违禁词(对测试很有用)

  • 它不与应用程序代码纠缠在一起

以这种方式放置代码,我们的应用程序模块将如下所示:

var _ = require('lodash');
var express = require('express');

var removeStopWords = require('./remove-stop-words')({bannedWords: ["kitten", "puppy", "parrot"]});

var app = express();

app.get('filter', function(req, res) {
  res.send(removeStopWords(req.query.text));
});

app.listen(3000, function() {
  console.log("app started in port 3000");
});

如您所见,我们已经清楚地将业务单元(捕获业务逻辑的功能)与操作单元(服务器的设置)分开。

正如我之前提到的,我不是在代码之前编写测试的忠实粉丝,但它们应该(在我看来)与代码一起编写,但始终牢记前面提到的问题。

公司似乎在推动采用 TDD 方法,但这可能会导致效率显着降低,尤其是在业务需求不明确(因为它们是 90% 的时间)并且 我们在开发过程中面临着变化。

Unit testing

现在我们的代码 的形状更好了,我们将对我们的函数进行单元测试。我们将使用 Mocha 和 Chai 来完成这样的任务:

var removeStopWords = require('./remove-stop-words')({bannedWords: ["kitten", "parrot"]});

var chai = require('chai');
var assert = chai.assert;
chai.should();
var expect = chai.expect;

describe('When executing "removeStopWords"', function() {
  
  it('should remove words with less than 3 chars of length', function() {
    removeStopWords('my small list of words', function(err, response) {
      expect(response).to.equal("small list words");
    });
  });
  
  it('should remove extra white spaces', function() {
    removeStopWords('my small       list of words', function(err, response) {
      expect(response).to.equal("small list words");
    });
  });
  
  it('should remove banned words', function() {
    removeStopWords('My kitten is sleeping', function(err, response) {
      expect(response).to.equal("sleeping");
    });
  });
  
  it('should not fail with null as input', function() {
    removeStopWords(null, function(err, response) {
      expect(response).to.equal("small list words");
    });
  });
  
  it('should fail if the input is not a string', function() {
    try {
      removeStopWords(5, function(err, response) {});
      assert.fail();
    }
    catch(err) {
    }
  });
});

如您所见,我们几乎涵盖了应用程序中的每一个案例和分支,但是我们的代码覆盖率看起来如何?

到目前为止,我们已经提到它,但从未实际测量过它。我们将使用一个名为 Istanbul 的工具来衡量测试覆盖率:

npm install -g istanbul

这应该安装伊斯坦布尔。现在我们需要运行覆盖率报告:

istanbul cover _mocha my-tests.js

这将产生类似于下图所示的输出:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

这还将在 HTML 中生成覆盖率报告,指出哪些行、函数、分支和语句没有被覆盖,如下面的屏幕截图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

正如你所看到的,我们看起来很好。我们的代码(不是测试)实际上被很好地覆盖了,特别是如果我们查看 我们代码文件的详细报告,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

我们可以看到只有一个分支(7 行中的 or 运算符)没有被覆盖,并且6 行中的 if 运算符从未转向 else 运算符。

我们还获得了有关行执行次数的信息:它显示在行号旁边的垂直条中。此信息对于发现我们应用程序中最能受益的热点区域也非常有用。

关于正确的覆盖率,在这个例子中,上升到 90% 以上是相当容易的,但不幸的是,在生产中并不是那么容易系统:

  • 代码要复杂得多

  • 时间永远是一种约束

  • 测试可能不被视为生产时间

但是,在使用动态语言时应谨慎行事。在 Java 或 C# 中,调用不存在的函数会导致编译时错误;而在 JavaScript 中,它会导致运行时错误。唯一真正的障碍是测试(手动或自动),因此最好确保至少每行执行一次。在一般情况下,超过 75% 的代码覆盖率应该足以满足大多数情况。

End-to-end testing

为了端到端地测试我们的应用程序,我们需要一个运行它的服务器。通常,端到端测试是针对受控环境(例如 QA 盒或预生产机器)执行的,以验证我们即将部署的软件是否按预期运行。

在这种情况下,我们的应用程序 是一个 API,因此我们将创建端到端测试,同时将用作集成测试。

然而,在一个完整的应用程序中,我们可能希望在集成和端到端测试之间有一个明确的分离,并使用 Selenium 之类的东西从 UI 的角度来测试我们的应用程序。

Selenium 是一个框架,它允许我们的代码向浏览器发送指令,如下所示:

  • 单击具有 button1 ID 的按钮

  • 将鼠标悬停在 CSS 类 highlighteddiv 元素上

通过这种方式,我们可以确保我们的应用程序流程按预期、端到端地工作,并且我们的下一个版本不会破坏我们应用程序的关键流程。

让我们专注于我们的微服务的端到端测试。我们一直在使用 Chai 和 Mocha 及其相应的断言接口来对我们的软件进行单元测试,并使用 Sinon.JS 来模拟服务功能和其他元素,以避免将调用传播到第三方 Web 服务或从一种方法获得受控响应。

现在,在我们的端到端测试计划中,我们实际上希望向我们的服务发出调用并获得响应以验证结果。

我们需要做的第一件事是在某个地方运行我们的微服务。我们将使用本地机器只是为了方便,但我们可以在持续开发环境中针对 QA 机器执行这些测试。

所以,让我们启动服务器:

node stop-words.js

为方便起见,我将我的脚本称为 stop-words.js。服务器运行后,我们就可以开始测试了。在某些 情况下,我们可能希望我们的测试启动和停止服务器,以便一切都是独立的。让我们看一个关于如何做到这一点的小例子:

var express = require('express');

var myServer = express();

var chai = require('chai');

myServer.get('/endpoint', function(req, res){
  res.send('endpoint reached');
});

var serverHandler;

before(function(){
  serverHandler = myServer.listen(3000);
});

describe("When executing 'GET' into /endpoint", function(){
  it("should return 'endpoint reached'", function(){
    // Your test logic here. http://localhost:3000 is your server.
  });
});

after(function(){
  serverHandler.close();
});

如您所见,Express 提供了一个处理程序来以编程方式操作服务器,因此它就像使用 before()after () 函数来解决这个问题。

在我们的示例中,我们将假设服务器正在运行。为了发出请求,我们将使用一个名为 request 的库来发出对服务器的调用。

安装它的方式,和往常一样,是执行npm install request。完成后,我们可以使用 这个神奇的库:

var chai = require('chai');
var chaiHttp = require('chai-http');
var expect = chai.expect;
chai.use(chaiHttp);

describe("when we issue a 'GET' to /filter with text='aaaa bbbb cccc'", function(){
  it("should return HTTP 200", function(done) {
    chai.request('http://localhost:3000')
      .get('/filter')
      .query({text: 'aa bb ccccc'}).end(function(req, res){
        expect(res.status).to.equal(200);
        done();
      });
  });
});

describe("when we issue a 'GET' to /filter with text='aa bb ccccc'", function(){
  it("should return 'ccccc'", function(done) {
    chai.request('http://localhost:3000')
      .get('/filter')
      .query({text: 'aa bb ccccc'}).end(function(req, res){
        expect(res.text).to.equal('ccccc');
        done();
      });
  });
});

describe("when we issue a 'GET' to /filter with text='aa bb cc'", function(){
  it("should return ''", function(done) {
    chai.request('http://localhost:3000')
      .get('/filter')
      .query({text: 'aa bb cc'}).end(function(req, res){
        expect(res.text).to.equal('');
        done();
      });
  });
});

通过前面的简单测试,我们成功地测试了我们的服务器,以确保应用程序的每个移动部分都已执行。

这里有一个我们以前没有的特殊性:

  it("should return 'ccccc'", function(done) {
    chai.request('http://localhost:3000')
      .get('/filter')
      .query({text: 'aa bb ccccc'}).end(function(req, res){
        expect(res.text).to.equal('ccccc');
        done();
      });
  });

如果您查看突出显示的代码,您会看到一个名为 done 的新回调。此回调有一个任务:在调用它之前阻止测试完成,以便 HTTP 请求有时间执行并返回适当的值。请记住,Node.js 是异步的,在一个操作完成之前不会阻塞线程。

除此之外,我们使用 chai-http 引入的新 DSL 来构建获取请求。

这种语言允许我们构建大范围的组合,考虑以下内容,例如:

chai.request('http://mydomain.com')
  .post('/myform')
  .field('_method', 'put')
  .field('username', 'dgonzalez')
  .field('password', '123456').end(...)

在前面的请求中,我们提交了一个看起来像登录的表单,因此在 end() 函数中,我们可以断言来自服务器的返回。

chai-http 有无数种组合来测试我们的 API。

Manual testing – the necessary evil

无论我们在自动化测试中付出了多少努力,总会有许多手动测试被执行。

有时,我们需要在开发 API 时这样做,因为我们希望看到从客户端到服务器的消息,但在其他时候,我们只想通过预先伪造的请求来访问我们的端点以导致软件按我们的预期执行。

在第一种情况下,我们将利用 Node.js 及其动态特性来构建一个代理,该代理将嗅探所有请求并将它们记录到终端,以便我们可以调试正在发生的事情。这种技术可用于利用两个微服务之间的通信,并在不中断流程的情况下查看正在发生的事情。

在第二种情况下,我们将使用名为 Postman 的软件以受控方式向我们的服务器发出请求。

Building a proxy to debug our microservices

我与 Node.js 的第一次接触正是由于这个问题:两台服务器相互发送消息,导致没有明显原因的不当行为。

这是一个非常常见的问题,有许多已经可行的解决方案(基本上是中间人代理),但我们将展示 Node.js 的强大功能:

var http = require('http');
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({});

http.createServer(function(req, res) {
  console.log(req.rawHeaders);
  proxy.web(req, res, { target: 'http://localhost:3000' });
}).listen(4000);

如果您还记得上一节,我们的 stop-words.js 程序在端口 3000 上运行。我们对这段代码所做的是使用 http-proxy 创建一个代理,它将所有在端口 4000 上发出的请求都通过隧道传输将标头登录到控制台后,进入端口 3000

如果我们在项目根目录下使用 npm install 命令安装所有依赖项后运行程序,我们可以看到 代理记录请求并将它们传送到目标主机的效率如何:

curl http://localhost:4000/filter?text=aaa

这将产生以下输出:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

这个例子非常简单,但这个小型代理实际上可以部署在我们的微服务之间的任何地方,并为我们提供有关网络中正在发生的事情的非常有价值的信息。

Postman

在我们可以在 Internet 上找到的所有用于测试 API 的 软件中,我最喜欢 Postman。它最初是作为 Google Chrome 的扩展程序,但如今,它采用了基于 Chrome 运行时构建的独立应用程序的形式。

它可以在 Chrome 网上商店中找到,并且是免费的(因此您无需付费),尽管它有一个为团队提供的具有更高级功能的付费版本。

界面非常简洁明了,如下截图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

在左侧,我们可以看到请求的 History 以及 Collections 个请求,这对于我们从事长期项目并且有一些复杂的请求要构建时非常方便。

我们将再次使用我们的 stop-words.js 微服务来展示 Postman 的强大功能。

因此,首先要确保我们的微服务正在运行。完成后,让我们从 Postman 发出请求,如以下屏幕截图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

就这么简单,我们已经发出了对我们服务的请求(使用 GET 动词),它已经回复了 文本过滤:非常简单有效。

现在假设我们希望 通过 Node.js 执行该调用。 Postman 有一个非常有趣的特性,它为我们从接口发出的请求生成代码。如果您单击窗口右侧保存按钮下的图标,出现的屏幕将发挥作用:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

我们来看看生成的代码:

var http = require("http");

var options = {
  "method": "GET",
  "hostname": "localhost",
  "port": "3000",
  "path": "/filter?text=aaa%20bb%20cc",
  "headers": {
    "cache-control": "no-cache",
    "postman-token": "912cacd8-bcc0-213f-f6ff-f0bcd98579c0"
  }
};

var req = http.request(options, function (res) {
  var chunks = [];

  res.on("data", function (chunk) {
    chunks.push(chunk);
  });

  res.on("end", function () {
    var body = Buffer.concat(chunks);
    console.log(body.toString());
  });
});

req.end();

这是一个很容易理解的代码,特别是如果您熟悉 HTTP 库。

使用 Postman,我们还可以将 cookie、标头和表单发送到服务器,以模拟应用程序将通过发送身份验证令牌或 cookie 来完成的身份验证。

让我们将请求 重定向到我们在上一节中创建的代理,如 如以下屏幕截图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

如果您有代理并且 stop-words.js 微服务正在运行,您应该会在代理中看到类似于以下输出的内容:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

我们用 Postman 发送的标头 my-awesome-header ,将 显示在原始标头列表中。

Documenting microservices


在本节中,我们将学习如何使用 Swagger 来记录 API。 Swagger 是一个 API 管理器,遵循 Open API 标准,因此它是一种通用语言< /em> 适用于所有 API 创建者。我们将讨论如何编写定义以及为什么就如何描述资源达成一致意见如此重要。

Documenting APIs with Swagger

文档总是一个问题。无论你如何努力,它最终都会过时。幸运的是,在过去几年中,一直在推动为 REST API 生成高质量文档。

API 管理器在其中发挥了关键作用,而 Swagger 是一个特别值得关注的有趣平台。 Swagger 不仅仅是一个文档模块,它还以一种可以让您全面了解您的工作的方式来管理您的 API。

让我们开始安装它:

npm install -g swagger

这将在系统范围内安装 Swagger,因此它将是我们系统中的另一个命令。现在,我们需要使用它创建一个项目:

swagger project create my-project

此命令将允许您选择不同的 Web 框架。我们将选择 Express,因为它是我们已经在使用的。上述 命令的输出如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

此屏幕截图显示如何使用 Swagger 启动项目

现在我们可以找到一个新的文件夹,名为my-project,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

该结构是不言自明的,它是 Node.js 应用程序的常见布局:

Swagger 带有一个令人印象深刻的功能:一个嵌入式编辑器,允许您对 API 的端点进行建模。为了运行它,在生成的文件夹中,执行以下命令:

Swagger project edit

它将在默认浏览器中打开 Swagger Editor,窗口类似于下图:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

Swagger 使用 Yet Another Markup Language (YAML)。它是一种与 JSON 非常相似的语言,但具有不同的 语法。

在本文档中,我们可以自定义一些东西,例如路径(我们应用程序中的路由)。我们看一下Swagger生成的路径:

/hello:
  # binds a127 app logic to a route
  x-swagger-router-controller: hello_world
  get:
    description: Returns 'Hello' to the caller
    # used as the method name of the controller
    operationId: hello
    parameters:
      - name: name
        in: query
        description: The name of the person to whom to say hello
        required: false
        type: string
    responses:
      "200":
        description: Success
        schema:
          # a pointer to a definition
          $ref: "#/definitions/HelloWorldResponse"
      # responses may fall through to errors
      default:
        description: Error
        schema:
          $ref: "#/definitions/ErrorResponse"

该定义是自记录的。基本上,我们将以声明的方式配置端点使用的参数。此端点将传入的操作映射到 hello_world 控制器,特别是映射到 hello 方法,该方法由 id 操作。让我们看看 Swagger 在这个控制器中为我们生成了什么:

'use strict';

var util = require('util');

module.exports = {
  hello: hello
};

function hello(req, res) {
  var name = req.swagger.params.name.value || 'stranger';
  var hello = util.format('Hello, %s!', name);
  res.json(hello);
}

此代码可以在项目的 api/controllers 文件夹中找到。如您所见,它是一个非常标准的 Express 控制器,打包为一个模块(内聚良好)。唯一奇怪的行是 hello 函数中的第一行,我们从 Swagger 中获取参数。一旦我们运行项目,我们稍后会回到这个问题。

端点的第二部分是响应。正如我们所见,我们引用了两个定义: HelloWorldResponse 用于 http code 200ErrorResponse 其余代码。这些对象在以下代码中定义:

definitions:
  HelloWorldResponse:
    required:
      - message
    properties:
      message:
        type: string
  ErrorResponse:
    required:
      - message
    properties:
      message:
        type: string

这真的很有趣,虽然我们使用的是动态语言,但合约是由 Swagger 定义的,因此我们有一个与语言无关的定义,可以被许多不同的技术使用,尊重技术原则 我们之前在第1章中讨论过的异质性,微服务架构,以及第2章Node.js 中的微服务 - Seneca 和 PM2 替代方案

在解释了定义是如何工作的之后,是时候启动服务器了:

swagger project start

这应该产生与以下代码非常相似的输出:

Starting: C:\my-project\app.js...
  project started here: http://localhost:10010/
  project will restart on changes.
  to restart at any time, enter `rs`
try this:
curl http://127.0.0.1:10010/hello?name=Scott

现在,如果我们按照输出的说明执行 curl 命令,我们会得到以下输出:

curl http://127.0.0.1:10010/hello?name=David
"Hello David!"

Swagger 将 name 查询参数绑定到 YAML 定义中指定的 Swagger 参数。这听起来可能很糟糕,因为我们正在将我们的软件耦合到 Swagger,但它给您带来了巨大的好处:Swagger 允许您通过编辑器测试端点。让我们看看它是如何工作的。

在编辑器的右侧,您可以看到一个带有 Try this operation 标签的按钮,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

一旦你点击它,它会给你一个表格,让你测试端点,如以下屏幕截图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

此表单上有一条关于跨域请求的警告消息。 在我们的本地机器上开发时,我们无需担心;但是,在使用 Swagger Editor 测试其他主机时,我们可能会遇到问题。

Note

有关更多信息,请访问以下 URL:

https://en.wikipedia.org/wiki/Cross-origin_resource_sharing

name 参数输入一个值,然后点击发送请求,如下图所示:

读书笔记《developing-microservices-with-node-js》测试和记录Node.js微服务

这是一个使用 Swagger Editor 测试端点的响应示例

请注意,要使 此测试正常运行,我们的应用服务器 必须是启动并运行。

Generating a project from the Swagger definition

到目前为止,我们一直在玩 Swagger 和生成的项目,但我们现在要 生成项目swagger.yaml 文件。我们将使用已经生成的项目作为起点,但我们将添加一个新的端点:

swagger: "2.0"
info:
  version: "0.0.1"
  title: Stop Words Filtering App
host: localhost:8000
basePath: /
schemes:
  - http
  - https
consumes:
  - application/json
produces:
  - application/json
paths:
  /stop-words:
    x-swagger-router-controller: stop_words
    get:
      description: Removes the stop words from an arbitrary input text.
      operationId: stopwords
      parameters:
        - name: text
          in: query
          description: The text to be sanitized
          required: false
          type: string
      responses:
        "200":
          description: Success
          schema:
            $ref: "#/definitions/StopWordsResponse"
  /swagger:
    x-swagger-pipe: swagger_raw
definitions:
  StopWordsResponse:
    required:
      - message
    properties:
      message:
        type: string

这个端点对你来说可能很熟悉,因为我们在本章前面对它进行了单元测试。正如您现在可能知道的那样,Swagger 编辑器非常酷:它会在您键入时提供有关 YAML 文件中发生的情况的反馈,并保存更改。

下一步是从 https://github.com 下载 Swagger 代码生成器/swagger-api/swagger-codegen.它是一个 Java 项目,因此我们需要 Java SDK 和 Maven 来构建它,如下所示:

mvn package

Codegen 是一个工具,它允许我们从 Swagger YAML 中读取 API 定义,并以我们选择的语言(在本例中为 Node.js)构建项目的基本结构。

项目根目录中的上述命令应构建所有子模块。现在,只需在 swagger-codegen 文件夹的根目录中执行以下命令即可:

java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -i my-project.yaml -l nodejs -o my-project

Swagger 代码生成器支持多种语言。在这里,诀窍在于,在将其用于微服务时,我们可以定义接口,然后使用最合适的技术来构建我们的服务。

如果你去 my-project 文件夹,你应该在那里找到项目的完整结构,准备开始编码。

Summary


在本章中,您学习了如何测试和记录微服务。由于交付新功能的压力,这通常是软件开发中被遗忘的活动,但在我看来,这是一个冒险的决定。我们必须在测试过多和过少之间找到平衡点。一般来说,我们总是会尝试为单元、集成和端到端测试找到合适的比例。

您还了解了手动测试和有效手动测试我们的软件的工具(总是有手动测试的组成部分)。

另一个有趣的地方是文档和 API 管理。在这种情况下,我们了解了 Swagger,它可能是最流行的 API 管理器,它导致了 Open API 标准的创建。

如果您想更深入地了解 API 世界(为了构建实用且高效的 API,需要学习很多东西),您可能应该浏览 http://apigee.com。 Apigee 是构建 API 的公司专家,并为开发人员和企业提供工具,可以帮助您构建更好的 API。