如何用k6进行HTTP请求测试

881 阅读10分钟

开发团队每天部署的许多多方面的应用程序都是松散耦合的,每个服务的存在都是为了支持另一个服务。大多数开发全栈应用的团队都知道,测试这些服务之间的通信至关重要。这个过程的一部分是测试HTTP请求端点,而本教程的重点正是这个。我将带领你学习如何扩展k6框架来测试我们的HTTP端点。你还将学习如何设置k6,并利用其他API测试框架中没有的功能。

先决条件

要学习本教程,你将需要:

  1. JavaScript的基本知识
  2. HTTP请求和测试的基本知识
  3. 在您的系统上安装Node.js(版本>=10.13)。
  4. 一个CircleCI账户
  5. 一个GitHub账户

我们的教程是与平台无关的,但使用CircleCI作为一个例子。如果你没有CircleCI账户,请**在这里注册一个免费账户。**

什么是k6?

k6是一个开源的框架,旨在为开发人员提供性能测试的乐趣。在这一点上,你可能会问自己这和HTTP请求测试有什么关系?测试性能意味着向API端点发出请求。我们已经在使用这个工具来进行HTTP请求了,为什么不使用它来测试这些端点呢?发出请求就意味着你已经完成了对端点的测试。我将在本教程的后面描述如何完成测试。

k6 Logo

k6是用goja语言编写的,它是纯Go语言的Ecmascript 5.1实现。这个实现纯粹是出于性能的考虑,它有很多很好的功能,使有JavaScript经验的软件开发者可以轻松地进行测试。k6捆绑了一个有用的CLI,用于制作API支持的k6命令。k6的API提供了对JavaScript ES2015/ES6的支持,以及用于负载测试的工具和能力,我们将在本教程的后续内容中使用。

现在我们知道了在k6引擎下运行的东西,为什么不深入到设置k6来运行我们的HTTP测试呢?

首先,从Github 仓库克隆示例应用程序。

安装k6

与 JavaScript 模块不同,k6 必须通过使用软件包管理器来安装。在macOS中我们可以使用Homebrew,而在Windows操作系统中,我们可以使用chocolatey。在k6文档中还有更多适用于其他操作系统的安装选项。在本教程中,我们将遵循macOS的安装指南,为了做到这一点,我们将首先在homebrew中执行以下命令。

brew install k6

这是我们开始编写k6测试时唯一需要运行的命令。

安装k6后,我们需要创建一个文件夹来存储我们的项目。我们将通过在我们要存储测试的目录中执行以下命令来完成这一工作。

$ mkdir http-request-testing-with-k6;
$ touch todos-testing.js

这些命令将创建一个空项目,同时创建我们的测试文件,todos-testing.js

k6 测试结构

k6采用的测试结构与大多数开发人员所习惯的有点不同,特别是那些来自JavaScript背景的开发人员。下面的代码片段显示了一个用k6编写的测试样本,断言我们正从一个开源的todo应用API中得到一个响应,我们将在本教程中使用这个API进行测试。你可以将同样的测试添加到你刚刚创建的项目中。

// snippet from ./todos-testing.js
import http from 'k6/http';
import { check } from 'k6';
 
export let options = {
   vus: 1,
};
 
export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   })

首先,如果你仔细看一下这个测试,它类似于JavaScript测试代码,但文件格式与我们通常写JavaScript测试的方式不同。让我们来分解一下测试的结构。

在上面的测试中,由于我们了解到k6使用goja ,而不是JavaScript,所以我们能够编写JavaScript代码,但我们只能使用JavaScript的某些方面来执行我们的测试。因此,我们不能使用像describeit 块这样的方法,也不能使用像chai 这样的断言库。k6 提供了group()check() 方法,其工作方式与我们使用describeassert 相同。group() 块允许我们在一个文件中编写多个测试,但也将不同测试的配置分开,以方便阅读。

k6还捆绑了一个默认的HTTP 请求模块,我们可以用它来进行所有的API请求,无论是GET POSTPUT ,还是DELETE 。当用纯JavaScript编写测试时,这个http() 方法就相当于fetch() 。对于测试的其余部分,我们使用check() 方法断言响应是200 ,并且我们可以继续使用这个断言块断言我们需要的任何其他响应。

提示k6测试中的options = {} 对象对于运行HTTP测试是可选的,但是当我们用k6运行负载测试时,它非常方便。在这个特定的测试中,我们告诉k6,我们想只用一个虚拟用户(vus)或一个迭代来运行测试,这对这个测试的上下文是有意义的。

k6测试的生命周期

