Lời mở đầu

Performance testing là một phần quan trọng để ứng dụng sẵn sàng đưa ra công chúng. Ứng dụng của bạn được kỳ vọng cho hàng nghìn, hàng triệu người sử dụng. Bạn không hề mong muốn hệ thống không hoạt động ổn định trong thời điểm có lưu lượng người dùng truy cập tăng đột biến. Và bạn muốn hệ thống vẫn đáp ứng được lượng truy cập lớn trong khi chi phí ở mức thấp, đong đếm được. Trong bài viết này, chúng ta sẽ tìm hiểu về kỹ thuật kiểm thử hiệu năng (performance testing) để đáp ứng các yêu cầu về tải hệ thống, và sử dụng công cụ K6 cho thao tác kiểm thử hiệu năng.

Hãy dành một chút không thời gian cho lý thuyết, nếu bạn đã từng viết unit test hay integration test, bạn đã thực hiện kiểm tra chức năng của ứng dụng. Đây là dạng functional test. Ở khía cạnh khác, chúng ta cũng cần quan tâm tới non-functional test. Đây là những bài test dành cho security, efficiency, reliability và performance. Với performance testing, nó dẫn chúng ta vào ý tưởng liệu hiệu năng của ứng dụng sẽ như thế nào trong đa dạng các điều kiện ở thế thới thực. Trong đó, chúng ta sẽ thấy những phân loại nhỏ hơn của performance testing:

  • low testing
  • stress testing
  • spike testing
  • soak testing

Giới thiệu về K6

K6 là một công cụ có thể cài đặt trên đa dạng các nền tảng, từ Windows, Linux, MacOS cho tới môi trường container như docker, kubernetes. Sử dụng K6, bạn có thể tiến hành các cuộc kiểm thử về reliability và performance của ứng dụng và hạ tầng. Giải pháp K6 hỗ trợ các giao thức kết nối HTTP version 1.1, version 2, grpc và websocket, ngoài ra có một vài mở rộng cho MQTT, AMPQ, Kafka, Mllp, Redis.

Bạn là người quan tâm tới price hay feature của công cụ? Bạn sẽ không cần lo lắng về price, bởi K6 được cung cấp sử dụng miễn phí bởi Granfa K6 team. Nếu thực sự price chỉ là con số đối với bạn, thì Grafana cũng cung cấp một giải pháp SaaS có tên k6 cloud với tính năng WebUI giúp bạn thiết kế, lập lịch và thực thi test sử dụng các load generators. Mặc dù k6 không cung cấp WebUI hay dashboard để hiển thị và phân tích dữ liệu kết quả, bạn vẫn có thể mở rộng tính năng bằng việc tích hợp các mở rộng tùy chọn (extensions), ví dụ như lưu trữ dữ liệu thống kê, kết quả trên Prometheus, hiển thị trực quan trên Grafana,…

Trong một nhóm phát triển phần mềm, thường bao gồm kỹ sư phần mềm, kỹ sư kiểm thử, kỹ sư vận hành sẽ thường sử dụng K6 với các mục đích:

  • Load and performance testing: K6 được tối ưu hóa lượng tài nguyên sử dụng và được thiết kế để chạy các bài test như spkie, stress hay soak test.
  • Browser performance testing: Thông qua module browser, các bài kiểm thử sẽ cung cấp các metrics trình duyệt để xác định các vấn đề hiệu năng liên quan tới trình duyệt. Thêm nữa, bạn có thể tích hợp browser test với các bài performance test khác để thu được một kết quả toàn diện đánh giá hiệu năng website.
  • Performance and synthetic monitoring: Bạn có thể lập lịch các bài test để liên tục, thường xuyên xác định, đánh giá hiệu suất và tính khả dụng của môi trường production. Đối với điều này, bạn có thể sử dụng Grafana Cloud Synthetic Monitoring có hỗ trợ chạy các k6 script.
  • Tự động hóa performance test: K6 tích hợp với CI/CD và các công cụ tự động hóa cho phép các team phối hợp chặt chẽ, lồng ghép hoạt động performance test một cách tự động trong chu trình phát triển phần mềm.
  • Chaos & Resilience testing: Bạn có thể sử dụng K6 để mô phỏng lưu lượng truy cập như một phần của các thử nghiệm hỗn loạn, kích hoạt các yếu tố ngẫu nhiên, bất lợi từ các bài kiểm tra hoặc đưa các loại khác nhau vào Kubernetes bằng mở rộng xk6-disruptor.
  • Infrastructure testing: Với các k6 extensions, bạn có thể mở rộng giới hạn tính năng của k6, kiểm thử với các giao thức mới hoặc sử dụng client cụ thể để trực tiếp kiểm tra một hệ thống riêng lẻ trong cơ sở hạ tầng của bạn.

Lợi ích chính khi sử dụng K6

🛠️ Dễ dàng viết script cho developer: K6 sử dụng JavaScript, giúp việc tùy chỉnh kịch bản kiểm thử trở nên nhanh chóng, linh hoạt và phù hợp với quy trình kiểm thử liên tục.

