菜单
文档breadcrumb arrow Grafana k6breadcrumb arrow 示例breadcrumb arrow k6 入门breadcrumb arrow 重用和重新运行测试
开源

重用和重新运行测试

在之前的教程中,您设计了 k6 脚本来断言性能并方便比较结果。

在本教程中,学习如何

  • 将测试脚本模块化为可重用组件
  • 使用环境变量动态配置脚本

示例脚本

为了好玩,让我们结合之前的教程中的脚本。使用 multiple-flows.js 测试的逻辑与 api-test.js 的阈值和场景(随意添加更多检查、请求、组等)。请注意这个脚本的特点:

  • default 函数有两个组:Contacts flowCoinflip game
  • options 对象有两个属性:thresholdsscenarios

在以下部分,学习如何将这些组件拆分到单独的文件中,并在运行时动态组合它们。

JavaScript
import http from 'k6/http';
import { group, sleep } from 'k6';
import { Trend } from 'k6/metrics';

//define configuration
export const options = {
  scenarios: {
    //arbitrary name of scenario:
    breaking: {
      executor: 'ramping-vus',
      stages: [
        { duration: '10s', target: 20 },
        { duration: '50s', target: 20 },
        { duration: '50s', target: 40 },
        { duration: '50s', target: 60 },
        { duration: '50s', target: 80 },
        { duration: '50s', target: 100 },
        { duration: '50s', target: 120 },
        { duration: '50s', target: 140 },
        //....
      ],
    },
  },
  //define thresholds
  thresholds: {
    http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
    http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
  },
};

//set baseURL
const baseUrl = 'https://test.k6.io';

// Create custom trends
const contactsLatency = new Trend('contacts_duration');
const coinflipLatency = new Trend('coinflip_duration');

export default function () {
  // Put visits to contact page in one group
  let res;
  group('Contacts flow', function () {
    // save response as variable
    res = http.get(`${baseUrl}/contacts.php`);
    // add duration property to metric
    contactsLatency.add(res.timings.duration);
    sleep(1);

    res = http.get(`${baseUrl}/`);
    // add duration property to metric
    contactsLatency.add(res.timings.duration);
    sleep(1);
  });

  // Coinflip players in another group

  group('Coinflip game', function () {
    // save response as variable
    let res = http.get(`${baseUrl}/flip_coin.php?bet=heads`);
    // add duration property to metric
    coinflipLatency.add(res.timings.duration);
    sleep(1);

    res = http.get(`${baseUrl}/flip_coin.php?bet=tails`);
    // add duration property to metric
    coinflipLatency.add(res.timings.duration);
    sleep(1);
  });
}

模块化逻辑

使用模块,您可以从其他文件中使用逻辑和变量。使用模块将函数提取到它们自己的文件中。

为此,请按照以下步骤操作:

  1. 复制前面的脚本(whole-tutorial.js)并将其另存为 main.js

  2. main.js 脚本文件中提取 Contacts flow 组函数,并将其粘贴到名为 contacts.js 的新文件中。

    JavaScript
    export function contacts() {
      group('Contacts flow', function () {
        // save response as variable
        let res = http.get(`${baseUrl}/contacts.php`);
        // add duration property to metric
        contactsLatency.add(res.timings.duration);
        sleep(1);
    
        res = http.get(`${baseUrl}/`);
        // add duration property to metric
        contactsLatency.add(res.timings.duration);
        sleep(1);
      });
    }

    这样,这个脚本将无法工作,因为它有未声明的函数和变量。

  3. 添加必要的导入和变量。这个脚本使用了 group, sleep, 和 http 函数或库。它还有一个自定义指标。由于此指标特定于该组,因此您可以将其添加到 contacts.js 中。

  4. 最后,将 baseUrl 作为 contacts 函数的参数传递。

    JavaScript
    import http from 'k6/http';
    import { Trend } from 'k6/metrics';
    import { group, sleep } from 'k6';
    
    const contactsLatency = new Trend('contact_duration');
    
    export function contacts(baseUrl) {
      group('Contacts flow', function () {
        // save response as variable
        let res = http.get(`${baseUrl}/contacts.php`);
        // add duration property to metric
        contactsLatency.add(res.timings.duration);
        sleep(1);
    
        res = http.get(`${baseUrl}/`);
        // add duration property to metric
        contactsLatency.add(res.timings.duration);
        sleep(1);
      });
    }
  5. 在名为 coinflip.js 的文件中对 coinflip 组重复此过程。使用标签页查看最后三个文件(options 已移到 main.js 的底部,以便更好地阅读)。

    main
    import { contacts } from './contacts.js';
    import { coinflip } from './coinflip.js';
    
    const baseUrl = 'https://test.k6.io';
    
    export default function () {
      // Put visits to contact page in one group
      contacts(baseUrl);
      // Coinflip players in another group
      coinflip(baseUrl);
    }
    
    //define configuration
    export const options = {
      scenarios: {
        //arbitrary name of scenario:
        breaking: {
          executor: 'ramping-vus',
          stages: [
            { duration: '10s', target: 20 },
            { duration: '50s', target: 20 },
            { duration: '50s', target: 40 },
            { duration: '50s', target: 60 },
            { duration: '50s', target: 80 },
            { duration: '50s', target: 100 },
            { duration: '50s', target: 120 },
            { duration: '50s', target: 140 },
            //....
          ],
        },
      },
      //define thresholds
      thresholds: {
        http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
        http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
      },
    };
    contacts
    import http from 'k6/http';
    import { Trend } from 'k6/metrics';
    import { group, sleep } from 'k6';
    
    const contactsLatency = new Trend('contact_duration');
    
    export function contacts(baseUrl) {
      group('Contacts flow', function () {
        // save response as variable
        let res = http.get(`${baseUrl}/contacts.php`);
        // add duration property to metric
        contactsLatency.add(res.timings.duration);
        sleep(1);
    
        res = http.get(`${baseUrl}/`);
        // add duration property to metric
        contactsLatency.add(res.timings.duration);
        sleep(1);
      });
    }
    coinflip
    import http from 'k6/http';
    import { Trend } from 'k6/metrics';
    import { group, sleep } from 'k6';
    
    const coinflipLatency = new Trend('coinflip_duration');
    
    export function coinflip(baseUrl) {
      group('Coinflip game', function () {
        // save response as variable
        let res = http.get(`${baseUrl}/flip_coin.php?bet=heads`);
        // add duration property to metric
        coinflipLatency.add(res.timings.duration);
        sleep(1);
        // mutate for new request
        res = http.get(`${baseUrl}/flip_coin.php?bet=tails`);
        // add duration property to metric
        coinflipLatency.add(res.timings.duration);
        sleep(1);
      });
    }

    运行测试

    bash
    # setting the workload to 10 iterations to limit run time
    k6 run main.js --iterations 10

    结果应与在组合文件中运行脚本非常相似,因为它们是同一个测试。