k6的生命周期是:init code,setup code,VU codeteardown code 。这个周期显示了一个测试通常如何运行,以及测试在执行时可以访问哪些资源。下面是一个显示k6测试生命周期的示例代码块。

// 1. init code

export function setup() {
  // 2. setup code
}

export default function (data) {
  // 3. VU code
}

export function teardown(data) {
  // 4. teardown code
}

initVU 阶段作为测试的入口,而teardownsetup 提供了 API 测试在测试完成执行之前和之后可以运行的脚本。拆分和设置方法是可选的,所以k6中最简单的测试形式可以只使用VU 的代码方法,在这个代码片段中显示。

// snippet from ./todos-testing.js
export default function () {
    const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
    check(response, {
        "status code should be 200": res => res.status === 200,
    });
}

当测试被调用时,默认函数VU code 内的代码会运行,既可以是单次迭代(一个VU),也可以是出于负载测试的目的而进行多次迭代。

VU 代码进行 API HTTP 请求并执行断言,但它不能导入其他模块或从文件系统加载文件。这就是k6如何知道在不同的执行模式下执行时,哪些测试要保留在内存中。这种策略提高了k6的性能,优化了测试设计。

现在,这变得有趣了,是时候将CI/CD整合到我们的应用程序中,以便你可以执行测试。在本教程中,我们将使用CircleCI来为应用程序设置CI/CD。

注意如果你已经克隆了项目库,你可以跳过本教程的这一部分。如果你想学习如何设置你自己的项目,我在这里添加了这些步骤。

设置Git并推送到CircleCI

为了设置CircleCI,通过运行以下命令在项目中初始化一个Git仓库。

git init

接下来,在根目录下创建一个.gitignore 文件。在该文件中添加node_modules ,以防止npm生成的模块被添加到你的远程仓库。然后,添加一个提交,将你的项目推送到GitHub

登录CircleCI,进入项目仪表板。你可以从与你的GitHub用户名或你的组织相关的所有GitHub仓库列表中选择你想建立的仓库。本教程的项目被命名为http-request-testing-with-k6 。在项目仪表板上,选择选项来设置你想要的项目。选择使用现有配置的选项。

注意启动构建后,预计你的管道会失败。你仍然需要将定制的.circleci/config.yml 配置文件添加到GitHub,以便项目能够正常构建。

设置CircleCI

在你的根目录下创建一个.circleci 目录,然后添加一个config.yml 文件。该配置文件保存了每个项目的CircleCI配置。使用该配置中的CircleCI k6orb 执行你的k6测试。

# snippet from .circleci/config.yml configuration file 
version: 2.1
orbs:
 k6io: k6io/test@1.1.0
workflows:
 load_test:
   jobs:
     - k6io/test:
         script: todos-testing.js

使用第三方orbs

CircleCI orbs是可重复使用的yaml配置包,将代码浓缩到一行。为了允许使用第三方的orbs,如python@1.2 ,你可能需要。

  • 如果你是管理员,启用组织设置,或
  • 向你的组织的CircleCI管理员申请许可。

设置好配置后,将配置推送到GitHub。CircleCI将开始构建该项目。

Voila!进入CircleCI仪表板,展开构建细节。验证测试是否成功运行并被集成到CircleCI。

Pipeline Setup Success

现在你已经建立了你的CI管道,你可以继续用k6编写更多的HTTP请求测试。

验证k6响应

现在你已经了解了k6的结构和如何编写一个基本的测试,下一步是在k6中编写可扩展的测试。使用你的todo API来创建一个名为write k6 tests 的todo项目。

// snippet from ./todos-testing.js
  group('Create a Todo', () => {
       const response = http.post('https://todo-app-barkend.herokuapp.com/todos/',
       {"task": "write k6 tests"}
       );
       todoID = response.json()._id;
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
       check(response, {
           "Response should have created todo": res => res.json().completed === false,
       });
   })

这个代码块传递一个todo项目,并向我们的Todo应用API发出HTTP请求,你的todo项目就被创建了。很简单,对吗?

为了使这一过程更有趣,添加另一个测试来获取已创建的todo项目。这里的挑战是获得对前一个测试范围的访问。要做到这一点,在前一个测试的返回响应中创建一个全局范围。然后在随后的测试中使用该todo项目的Id 。下面是一个例子。

// snippet from ./todos-testing.js
group('get a todo item', () => {
    const response = http.get(`https://todo-app-barkend.herokuapp.com/todos/${todoID}`
       );
    check(response, {
        "status code should be 200": res => res.status === 200,
    });
    check(response, {
        "response should have the created todo": res => res.json()[0]._id === todoID,
    });
    check(response, {
        "response should have the correct state": res => res.json()[0].completed === false,
    });
   })