Hiệu năng cao: K6 nhẹ, tối ưu tài nguyên và có khả năng mô phỏng hàng nghìn người dùng ảo, phù hợp cho ứng dụng lớn, microservices và API.

📊 Theo dõi thời gian thực: K6 cung cấp các chỉ số như tỷ lệ lỗi, throughput và thời gian phản hồi ngay trong quá trình kiểm thử. Nhờ đó, dễ dàng phát hiện sớm các bottleneck và tối ưu hiệu suất hệ thống.

🔗 Tích hợp CI/CD: K6 tích hợp mượt mà vào quy trình CI/CD, đảm bảo kiểm thử hiệu năng là một phần không thể thiếu trong vòng đời phát triển phần mềm chứ không phải tính năng bổ sung.

📈 Khả năng mở rộng: K6 hỗ trợ cả môi trường kiểm thử tại chỗ (local) và trên cloud, dễ dàng mở rộng theo nhu cầu — từ kiểm thử một API nhỏ đến hệ thống enterprise lớn.

Cài đặt K6

Hands-on time! K6 hỗ trợ đa dạng các nền tảng, từ Linux, Mac, Windows, cho tới container hoặc binary file. Hãy chọn nền tảng phù hợp và chạy tập lệnh cài đặt - bước đầu của sử dụng K6 và thực hiện kiểm thử hiệu năng.

Linux

Debian/Ubuntu

sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

Fedora/CentOS

sudo dnf install https://dl.k6.io/rpm/repo.rpm
sudo dnf install k6

MacOS

brew install k6

Windows

brew install k6

# hoặc
winget install k6 --source winget

Sau cài đặt, cần chắc chắn rằng công cụ k6 đã được cài đặt với câu lệnh kiểm tra version của k6:

k6 --version

Chạy kịch bản kiểm thử với K6

Trong phần này, chúng ta sẽ viết một script load testing đơn giản first_test.js và sử dụng k6 để tiến hành kiểm thử.

Mở editor yêu thích của bạn và nhập đoạn mã sau:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  duration: '3m', // Thời gian kiểm thử
  vus: 50, // Số lượng user ảo
  thresholds: {
    http_req_duration: ['p(95)<500'], // thiết lập ngưỡng đánh giá của bài test, 95% requests cần nhỏ hơn 500 mili giây
  },
};

export default function () {
  const url = 'https://jsonplaceholder.typicode.com/posts'; // API cần được đánh giá hiệu năng
   // các tham số đầu vào cho API hoạt động 
  const payload = JSON.stringify({
    title: 'foo',
    body: 'bar',
    userId: 1,
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
    },
  };

  // Gửi request HTTP với method POST
  const response = http.post(url, payload, params);

  // Validating response status
  check(response, {
    'status is 201': (r) => r.status === 201,
  });

  sleep(1);  // Giả lập thời gian cách quãng như trong thực tế
}

Trong đoạn mã viết bằng ngôn ngữ Javascript, có một vài cấu hình quan trọng sau:

  1. Duration và VUs:
    • duration: '3m': bài kiểm thử diễn ra trong vòng 3 phút.
    • vus: 50: mô phỏng 50 người dùng ảo thực hiện request đồng thời.
  2. Target URL:
  3. Request method:
    • http.post : lệnh tạo request với method POST cho phép gửi JSON payload.
  4. Validations:
    • check : function thiết lập điều kiện đúng cho các request, nếu không, ghi nhận request bị fail. Trong script của chúng ta, nếu response code của request trả về khác code 201, ghi nhận request đó bị fail.
  5. Pacing:
    • sleep(1) : function mô phỏng độ trễ phát sinh giữa những lần thao tác người dùng.

Run script

Hai yếu tố công cụ và kịch bản kiểm thử đã sẵn sàng, chúng ta tiến hành chạy kiểm thử với lệnh sau:

k6 run first_test.js

Thành phần cơ bản trong K6

Ngôn ngữ lập trình Javascript

Hiện tại, K6 chỉ hỗ trợ viết kịch bản trên một ngôn ngữ lập trình duy nhất, đó là JavaScript - một trong những ngôn ngữ dễ sử dụng nhất. Dưới đây là một vài tài liệu giúp bạn nhanh chóng làm quen với JavaScript:

Lifecyle

Trong K6, một kịch bản luôn chạy theo các giai đoạn theo thứ tự sau (nhưng không nhất thiết phải có đầy đủ cả 4 bước):

  1. Init: tải các tệp tin local, import modules.
  2. Configuration: thiết lập các cấu hình, dữ liệu chung cho bài test, ví dụ như thời gian chạy test, số lượng VU, …
  3. VU code: định nghĩa logic kiểm thử, ví dụ như tạo các http request, kiểm tra response. Đoạn code này sẽ được lặp đi lặp lại nhiều lần trong thời gian của bài test.
  4. Teardown: phần này sẽ định nghĩa các hành vi xử lý kết quả sau khi bài test được hoàn thành hoặc thất bại, ví dụ như gửi thông báo kết quả test.
// 1. init code
import http from 'k6/http';