模块化工作负载

现在迭代代码完全模块化了,您也可以将 options 模块化。

以下示例创建了一个模块 config.js 来导出阈值和工作负载设置。

main
import { coinflip } from './coinflip.js';
import { contacts } from './contacts.js';
import { thresholdsSettings, breakingWorkload } from './config.js';

export const options = {
  scenarios: { breaking: breakingWorkload },
  thresholds: thresholdsSettings,
};

const baseUrl = 'https://test.k6.io';

export default function () {
  contacts(baseUrl);
  coinflip(baseUrl);
}
config
export const thresholdsSettings = {
  http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }],
  http_req_duration: ['p(99)<1000'],
};

export const breakingWorkload = {
  executor: 'ramping-vus',
  stages: [
    { duration: '10s', target: 20 },
    { duration: '50s', target: 20 },
    { duration: '50s', target: 40 },
    { duration: '50s', target: 60 },
    { duration: '50s', target: 80 },
    { duration: '50s', target: 100 },
    { duration: '50s', target: 120 },
    { duration: '50s', target: 140 },
    //....
  ],
};

注意这个最终脚本的长度,并与本页开头的脚本进行比较。虽然最终执行相同,但它的大小减半,并且更易读。

除了简洁性之外,这种模块化还允许您从多个部分组合脚本,或在运行时动态配置脚本。

混合搭配逻辑

通过模块化的配置和逻辑,您可以混合搭配逻辑。一种简单的配置方法是通过环境变量

修改 main.jsconfig.js,使其

  • 默认情况下运行一个 5 次迭代的冒烟测试
  • 通过正确的环境变量值,运行一个破坏性测试

为此,请按照以下步骤操作:

  1. 将配置冒烟测试的工作负载设置添加到 config.js 中。

    JavaScript
    export const smokeWorkload = {
      executor: 'shared-iterations',
      iterations: 5,
      vus: 1,
    };
    
    export const thresholdsSettings = {
      http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }],
      http_req_duration: ['p(99)<1000'],
    };
    
    export const breakingWorkload = {
      executor: 'ramping-vus',
      stages: [
        { duration: '10s', target: 20 },
        { duration: '50s', target: 20 },
        { duration: '50s', target: 40 },
        { duration: '50s', target: 60 },
        { duration: '50s', target: 80 },
        { duration: '50s', target: 100 },
        { duration: '50s', target: 120 },
        { duration: '50s', target: 140 },
        //....
      ],
    };
  2. 编辑 main.js 以根据 WORKLOAD 环境变量选择工作负载设置。例如:

    JavaScript
    import { coinflip } from './coinflip.js';
    import { contacts } from './contacts.js';
    import { thresholdsSettings, breakingWorkload, smokeWorkload } from './config.js';
    
    export const options = {
      scenarios: {
        my_scenario: __ENV.WORKLOAD === 'breaking' ? breakingWorkload : smokeWorkload,
      },
      thresholds: thresholdsSettings,
    };
    
    const baseUrl = 'https://test.k6.io';
    
    export default function () {
      contacts(baseUrl);
      coinflip(baseUrl);
    }
  3. 运行脚本时带或不带 -e 标志。

    • 当您运行 k6 run main.js 时会发生什么?
    • 那么运行 k6 run main.js -e WORKLOAD=breaking 呢?

这是一个简单的例子,展示了如何模块化测试。随着您的测试套件增长以及更多人参与性能测试,您的模块化策略对于构建和维护高效测试套件至关重要。

下一步

现在您已经看到了编写测试、断言性能、过滤结果和模块化脚本的示例。请注意测试如何在复杂性上循序渐进:从单个端点到整体测试,从小型负载到大型负载,以及从单个测试到可重用模块。这些进展在测试中是典型的,下一步是自动化。自动化本教程可能不切实际,但如果您感兴趣,请阅读自动化性能测试指南。

更可能的是,您想了解更多关于 k6 的信息。k6-learn repository 中有更多详细的练习。或者,您可以阅读和探索测试指南,并尝试构建您的测试策略。

祝您测试愉快!