这个测试验证了所创建的todo项目就是你所创建的那个,而且它的属性没有改变。在本地运行这个测试成功通过,表明就像在JavaScript测试中一样,k6可以在测试中共享状态。

在这个示例代码中还显示,使用groups() 来分离不相关的测试,在测试失败的情况下增加了松散耦合。它也提高了测试的可读性。

使用场景为多个环境配置k6

作为一个负载测试工具,k6捆绑了令人敬畏的功能,用于执行在不同环境下编写测试等操作,而不需要大量的配置。scenario 功能就是一个很好的例子。场景提供了对k6测试的深度配置,使其有可能根据配置偏好配置每个人的 VU根据配置的偏好。

export let options = {
   scenarios: {
     example_scenario: {
       env: { EXAMPLEVAR: 'testing' },
       tags: { example_tag: 'testing' },
     },
   }
 }

这个代码块显示了场景对象如何被捆绑在k6options 对象中。它传递了env 对象,其中包含了我们对不同环境的环境配置。金矿!

要自己做这个,创建一个叫做environmentConfig.js 的文件,或者使用克隆的资源库中的文件。为开发和暂存环境添加配置,并将它们传递给不同的测试。

// snippet from ./environmentConfig.js
export function developmentConfig(filename) {
   let envConfig = {
     BASE_URL: "http://todo-app-barkend.herokuapp.com/todos/",
     ENV: "DEVELOPMENT",
   };
   return Object.assign({}, envConfig, filename);
 }
  export function stagingConfig(filename) {
   let envConfig = {
     BASE_URL: "https://todo-app-barkend.herokuapp.com/todos/",
     ENV: "STAGING",
   };
   return Object.assign({}, envConfig, filename);
 }

这个代码块显示了如何为不同的环境配置测试。注意,它为开发环境使用HTTP (非SSL)URL,为暂存环境使用HTTPS (启用SSL)URL。

一旦你为各个环境设置了变量,你可以用options 对象使用场景将它们导入不同的测试中。创建两个新文件来测试这个功能,一个用于开发环境,一个用于暂存环境。在开发环境的测试中使用这个代码块。

// snippet from scenario-tests/todos-development-tests.js
import { developmentConfig } from '../environmentConfig.js';
 
export const options = {
   scenarios: {
     example_scenario: {
       env: developmentConfig(),
       executor: 'shared-iterations',
       vus: 1
     }
   }
 };
 
export default function () {
   group('API uptime check - development', () => {
       const response = http.get(`${__ENV.BASE_URL}`);
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   });
…
});

这个代码块显示了如何利用scenarios 配置,同时在不同环境下运行同一组测试。

注意为暂存环境和开发环境进行不同配置的完整测试可以在克隆版本库的scenarios-tests 文件夹中找到。

使用这种方法,你可以在DEV,UAT, 甚至PROD 环境中运行测试,即使在每个环境中相同测试的配置是不同的。为了验证成功,你可以在scenario-tests 文件夹中运行测试。

对于DEV环境/配置,运行。

K6 run scenario-tests/todos-development-tests.js

对于暂存环境,运行。

K6 run scenario-tests/todos-staging-tests.js

为了确保这些测试在CI/CD管道上运行,你需要修改.circleci/config.yml ,包括这些运行命令。

# snippet from .circleci/config.yml configuration file 
    jobs:
      - k6io/test:
          script: todos-testing.js
      # Add more jobs here
      - k6io/test:
          script: scenario-tests/todos-development-tests.js
      - k6io/test:
          script: scenario-tests/todos-development-tests.js 

场景让你把k6测试配置成单独的迭代。当你想在一个文件中编写多个负载测试场景时,它们提供了扩展性,对每个场景使用不同的配置。

在CircleCI中验证管道的成功

通过本教程,我们已经能够成功地运行我们的测试,但这并不能验证它们在CI上总是工作。为了增加我们对这个过程的信心,让我们提交我们的变化并再次推送到GitHub。由于我们已经配置了CircleCI,一旦修改被推送到GitHub的远程仓库,构建就会自动开始。

Pipeline Run Success

万岁!我们在三个测试文件的构建中都得到了绿色检查。这只意味着一件事:庆祝时间到了!

总结

在本教程中,我们熟悉了什么是k6框架以及它是如何工作的。我们了解到它是用goja 语言创建的,但是是用ES6 JavaScript语法编写的,这使它作为一个负载测试工具的性能得到了优化。我们还学习了k6测试的不同生命周期阶段,以及我们如何编写和断言k6测试。最后,我们学习了如何在多种环境下配置k6,以及如何利用k6中的optionsscenarios 对象。现在,去吧,让我们的生活更加美好!