// 2. configuration
export const options = {
  vus: 10,
  duration: '30s',
};

// 3. VU code
export function setup() {
  const res = http.get('https://quickpizza.grafana.com/api/json');
  return { data: res.json() };
}

// 4. teardown code
export default function (data) {
  console.log(JSON.stringify(data));
}

HTTP Requests

Đầu vào của bài kiểm thử là các http request. Các method của request được k6 hỗ trợ gồm:

  • DELETE
  • GET
  • HEAD
  • OPTIONS
  • PATCH
  • POST
  • PUT

Với những trường hợp gửi requests có kèm payload (API body) hay tùy chỉnh về headers thì chúng ta có thể bổ sung vào câu lệnh gọi API như dưới đây:

import http from 'k6/http';

export default function () {
  const url = 'http://test.k6.io/login';
  const payload = JSON.stringify({
    email: 'aaa',
    password: 'bbb',
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
    },
  };

  http.post(url, payload, params);
}

Checks

Kết quả của các request được sau khi được trả về, được kiểm tra bởi điều kiện trong phần check để xác định hệ thống phản hồi kết quả đúng. Ví dụ, một điều kiện check response.status == 201 là điều kiện để ghi nhận request POST được hệ thống trả về đúng.

Việc ghi nhận hệ thống phản hồi kết quả không đúng sẽ không ngừng bài test hay kết thúc với kết quả test thất bại, thay vào đó, K6 sẽ đo lường tỉ lệ thất bại của các request và tiếp tục chạy.

Trong mã nguồn k6, chúng ta sẽ sử dụng function check để kiểm tra response, ví dụ:

import { check } from 'k6';
import http from 'k6/http';

export default function () {
  const res = http.get('http://test.k6.io/');
  check(res, {
    'is status 200': (r) => r.status === 200,
  });
}

Bài test của bạn có thể xây dựng các điều kiện check kết quả trả về từ đơn giản tới phức tạp:

  • HTTP response code

    • Kiểm tra mã code trả về.
    • ví dụ: (r) => r.status === 200
  • Response body text

    • Kiểm tra thông tin văn bản trả về.
    • ví dụ: (r) => r.body.includes('Collection of simple web-pages suitable for load tesing')
  • Response body size

    • Kiểm tra kích cỡ của nội dung trả về.
    • ví dụ: (r) => r.body.length === 11105
  • Multiple checks

    • Kết hợp các điều kiện check
    • ví dụ:
    check(res, {
      'is status 200': (r) => r.status === 200,
      'body size is 11,105 bytes': (r) => r.body.length == 11105,
    });
    

Thresholds

Thresholds là những tiêu chí pass/fail sử dụng cho mục đích định nghĩa kết quả tổng quan của bài test. Nếu hiệu năng của hệ thống được kiểm thử (system under test - SUT) không đạt các yêu cầu mà bạn đặt ra, bài test sẽ kết thúc với trạng thái thất bại.

Thông thường, kỹ sư kiểm thử phần mềm sử dụng thresholds để ánh xạ với SLOs của ứng dụng, dịch vụ. Ví dụ, bạn tạo thresholds với những mục tiêu dưới đây:

  • Ít hơn 1% request trả về lỗi.
  • 95% request có thời gian phản hồi thấp hơn 200ms.
  • 99% request có thời gian phản hồi thấp hơn 400ms.
  • Một endpoint cụ thể luôn luôn phản hồi thấp hơn 300ms.

Với những bài test có thiết lập threshold, kết quả của bài test sẽ trực tiếp phản ánh kết quả đáp ứng yêu cầu hiệu năng của hệ thống.

Dưới đây, chúng ta sẽ có một ví dụ về threshold nhằm đánh giá hệ thống dựa trên lượng request thành công và thời gian phản hồi của chúng:

import http from 'k6/http';

export const options = {
  thresholds: {
    http_req_failed: ['rate<0.01'], // tỉ lệ request lỗi nhỏ hơn 1%
    http_req_duration: ['p(95)<200'], // 95% request cần đạt phản hồi dưới 200ms
  },
};

export default function () {
  http.get('https://test-api.k6.io/public/crocodiles/1/');
}

Để sử dụng được threshold trong K6 cho những trường hợp phức tạp, các tiêu chí đánh giá sẽ cần tổ hợp chính của các metrics, vui lòng tham khảo thêm về các metrics và cú pháp của threshold.

Tổng kết

Kiểm thử hiệu năng là điều cần thiết để đảm bảo tính tin cậy và khả năng mở rộng của ứng dụng. Bằng cách tận tụng K6, bạn có thể mô phỏng hành vi người dùng, phân tích thông số hiệu năng, và xác định những thành phần cần cải thiện trong ứng dụng. Với lợi thế về mã nguồn mở, tính dễ sử dụng, sự linh hoạt trong xây dựng kịch bản, K6 xứng đáng là một trong những lựa chọn hàng đầu của developer & tester trong hành trình tạo nên những ứng dụng, hệ thống làm hài lòng trải nghiệm người dùng.

Tài nguyên học tập: