Việc sử dụng Testcontainers đã cải thiện triệt để quá trình làm việc với các kịch bản thử nghiệm. Nhờ công cụ này, việc tạo môi trường cho các thử nghiệm tích hợp đã trở nên đơn giản hơn (xem bài viết Cách ly trong thử nghiệm với Kafka ). Giờ đây, chúng tôi có thể dễ dàng khởi chạy các vùng chứa với các phiên bản cơ sở dữ liệu, trình môi giới tin nhắn và các dịch vụ khác khác nhau. Đối với các thử nghiệm tích hợp, Testcontainers đã được chứng minh là không thể thiếu.
Mặc dù kiểm tra tải ít phổ biến hơn kiểm tra chức năng nhưng nó có thể thú vị hơn nhiều. Nghiên cứu biểu đồ và phân tích hiệu suất của một dịch vụ cụ thể có thể mang lại niềm vui thực sự. Những nhiệm vụ như vậy rất hiếm nhưng chúng đặc biệt thú vị đối với tôi.
Mục đích của bài viết này là trình bày cách tiếp cận để tạo thiết lập cho thử nghiệm tải giống như cách viết các thử nghiệm tích hợp thông thường: dưới dạng thử nghiệm Spock bằng cách sử dụng Testcontainers trong môi trường dự án Gradle. Các tiện ích kiểm tra tải như Gatling, WRK và Yandex.Tank được sử dụng.
Bộ công cụ: Gradle + Spock Framework + Testcontainer. Biến thể triển khai là một mô-đun Gradle riêng biệt. Các tiện ích kiểm tra tải được sử dụng là Gatling, WRK và Yandex.Tank.
Có hai cách tiếp cận để làm việc với đối tượng thử nghiệm:
Trong trường hợp đầu tiên, chúng tôi có một bộ thử nghiệm tải độc lập với phiên bản và các thay đổi của dự án. Cách tiếp cận này dễ duy trì hơn trong tương lai nhưng chỉ giới hạn ở việc thử nghiệm những hình ảnh đã xuất bản. Tất nhiên, chúng tôi có thể xây dựng những hình ảnh này một cách thủ công nhưng cách này ít tự động hơn và làm giảm khả năng tái tạo. Khi chạy trong CI/CD mà không có hình ảnh cần thiết, các thử nghiệm sẽ thất bại.
Trong trường hợp thứ hai, các bài kiểm tra được chạy trên phiên bản mới nhất của dịch vụ. Điều này cho phép tích hợp các thử nghiệm tải vào CI và thu được các thay đổi về dữ liệu hiệu suất giữa các phiên bản dịch vụ. Tuy nhiên, kiểm tra tải thường mất nhiều thời gian hơn kiểm tra đơn vị. Quyết định đưa các thử nghiệm như vậy vào CI như một phần của cổng chất lượng là tùy thuộc vào bạn.
Bài viết này xem xét cách tiếp cận đầu tiên. Nhờ Spock, chúng tôi có thể chạy thử nghiệm trên nhiều phiên bản của dịch vụ để phân tích so sánh:
where: image | _ 'avvero/sandbox:1.0.0' | _ 'avvero/sandbox:1.1.0' | _
Điều quan trọng cần lưu ý là mục tiêu của bài viết này là trình bày cách tổ chức không gian thử nghiệm chứ không phải thử nghiệm tải toàn diện.
Đối với đối tượng thử nghiệm, hãy lấy một dịch vụ HTTP đơn giản có tên Sandbox, dịch vụ này xuất bản điểm cuối và sử dụng dữ liệu từ nguồn bên ngoài để xử lý các yêu cầu. Dịch vụ này có cơ sở dữ liệu.
Mã nguồn của dịch vụ, bao gồm Dockerfile, có sẵn trong kho dự án spring-sandbox .
Khi chúng ta đi sâu vào chi tiết ở phần sau của bài viết, tôi muốn bắt đầu với một cái nhìn tổng quan ngắn gọn về cấu trúc của mô-đun Gradle load-tests
để hiểu rõ về thành phần của nó:
load-tests/ |-- src/ | |-- gatling/ | | |-- scala/ | | | |-- MainSimulation.scala # Main Gatling simulation file | | |-- resources/ | | | |-- gatling.conf # Gatling configuration file | | | |-- logback-test.xml # Logback configuration for testing | |-- test/ | | |-- groovy/ | | | |-- pw.avvero.spring.sandbox/ | | | | |-- GatlingTests.groovy # Gatling load test file | | | | |-- WrkTests.groovy # Wrk load test file | | | | |-- YandexTankTests.groovy # Yandex.Tank load test file | | |-- java/ | | | |-- pw.avvero.spring.sandbox/ | | | | |-- FileHeadLogConsumer.java # Helper class for logging to a file | | |-- resources/ | | | |-- wiremock/ | | | | |-- mappings/ # WireMock setup for mocking external services | | | | | |-- health.json | | | | | |-- forecast.json | | | |-- yandex-tank/ # Yandex.Tank load testing configuration | | | | |-- ammo.txt | | | | |-- load.yaml | | | | |-- make_ammo.py | | | |-- wrk/ # LuaJIT scripts for Wrk | | | | |-- scripts/ | | | | | |-- getForecast.lua |-- build.gradle
Kho lưu trữ dự án - https://github.com/avvero/testing-bench .
Từ mô tả ở trên, chúng tôi thấy rằng dịch vụ có hai phần phụ thuộc: dịch vụ https://external-weather-api.com và cơ sở dữ liệu. Mô tả của chúng sẽ được cung cấp bên dưới, nhưng hãy bắt đầu bằng cách cho phép tất cả các thành phần của lược đồ giao tiếp trong môi trường Docker - chúng tôi sẽ mô tả mạng:
def network = Network.newNetwork()
và cung cấp bí danh mạng cho từng thành phần. Điều này cực kỳ thuận tiện và cho phép chúng ta mô tả tĩnh các tham số tích phân.
Các phần phụ thuộc như WireMock và bản thân các tiện ích kiểm tra tải yêu cầu cấu hình để hoạt động. Đây có thể là các tham số có thể được chuyển đến vùng chứa hoặc toàn bộ tệp và thư mục cần được gắn vào vùng chứa.
Ngoài ra, chúng ta cần lấy kết quả công việc của chúng từ các container. Để giải quyết các tác vụ này, chúng tôi cần cung cấp hai bộ thư mục:
workingDirectory
— thư mục tài nguyên của mô-đun, trực tiếp tại load-tests/
.
reportDirectory
- thư mục chứa kết quả công việc, bao gồm số liệu và nhật ký. Thông tin thêm về điều này sẽ có trong phần báo cáo.Dịch vụ Sandbox sử dụng Postgres làm cơ sở dữ liệu. Hãy mô tả sự phụ thuộc này như sau:
def postgres = new PostgreSQLContainer<>("postgres:15-alpine") .withNetwork(network) .withNetworkAliases("postgres") .withUsername("sandbox") .withPassword("sandbox") .withDatabaseName("sandbox")
Tuyên bố chỉ định bí danh mạng postgres
mà dịch vụ Sandbox sẽ sử dụng để kết nối với cơ sở dữ liệu. Để hoàn thành mô tả tích hợp với cơ sở dữ liệu, dịch vụ cần được cung cấp các tham số sau:
'spring.datasource.url' : 'jdbc:postgresql://postgres:5432/sandbox', 'spring.datasource.username' : 'sandbox', 'spring.datasource.password' : 'sandbox', 'spring.jpa.properties.hibernate.default_schema': 'sandbox'
Cấu trúc cơ sở dữ liệu được chính ứng dụng quản lý bằng Flyway, do đó không cần thao tác cơ sở dữ liệu bổ sung trong thử nghiệm.
Nếu chúng tôi không có khả năng, sự cần thiết hoặc mong muốn chạy thành phần thực tế trong vùng chứa, chúng tôi có thể cung cấp bản mô phỏng cho API của nó. Đối với dịch vụ https://external-weather-api.com, WireMock được sử dụng.
Phần khai báo của WireMock container sẽ như sau:
def wiremock = new GenericContainer<>("wiremock/wiremock:3.5.4") .withNetwork(network) .withNetworkAliases("wiremock") .withFileSystemBind("${workingDirectory}/src/test/resources/wiremock/mappings", "/home/wiremock/mappings", READ_WRITE) .withCommand("--no-request-journal") .waitingFor(new LogMessageWaitStrategy().withRegEx(".*https://wiremock.io/cloud.*")) wiremock.start()
WireMock yêu cầu cấu hình mô phỏng. Lệnh withFileSystemBind
mô tả liên kết hệ thống tệp giữa đường dẫn tệp cục bộ và đường dẫn bên trong vùng chứa Docker. Trong trường hợp này, thư mục "${workingDirectory}/src/test/resources/wiremock/mappings"
trên máy cục bộ sẽ được gắn vào /home/wiremock/mappings
bên trong vùng chứa WireMock.
Dưới đây là một phần bổ sung của cấu trúc dự án để hiểu thành phần tệp trong thư mục:
load-tests/ |-- src/ | |-- test/ | | |-- resources/ | | | |-- wiremock/ | | | | |-- mappings/ | | | | | |-- health.json | | | | | |-- forecast.json
Để đảm bảo rằng các tệp cấu hình mô phỏng được WireMock tải và chấp nhận chính xác, bạn có thể sử dụng vùng chứa trợ giúp:
helper.execInContainer("wget", "-O", "-", "http://wiremock:8080/health").getStdout() == "Ok"
Vùng chứa trợ giúp được mô tả như sau:
def helper = new GenericContainer<>("alpine:3.17") .withNetwork(network) .withCommand("top")
Nhân tiện, IntelliJ IDEA phiên bản 2024.1 đã giới thiệu hỗ trợ cho WireMock và IDE cung cấp các đề xuất khi tạo các tệp cấu hình mô phỏng.
Tuyên bố của bộ chứa dịch vụ Sandbox trông như sau:
def javaOpts = ' -Xloggc:/tmp/gc/gc.log -XX:+PrintGCDetails' + ' -XX:+UnlockDiagnosticVMOptions' + ' -XX:+FlightRecorder' + ' -XX:StartFlightRecording:settings=default,dumponexit=true,disk=true,duration=60s,filename=/tmp/jfr/flight.jfr' def sandbox = new GenericContainer<>(image) .withNetwork(network) .withNetworkAliases("sandbox") .withFileSystemBind("${reportDirectory}/logs", "/tmp/gc", READ_WRITE) .withFileSystemBind("${reportDirectory}/jfr", "/tmp/jfr", READ_WRITE) .withEnv([ 'JAVA_OPTS' : javaOpts, 'app.weather.url' : 'http://wiremock:8080', 'spring.datasource.url' : 'jdbc:postgresql://postgres:5432/sandbox', 'spring.datasource.username' : 'sandbox', 'spring.datasource.password' : 'sandbox', 'spring.jpa.properties.hibernate.default_schema': 'sandbox' ]) .waitingFor(new LogMessageWaitStrategy().withRegEx(".*Started SandboxApplication.*")) .withStartupTimeout(Duration.ofSeconds(10)) sandbox.start()
Các tham số và cài đặt JVM đáng chú ý bao gồm:
Ngoài ra, các thư mục được cấu hình để lưu kết quả chẩn đoán của dịch vụ.
Nếu bạn cần xem nhật ký của bất kỳ vùng chứa nào trong một tệp, điều này có thể cần thiết trong giai đoạn viết và cấu hình kịch bản thử nghiệm, bạn có thể sử dụng hướng dẫn sau khi mô tả vùng chứa:
.withLogConsumer(new FileHeadLogConsumer("${reportDirectory}/logs/${alias}.log"))
Trong trường hợp này, lớp FileHeadLogConsumer
được sử dụng, cho phép ghi một lượng nhật ký giới hạn vào một tệp. Điều này được thực hiện vì toàn bộ nhật ký có thể không cần thiết trong các tình huống kiểm tra tải và một phần nhật ký sẽ đủ để đánh giá xem dịch vụ có hoạt động chính xác hay không.
Có nhiều công cụ để kiểm tra tải. Trong bài viết này, tôi đề xuất xem xét sử dụng ba trong số chúng: Gatling, Wrk và Yandex.Tank. Tất cả ba công cụ có thể được sử dụng độc lập với nhau.
Gatling là một công cụ kiểm tra tải nguồn mở được viết bằng Scala. Nó cho phép tạo ra các kịch bản thử nghiệm phức tạp và cung cấp các báo cáo chi tiết. Tệp mô phỏng chính của Gatling được kết nối dưới dạng tài nguyên Scala với mô-đun, giúp thuận tiện khi làm việc với phạm vi hỗ trợ đầy đủ từ IntelliJ IDEA, bao gồm tô sáng cú pháp và điều hướng thông qua các phương pháp để tham khảo tài liệu.
Cấu hình vùng chứa cho Gatling như sau:
def gatling = new GenericContainer<>("denvazh/gatling:3.2.1") .withNetwork(network) .withFileSystemBind("${reportDirectory}/gatling-results", "/opt/gatling/results", READ_WRITE) .withFileSystemBind("${workingDirectory}/src/gatling/scala", "/opt/gatling/user-files/simulations", READ_WRITE) .withFileSystemBind("${workingDirectory}/src/gatling/resources", "/opt/gatling/conf", READ_WRITE) .withEnv("SERVICE_URL", "http://sandbox:8080") .withCommand("-s", "MainSimulation") .waitingFor(new LogMessageWaitStrategy() .withRegEx(".*Please open the following file: /opt/gatling/results.*") .withStartupTimeout(Duration.ofSeconds(60L * 2)) ); gatling.start()
Việc thiết lập gần như giống hệt với các vùng chứa khác:
reportDirectory
.workingDirectory
.workingDirectory
.
Ngoài ra, các tham số được chuyển đến vùng chứa:
SERVICE_URL
có giá trị URL cho dịch vụ Hộp cát. Mặc dù, như đã đề cập trước đó, việc sử dụng bí danh mạng cho phép mã hóa cứng URL trực tiếp trong mã kịch bản.
-s MainSimulation
để chạy một mô phỏng cụ thể.
Dưới đây là lời nhắc về cấu trúc tệp nguồn dự án để hiểu những gì đang được chuyển và ở đâu:
load-tests/ |-- src/ | |-- gatling/ | | |-- scala/ | | | |-- MainSimulation.scala # Main Gatling simulation file | | |-- resources/ | | | |-- gatling.conf # Gatling configuration file | | | |-- logback-test.xml # Logback configuration for testing
Vì đây là vùng chứa cuối cùng và chúng tôi mong đợi nhận được kết quả sau khi hoàn thành nên chúng tôi đặt kỳ vọng .withRegEx(".*Please open the following file: /opt/gatling/results.*")
. Quá trình kiểm tra sẽ kết thúc khi thông báo này xuất hiện trong nhật ký vùng chứa hoặc sau 60 * 2
giây.
Tôi sẽ không đi sâu vào DSL trong các tình huống của công cụ này. Bạn có thể kiểm tra mã của kịch bản đã sử dụng trong kho dự án .
Wrk là một công cụ kiểm tra tải đơn giản và nhanh chóng. Nó có thể tạo ra tải đáng kể với nguồn lực tối thiểu. Các tính năng chính bao gồm:
Cấu hình vùng chứa cho Wrk như sau:
def wrk = new GenericContainer<>("ruslanys/wrk") .withNetwork(network) .withFileSystemBind("${workingDirectory}/src/test/resources/wrk/scripts", "/tmp/scripts", READ_WRITE) .withCommand("-t10", "-c10", "-d60s", "--latency", "-s", "/tmp/scripts/getForecast.lua", "http://sandbox:8080/weather/getForecast") .waitingFor(new LogMessageWaitStrategy() .withRegEx(".*Transfer/sec.*") .withStartupTimeout(Duration.ofSeconds(60L * 2)) ) wrk.start()
Để Wrk hoạt động với các yêu cầu tới dịch vụ Sandbox, cần phải có mô tả yêu cầu thông qua tập lệnh Lua, vì vậy chúng tôi gắn thư mục tập lệnh từ workingDirectory
. Sử dụng lệnh, chúng tôi chạy Wrk, chỉ định tập lệnh và URL của phương thức dịch vụ đích. Wrk viết báo cáo vào nhật ký dựa trên kết quả của nó, báo cáo này có thể được sử dụng để đặt kỳ vọng.
Yandex.Tank là một công cụ kiểm tra tải được phát triển bởi Yandex. Nó hỗ trợ nhiều công cụ kiểm tra tải khác nhau, chẳng hạn như JMeter và Phantom. Để lưu trữ và hiển thị kết quả kiểm tra tải, bạn có thể sử dụng dịch vụ Overload miễn phí.
Đây là cấu hình vùng chứa:
copyFiles("${workingDirectory}/src/test/resources/yandex-tank", "${reportDirectory}/yandex-tank") def tank = new GenericContainer<>("yandex/yandex-tank") .withNetwork(network) .withFileSystemBind("${reportDirectory}/yandex-tank", "/var/loadtest", READ_WRITE) .waitingFor(new LogMessageWaitStrategy() .withRegEx(".*Phantom done its work.*") .withStartupTimeout(Duration.ofSeconds(60L * 2)) ) tank.start()
Cấu hình kiểm tra tải cho Sandbox được thể hiện bằng hai tệp: load.yaml
và ammo.txt
. Là một phần của mô tả vùng chứa, các tệp cấu hình được sao chép vào reportDirectory
, tệp này sẽ được gắn làm thư mục làm việc. Đây là cấu trúc của các tệp nguồn dự án để hiểu những gì đang được truyền và ở đâu:
load-tests/ |-- src/ | |-- test/ | | |-- resources/ | | | |-- yandex-tank/ | | | | |-- ammo.txt | | | | |-- load.yaml | | | | |-- make_ammo.py
Kết quả kiểm tra, bao gồm các bản ghi và nhật ký hiệu suất JVM, được lưu trong thư mục build/${timestamp}
, trong đó ${timestamp}
biểu thị dấu thời gian của mỗi lần chạy thử nghiệm.
Các báo cáo sau đây sẽ có sẵn để xem xét:
Nếu Gatling được sử dụng:
Nếu Wrk đã được sử dụng:
Nếu Yandex.Tank đã được sử dụng:
Cấu trúc thư mục cho các báo cáo như sau:
load-tests/ |-- build/ | |-- ${timestamp}/ | | |-- gatling-results/ | | |-- jfr/ | | |-- yandex-tank/ | | |-- logs/ | | | |-- sandbox.log | | | |-- gatling.log | | | |-- gc.log | | | |-- wiremock.log | | | |-- wrk.log | | | |-- yandex-tank.log | |-- ${timestamp}/ | |-- ...
Kiểm tra tải là một giai đoạn quan trọng trong vòng đời phát triển phần mềm. Nó giúp đánh giá hiệu suất và tính ổn định của ứng dụng trong các điều kiện tải khác nhau. Bài viết này trình bày một cách tiếp cận để tạo môi trường thử nghiệm tải bằng Testcontainers, cho phép thiết lập môi trường thử nghiệm dễ dàng và hiệu quả.
Bộ chứa thử nghiệm đơn giản hóa đáng kể việc tạo môi trường cho các thử nghiệm tích hợp, mang lại sự linh hoạt và tách biệt. Để kiểm tra tải, công cụ này cho phép triển khai các vùng chứa cần thiết với các phiên bản dịch vụ và cơ sở dữ liệu khác nhau, giúp tiến hành kiểm tra và cải thiện khả năng tái tạo kết quả dễ dàng hơn.
Các ví dụ về cấu hình được cung cấp cho Gatling, Wrk và Yandex.Tank, cùng với thiết lập vùng chứa, trình bày cách tích hợp hiệu quả các công cụ khác nhau và quản lý các tham số thử nghiệm.
Ngoài ra, quy trình ghi nhật ký và lưu kết quả kiểm tra cũng được mô tả, quy trình này rất cần thiết để phân tích và cải thiện hiệu suất ứng dụng. Cách tiếp cận này có thể được mở rộng trong tương lai để hỗ trợ các kịch bản phức tạp hơn và tích hợp với các công cụ giám sát và phân tích khác.
Cảm ơn bạn đã quan tâm đến bài viết này và chúc bạn may mắn trong nỗ lực viết các bài kiểm tra hữu ích!