Sử dụng GitHub Colpilot (AI) hỗ trợ viết kiểm thử (Test case) trong dự án Spring Boot

Có rất nhiều phương pháp kiểm thử được áp dụng trong phát triển phần mềm, nói riêng về Spring Boot cũng có nhiều loại kiểm thử khác nhau. Trong khuôn khổ của bài viết này mình tổng hợp lại một số phương pháp kiểm thử phần mềm hay được áp dụng trong các dự án.
Ngày nay với sự hỗ trợ AI trong việc phát triển việc viết các Test case trở lên dễ dàng hơn bao giờ hết, như trong bài trước , AI rất mạnh mẽ trong việc xử lý logic và viết test case là một thế mạnh của AI. Github Copilot trong IntelliJ bó tạo sẵn một câu lệnh cho việc viết test case cho class.
AI hỗ trợ mạnh mẽ, nhưng lập trình viên cũng cần hiểu được những cơ bản, nguyên tắc chung trong việc viết Test case để sử dụng AI hiệu quả hơn.

Trong bài này mình sẽ giới thiệu các phần chính sau
- Các loại kiểm thử
- Khái niệm về độ bao phủ
- Cách viết test case
- Unit Test
- Web Layer Test
- Integration Test
- Triển khai theo BDD
- Test-container
- Performance Test
- Kiểm tra độ bao phủ
Các loại kiểm thử trong Spring Boot
Trong khuôn khổ bài viết này mình giới thiệu với các bạn một loại kiểm thử mà mình đã thực hiện trong các dự án
Unit Tests
là test cơ bản nhất, Test từng thành phần độc lập (method trong Java) hoạt động như kỳ vọng. Hầu hết các dự án đều yêu cầu triển khai Unit TestsIntegration Test
dùng để kiểm thử việc tích hợp các thành phần trong hệ thống với nhau (như API kết nối đến tầng database), khi chạy test này app phải khởi chạy như một app thật, cần load đủ ngữ cảnh của Spring.Web Layer Tests
tập trung vào test phần controller của RESTful APIs, các tầng dưới có thể được giả lập, khi chạy không cần tải toàn bộ ngữ cảnh của SpringPerformance Tests
tập trung test về hiệu năng của hệ thống, đưa ra mức thời gian chấp nhận được của mỗi API, nếu vượt qua thời gian đó test case sẽ fail.
Ngoài ra một số loại kiểm thử khác các bạn có thể tìm hiểu trên các kênh khác
Smoke Test
Data Layer Tests
End-to-End (E2E) or System Tests
- khác nữa
Độ bao phủ code code coverage
Dùng để đo lường mức độ bao phủ của toàn bộ test case lên code của dự án, đánh giá theo dạng phần trăm. Bao phủ ở đây có thể hiểu là khi mỗi test case thực hiện một test với class, với hàm, logic test case chạy qua từng dòng code, đừng đoạn rẽ nhánh, chạy vào vòng lặp, thì được tính là đã bao phủ.
Có nhiều loại bao phủ khác nhau, trong spring boot người ta thường tính các loại bao phủ (còn các loại bao phủ khác, các bạn có thể tìm hiểu thêm)
class coverage
bao phủ lớpmethod coverage
bao phủ methodbranch coverage
bảo phủ rẽ nhánh (có bao nhiêu case if/else switch các test case cần phải chạy qua)line coverage
bao phủ dòng
Trong quá trình triển khai thực tế mình thấy branch coverage
, là khó viết nhất, nhất là code có nhiều if/else hoặc switch case.
Kiểm thử là một bước thường không thể thiếu trong quy trình triển khai CICD, một bước kiểm thử được gọi là PASS/SUCCESS khi toàn bộ các test là pass và code coverage đạt được mức đề xuất.
Mức đề xuất cho code coverage là bao nhiêu? Cài này tùy thuộc vào loại dự án, theo nhu cầu của chủ đâu tư, các PM/Leader sẽ quyết định mức này, vì mức code coverage cao làm cho chương trình ổn định hơn nhưng tốn chi phí nhân sự. Trong các dự án mình đã tham gia thì sẽ có mức code coverage từ 50% đến 90%.
jacoco
là công cụ để đo độ bao phủ code trong dự án Java, và jacoco thường kết hợp với SonarQube là một bước trong quy trình CICD để kiểm tra độ bao phủ và chất lượng code, jacoco sẽ gửi báo cáo đánh giá code coverage cho SonarQube phân tích và thực hiện thêm các test về an ninh và chất lượng code, sau đó trả lại tín hiệu pass/fail cho CICD
Cách viết test case
Trong mục này mình sẽ trình bày chi tiết về cách triển khai các loại kiểm thử đã liệt kê ở phần trên.
Nguyên tắc cho viết test cũng là áp dụng tối đa AI nhưng cần hiểu nguyên lý hoạt động để xây dựng các prompt
hiệu quả hơn, và có thể xử lý được các tình huống AI đưa ra kết quả sai.
Unit Tests
Trong Spring Boot sẽ dựa vào JUnit và Mockito để viết các test case.
Luôn viết đủ Happy Case và Unhappy Case cho mỗi class
Nguyên tắc của Unit Tests là test độc lập các thành phần với nhau, nhưng nếu tại một component/service muốn test có phụ thuộc vào các component/service khác sẽ xử lý như thế nào?
Trong ProductService có inject để sử dụng 2 service ProductMapper
và ProductRepository
Khi viết Unit Test ta sẽ @InjectMocks service/component cần test và @Mock các service/component phụ thuộc
@Mock
private ProductMapper productMapper;
@Mock
private ProductEntityRepository productRepository;
@InjectMocks
private ProductService productService;
Các method trong ProductService sẽ gọi đến các method trong ProductMapper
và ProductRepository
, nếu ta không @Mock sẽ gây ra nullpoiter
.
Khi muốn giả lập các case có thế sảy ra trong thực tế ta sẽ dùng when
để giả định các kết quả hoạt động của một method trong các đối tượng được @Mock
when(productMapper.toProductEntity(request, userId)).thenReturn(entity);
when(productRepository.save(entity)).thenReturn(savedEntity);
Mỗi test case phải có ít nhật một kỳ vọng trả về của hàm được test. Có nhiều loại kỳ vọng kết quả của một test case
- Hàm đã được gọi
verify
- Kỳ vọng kết quả bằng hoặc không bằng
assertThat
- Kỳ vọng có ngoại lệ sảy ra
assertThrows
Một trong những kỹ thuật hay sử dụng trong Unit Test là dùng ArgumentCaptor
để lấy đối tượng được truyền vào khi một hàm được gọi
ArgumentCaptor<ProductEntity> captor = ArgumentCaptor.forClass(ProductEntity.class);
verify(productRepository).save(captor.capture());
ProductEntity capturedEntity = captor.getValue();
Như trên ta khai báo một captor cho class ProductEntity
, khi hàm productRepository gọi method save, nó sẽ “chụp lại” đối tượng này thông qua captor.capture()
ta có thể mang đối tượng này làm giá trị tham chiếu cho các hàm kỳ vọng.
JUnit hỗ trợ các annotation @BeforeEach
@BeforeAll
@AfterEach
@AfterAll
để thực hiện các nghiệp vụ trước và sau khi chạy test toàn bộ hoặc từng test case
Khi dùng AI viết code ta gọi mặc định câu lệnh /tests hệ thống sẽ tự hiểu viết UnitTests cho class đang ở màn hình active trên IntelliJ. Test case sinh ra có thể có lỗi, có thể do hàm được test thiếu các case logic của test case yêu cầu hoặc có thể test case logic bị sai. Tùy trường hợp ta cập nhật vào app của chương trình hoặc cập nhật test case.
AI sinh ra gần như đầy đủ các case cho Unit Tests, nếu muốn bổ xung các case khác ta viết thêm lệnh, như bên dưới là thêm lệnh cho tạo test case có sử dụng Capter
I think have a way to use Captor data when test, do you know about it?
sau đócan you help me make a new method to use Captor?
Đôi khi AI đưa ra test case tối thiểu, không có import và tên hiển thị chúng ta có thể yêu cầu một phiên bản đầy đủ, đây là một câu mình hay dùng
Can you give me full class with import
hoặc đơn giản ngay từ đầu ta yêu cầu như sau/tests with full class and import
Đây là một hàm Unit Test đầy đủ được tạo ra bởi AI và có một số chỉnh sửa nhỏ của developer, mọi người tham khảo
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
@Mock
private ProductMapper productMapper;
@Mock
private ProductEntityRepository productRepository;
@InjectMocks
private ProductService productService;
@BeforeEach
void setUp() {
// No setup required for now
}
@Test
@DisplayName("Should save product and return response when valid request is provided")
void shouldSaveProductAndReturnResponseWhenValidRequestProvided() {
ProductRequest request = new ProductRequest();
String userId = "user-123";
ProductEntity entity = new ProductEntity();
ProductEntity savedEntity = new ProductEntity();
ProductResponse expectedResponse = new ProductResponse();
when(productMapper.toProductEntity(request, userId)).thenReturn(entity);
when(productRepository.save(entity)).thenReturn(savedEntity);
when(productMapper.toProductResponse(savedEntity)).thenReturn(expectedResponse);
ProductResponse actualResponse = productService.saveProduct(request, userId);
assertThat(actualResponse).isEqualTo(expectedResponse);
verify(productMapper).toProductEntity(request, userId);
verify(productRepository).save(entity);
verify(productMapper).toProductResponse(savedEntity);
}
@Test
@DisplayName("Should capture and verify ProductEntity passed to repository")
void shouldCaptureProductEntityPassedToRepository() {
ProductRequest request = new ProductRequest();
String userId = "user-123";
ProductEntity entity = new ProductEntity();
ProductEntity savedEntity = new ProductEntity();
ProductResponse expectedResponse = new ProductResponse();
when(productMapper.toProductEntity(request, userId)).thenReturn(entity);
when(productRepository.save(any(ProductEntity.class))).thenReturn(savedEntity);
when(productMapper.toProductResponse(savedEntity)).thenReturn(expectedResponse);
productService.saveProduct(request, userId);
ArgumentCaptor<ProductEntity> captor = ArgumentCaptor.forClass(ProductEntity.class);
verify(productRepository).save(captor.capture());
ProductEntity capturedEntity = captor.getValue();
assertThat(capturedEntity).isEqualTo(entity);
}
@Test
@DisplayName("Should throw NullPointerException when userId is null")
void shouldThrowExceptionWhenUserIdIsNull() {
ProductRequest request = new ProductRequest();
assertThrows(NullPointerException.class, () -> {
productService.saveProduct(request, null);
});
}
@Test
@DisplayName("Should propagate exception when repository save fails")
void shouldPropagateExceptionWhenRepositorySaveFails() {
ProductRequest request = new ProductRequest();
String userId = "user-123";
ProductEntity entity = new ProductEntity();
when(productMapper.toProductEntity(request, userId)).thenReturn(entity);
when(productRepository.save(entity)).thenThrow(new RuntimeException("DB error"));
assertThrows(RuntimeException.class, () -> {
productService.saveProduct(request, userId);
});
}
}
Web Layer Test
Có thể coi đây là một Unit Test đặc biệt cho các Endpoint APIs, chúng ta không dùng Unit Test thông thường @ExtendWith(MockitoExtension.class)
thay vào đó chúng ta sử dụng @WebMvcTest
, nó sẽ khởi động Spring Boot app nhưng không load toàn bộ context mà chỉ load những thành phần cho Spring MVC (controller, controller advice, filter), không load tầng database và service. Sau đó ta sẽ dùng thư viện MockMvc
để giả định cho các gọi RESTful API như POST /products
, GET /products/{productId}
Oh, nếu không khởi động tầng database thì dữ liệu test sẽ ở đâu? Giống như UnitTest các tầng phụ thuộc sẽ được @MockBean để đưa vào test, ta cũng sử dụng when
để giả định các trường hợp thực tế của dữ liệu
Để sử dụng AI cho tạo test case cho Web Layer Test ta đơn giản chỉ cần gọi lệnh
can you help me write Web Layer Test for ProductApis
, gần như toàn bộ test case sẽ được tạo ra, cũng có thể các test case có lỗi, nhưng nếu ta hiểu nguyên lý hoạt động củaWebMvcTest
sẽ dễ dàng xử lý lỗi hơn.
Đây là test case được sinh ra bởi AI có chỉnh sửa bởi develop và yêu cầu bổ xung unhappy case
@WebMvcTest(ProductApis.class)
class ProductApisWebLayerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private ProductService productService;
@MockitoBean
private ProductValidator productValidator;
@Autowired
private ObjectMapper objectMapper;
@Test
@DisplayName("Should create product and return response")
void shouldCreateProductAndReturnResponse() throws Exception {
ProductRequest request = new ProductRequest(UUID.randomUUID(), "Test", "SKU", new BigDecimal("10.0"), "desc");
ProductResponse response = new ProductResponse(UUID.randomUUID(), "Test", "SKU", new BigDecimal("10.0"), "desc", "ACTIVE", LocalDateTime.now(), LocalDateTime.now());
Mockito.when(productService.saveProduct(any(ProductRequest.class), anyString())).thenReturn(response);
mockMvc.perform(post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Test"));
}
@Test
@DisplayName("Should return 400 Bad Request when service throws exception")
void shouldReturnBadRequestWhenServiceThrowsException() throws Exception {
ProductRequest request = new ProductRequest(UUID.randomUUID(), "Test", "SKU", new BigDecimal("10.0"), "desc");
Mockito.when(productService.saveProduct(any(ProductRequest.class), anyString()))
.thenThrow(new IllegalArgumentException("Invalid product"));
mockMvc.perform(post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
}
Integration Test – Kiểm thử tích hợp
Trong phần này mình sẽ giới thiệu với các bạn về Integration Test (sau đây viết tắt là IT hoặc IT test) và hai vấn đề mở rộng của IT test
- Áp dụng IT test theo mô hình phát triển BDD
- Sử dụng test-container khi chạy test tích hợp
IT test là gì
Như phần trên khi tìm hiểu về Web Layer Test
ta biết khi sử dụng WebMvcTest
, khi app chạy lên chỉ load những thành phần cần thiết để cho test APIs, các tầng liên quan đến service/mapper/repo sẽ không được load. Với IT test khi chạy nó sẽ khởi chạy như một app bình thường với số port là ngẫu nhiên, toàn bộ ngữ cảnh được tải lên ứng dụng, sau đó ta sẽ viết các test case gọi đến APIs theo logic nghiệp vụ của từng test case để đảm bảo app chạy đúng như kỳ vọng.
Ta sử dụng @SpringBootTest
để khởi chạy ứng dụng và MockMvc
như một RESTful client để test các API.
Ta dùng câu lệnh sau để tạo IT test cho app
can you help me write Integration test for this app
, nếu thấy chưa có Unhappy case ta có thể yêu cầu thêmcan you add unhappy case ?
Dưới đây là đoạn IT test được sinh ra bởi AI và có điều chỉnh bởi developer
@SpringBootTest
@AutoConfigureMockMvc
class ProductApisIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
@DisplayName("Should create and retrieve product successfully")
void shouldCreateAndRetrieveProductSuccessfully() throws Exception {
ProductRequest request = new ProductRequest(
UUID.randomUUID(), "Integration Product", "SKU-INT", new BigDecimal("99.99"), "Integration test"
);
// Create product
String responseBody = mockMvc.perform(post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Integration Product"))
.andReturn().getResponse().getContentAsString();
ProductResponse response = objectMapper.readValue(responseBody, ProductResponse.class);
// Retrieve product
mockMvc.perform(get("/products/{uuid}", response.getUuid()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Integration Product"))
.andExpect(jsonPath("$.sku").value("SKU-INT"));
}
@Test
@DisplayName("Should return 404 when product not found")
void shouldReturnNotFoundWhenProductDoesNotExist() throws Exception {
UUID nonExistentUuid = UUID.randomUUID();
mockMvc.perform(get("/products/{uuid}", nonExistentUuid))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.error").exists())
.andExpect(jsonPath("$.message").value("Product with UUID does not exist: " + nonExistentUuid));
}
}
IT test theo mô hình phát triển BDD
Trong một dự án phát triển phần mềm theo mô hình BDD
, các kịch bản test có thể được viết trước khi code được implement, developer sẽ phát triển code thỏa mãn các điều kiện mong muốn.
Một số lưu ý khi triển khai Gherkin/Cucumber
- Nên tách nhỏ file feature theo từng API, và luôn viết đủ happy và unhappy case
- Với Spring Boot 3 sử dụng JUnit 5, do vậy cầu hình với
@RunWith(Cucumber.class)
sẽ không hoạt động, mình sẽ hướng dẫn cấu hình thông qua annoation
Gia sử ta có một file Gherkin mô tả nghiệp vụ của API POST /products
Feature: Add Product API
Scenario Outline: Successful Create and retrieve a product
Given a product payload with '<name>' '<desc>' '<sku>' and <price>
When I send a POST request to the products endpoint
Then the product is created successfully
And I can retrieve the product by UUID
Examples:
| name | desc | sku | price |
| Property | Desc | SKU | 1500 |
| Property 2 | Desc | SKU | 1500 |
Scenario Outline: Unsuccessful Create a product with same name
Given a product payload with '<name>' '<desc>' '<sku>' and <price>
When I send a POST request to the products endpoint
Then I receive an error response indicating the product already exists with the same name
Examples:
| name | desc | sku | price |
| Property | Desc-1 | SKU-01 | 1500 |
Scenario: Unsuccessful Create a product with missing fields
Given a product payload with missing fields
When I send a POST request to the products endpoint
Then I receive an error response indicating missing fields
Về cách viết file feature thì có nhiều hướng dẫn trên mạng hoặc có thể nhờ AI giải thích, mình ghi chú lại một số ý quan trọng trong Gherkin feature file.
- Scenario sẽ có 2 loại Scenario và Scenario Outline.
Scenario Outline
nhận dữ liệu từ bảngExamples
, Gherkin hỗ trợ 2 loại dữ liệu: string, int và float, với loại dữ liệu string thì tên biến phải có dấu nháy đơn, mọi người để ý biếnname
vàprice
có khai báo khác nhau. Mỗi dòng dữ liệu trong Examples sẽ chạy kịch bản Scenario Outline một lần - Thứ tự chạy kịch bản theo thứ tự khai báo trong file feature
- Có nhiều từ khóa khác ngoài những từ khóa hiển thị trọng file mẫu, mọi người tham khảo thêm trên các kênh khác, trong các dự án mình đã làm thì những từ khóa trên gần như là đủ.
Viết một implement cho feature cần những gì
Định nghĩa 1 file khai báo sử dụng Cucumber Engine test
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(key = Constants.PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = Constants.PLUGIN_PROPERTY_NAME, value = "html:target/cucumber-reports.html")
@ConfigurationParameter(key = Constants.GLUE_PROPERTY_NAME, value = "com.koder.course.applayered.it.cucumber.steps")
public class CucumberTest {
// Empty test runner class - configuration is handled by annotations
}
Phần này anh em tham khảo AI (mình dùng Copilot với GPT 4.1, có thể khi các bạn sử dụng cho kết quả khác) sẽ hướng dẫn chúng ta sử dụng
@RunWith(Cucumber.class)
sẽ không dùng được (hoặc sẽ phải cấu hình rất nhiều) khi vừa kết hợp JUnit 5 và JUnit 4.
Định nghĩa 1 file cấu hình cho chạy Spring Boot
@CucumberContextConfiguration
@SpringBootTest(classes = TutsAppLayeredApplication.class)
@AutoConfigureMockMvc
public class CucumberSpringConfiguration {
}
Implement các steps được định nghĩa trong feature file
Mỗi Given
, When
, Then
, And
là một method trong Steps class mà chúng ta cần implement.
Có 2 đối tượng quan trọng khi thực hiện implement là MockMvc
và MvcResult
để gọi API và lưu kết quả của việc gọi API.
Khai báo đối tượng cần sử dụng trong Steps class AddProductStepDefs
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
private ProductRequest request;
private ProductResponse response;
private MvcResult mvcResult;
private UUID createdProductUuid;
Implement một bước trọng kịch bản, tương ứng với một method trong Java
@Given("a product payload with {string} {string} {string} and {float}")
public void a_product_payload_with_name_property(String name, String desc, String sku, double price) {
request = new ProductRequest(null, name, sku, new BigDecimal(price), desc);
}
@When("I send a POST request to the products endpoint")
public void i_send_a_post_request_to_products() throws Exception {
mvcResult = mockMvc.perform(post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andReturn();
if (mvcResult.getResponse().getStatus() == 201) {
response = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), ProductResponse.class);
createdProductUuid = response.getUuid();
}
}
Kết quả sau khi chạy ta có

Test-container
Test-container ngày càng phổ biến và hỗ trợ nhiều loại container khách nhau, danh sách các image hỗ trợ mọi người có thể xem tại trang chủ của test-container.
Là một thư viện rất hữu dụng trong việc triển khai test tích hợp trên môi trường CICD. Giả sử APP của chúng ta chạy test tích hợp cần kết nối tời DB, thay thì tạo sẵn một DB chỉ cho việc test, ta có thể cấu hình một test-container có image là database cần thiết cho chạy ứng dụng, khi ứng dụng chạy trên môi trường CICD hoặc ở local có sẵn Docker engine, nó sẽ tự pull và khởi chạy image cần thiết cho việc test.
Nếu không có Docker? Có tùy chọn cho phép không chạy nếu môi trường kiểm tra không có Docker.
Các bước cần cấu hình để chạy test-container
Bổ xung thư viện
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
Cập nhật cấu hình của test case
Với IT test thường
@Testcontainers //bổ xung annotation
class ProductApisIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
//khai báo này sẽ tự động chạy container
//và tự động cập nhật cấu hình kết nối trong application.yaml file
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15.8")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
}
Vơi IT test sử dụng Gherkin/Cucumber
Cập nhật vào file cấu hình
@Testcontainers
public class CucumberSpringConfiguration {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15.8")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
}
Performance Test – Kiểm thử hiệu năng
Ngữ cảnh của chúng ta là đang phát triển các backend API, vậy kiểm thử hiệu năng ở đây là kiểm thử về thời gian đáp ứng, tỷ lệ thành công trên một lượng active user trong một khoảng thời gian kỳ vọng khi tương tác với API
Có nhiều công cụ có thể dùng để kiểm tra hiệu năng: JMeter, Postman, Isomina… Để tích hợp vào quy trình CICD, cũng như thuận tiện cho việc kiểm thử tại dev local, thì Getling là một lựa chọn tốt.
Để làm quen với việc test hiệu năng anh em cũng cần làm quen với các khái niệm như concurrent/active user
, response time
, success/fail rate
Gatling được phát triển và thường xuyên cập nhật bản mới, nó là một plugin của Gradle và Maven, cách cài đặt và sử dụng rất đơn giản.
Gatling Test không tác động vào code coverage
Cấu hình plugin trong Gradle
plugins {
id 'io.gatling.gradle' version 'PHIEN_BAN_MOI_NHAT'
}
Viết kịch bản giả lập
Gatling hỗ trợ nhiều ngôn ngữ cho việc viết giả lập: Java, Scala, Kotlin
Nên chia nhở kịch bản giả lập theo từng API, một số kịch bản cần kết hợp gọi giây chuyền (chain) các API do vậy tùy tình huống mà ta áp dụng cho linh hoạt
Đây là một đoạn giả lập 2000 active user gọi API tạo sản phẩm trong vòng 5s viết bằng Java
public class ProductSimulation extends Simulation {
HttpProtocolBuilder httpProtocol = http
.baseUrl("http://localhost:8080")
.acceptHeader("application/json")
.contentTypeHeader("application/json");
ScenarioBuilder scn = scenario("Concurrent Save Product")
.exec(session -> session
.set("randomUuid", UUID.randomUUID().toString())
.set("randomName", UUID.randomUUID().toString())
)
.exec(
http("Save Product")
.post("/products")
.body(StringBody(session ->
"{ \"uuid\": \"" + session.getString("randomUuid") + "\", " +
"\"name\": \"" + session.getString("randomName") + "\", " +
"\"sku\": \"SKU-INT\", " +
"\"price\": 99.99, " +
"\"description\": \"Integration test\" }"
))
.check(status().is(201))
);
{
setUp(
scn.injectOpen(rampUsers(2000).during(5))
).protocols(httpProtocol);
}
}
Khi Gatling test là một bước trong CICD nó cần đưa ra tín hiệu OK hoặc FAIL, ta cần thêm điều kiện kỳ vọng vào trong gải lập
global().responseTime().percentile(95).lt(1000), // 95% of responses < 1s
global().successfulRequests().percent().gt(99.9)
Chạy Gatling trong Gradle
./gradlew gatlingRun
Đây report sau khi chạy Gatling

Kiểm tra độ bao phủ (codecoverage) của code
Khi dev ở phía local, các IDE như IntelliJ có hỗ trợ xem trực tiếp độ bao phủ của code

Khi ứng dụng chạy trên hệ thống CICD thì ta cần sử dụng jacoco
để report được độ bao phủ của code. Để sử dụng jacoco trong Gradle ta chỉ cần bật plugin jacoco
plugins {
id 'java'
id 'jacoco'
}
và chạy lệnh
./gradlew test jacocoTestReport
Trong báo cáo jacoco ta có thể xem tổng quan code coverage của cả hệ thống và xem chi tiết từng

Tổng kết
Có nhiều yếu tốt để làm lên một API chất lượng, và việc viết test case đầy đủ ở tầng develop là một trong các yếu tố quan trọng.
AI hỗ trợ rất nhiều cho việc dev tạo ra các test case nhưng dev cũng cần hiểu về thư viện test và các loại test hoạt động như thế nào để xử lý tình huống một cách hiệu quả.
Một yếu tố quan trọng hơn nữa, cái này thì phụ thuộc vào từng dev đó là các test case có ý nghĩa mô tả được các tình huống thực tế hơn là chỉ đơn thuần để đảm bảo code coverage
Thông số code-coverage trong những dự án mình tham gia thì những dự án của khách EU, US có yêu cầu coverage rất cao (80 đến 90%), các dự án ở Asia thì thấp hơn(hơn 50%), ở VN thì test chưa được đề cao.
Để lại một bình luận