paint-brush
Tạo các bài kiểm tra tích hợp hiệu quả: Các công cụ và phương pháp thực hành tốt nhất trong Spring Frameworktừ tác giả@avvero
534 lượt đọc
534 lượt đọc

Tạo các bài kiểm tra tích hợp hiệu quả: Các công cụ và phương pháp thực hành tốt nhất trong Spring Framework

từ tác giả Anton Belyaev8m2024/05/26
Read on Terminal Reader

dài quá đọc không nổi

Bài viết này đưa ra các đề xuất thiết thực để viết các bài kiểm thử tích hợp, trình bày cách tập trung vào các thông số kỹ thuật của sự tương tác với các dịch vụ bên ngoài, giúp các bài kiểm thử dễ đọc hơn và dễ bảo trì hơn. Cách tiếp cận này không chỉ nâng cao hiệu quả thử nghiệm mà còn thúc đẩy sự hiểu biết tốt hơn về các quy trình tích hợp trong ứng dụng. Thông qua lăng kính của các ví dụ cụ thể, các chiến lược và công cụ khác nhau - chẳng hạn như trình bao bọc DSL, JsonAssert và Pact - sẽ được khám phá, cung cấp cho người đọc hướng dẫn toàn diện để cải thiện chất lượng và khả năng hiển thị của các thử nghiệm tích hợp.
featured image - Tạo các bài kiểm tra tích hợp hiệu quả: Các công cụ và phương pháp thực hành tốt nhất trong Spring Framework
Anton Belyaev HackerNoon profile picture
0-item

Trong phát triển phần mềm hiện đại, kiểm thử hiệu quả đóng vai trò then chốt trong việc đảm bảo độ tin cậy và ổn định của ứng dụng.


Bài viết này đưa ra các đề xuất thực tế để viết các bài kiểm thử tích hợp, trình bày cách tập trung vào các thông số kỹ thuật của sự tương tác với các dịch vụ bên ngoài, giúp các bài kiểm thử dễ đọc hơn và dễ bảo trì hơn. Cách tiếp cận này không chỉ nâng cao hiệu quả thử nghiệm mà còn thúc đẩy sự hiểu biết tốt hơn về các quy trình tích hợp trong ứng dụng. Thông qua lăng kính của các ví dụ cụ thể, các chiến lược và công cụ khác nhau - chẳng hạn như trình bao bọc DSL, JsonAssert và Pact - sẽ được khám phá, cung cấp cho người đọc hướng dẫn toàn diện để cải thiện chất lượng và khả năng hiển thị của các thử nghiệm tích hợp.


Bài viết trình bày các ví dụ về kiểm tra tích hợp được thực hiện bằng Spock Framework trong Groovy để kiểm tra tương tác HTTP trong ứng dụng Spring. Đồng thời, các kỹ thuật và phương pháp tiếp cận chính được đề xuất có thể được áp dụng hiệu quả cho nhiều loại tương tác khác nhau ngoài HTTP.

Mô tả vấn đề

Bài viết Viết bài kiểm tra tích hợp hiệu quả trong mùa xuân: Chiến lược kiểm tra có tổ chức cho mô phỏng yêu cầu HTTP mô tả một cách tiếp cận để viết bài kiểm tra với sự phân tách rõ ràng thành các giai đoạn riêng biệt, mỗi giai đoạn thực hiện vai trò cụ thể của mình. Hãy mô tả một ví dụ thử nghiệm theo các khuyến nghị này, nhưng không phải một mà là hai yêu cầu chế nhạo. Giai đoạn Hành động (Thực thi) sẽ được lược bỏ để ngắn gọn (bạn có thể tìm thấy ví dụ kiểm tra đầy đủ trong kho dự án ).

Mã được trình bày được chia thành các phần có điều kiện: "Mã hỗ trợ" (có màu xám) và "Thông số kỹ thuật tương tác bên ngoài" (có màu xanh lam). Mã hỗ trợ bao gồm các cơ chế và tiện ích để thử nghiệm, bao gồm cả việc chặn yêu cầu và mô phỏng phản hồi. Đặc tả tương tác bên ngoài mô tả dữ liệu cụ thể về các dịch vụ bên ngoài mà hệ thống sẽ tương tác trong quá trình thử nghiệm, bao gồm các yêu cầu và phản hồi dự kiến. Mã hỗ trợ đặt nền tảng cho việc thử nghiệm, trong khi Thông số kỹ thuật liên quan trực tiếp đến logic nghiệp vụ và các chức năng chính của hệ thống mà chúng tôi đang thử nghiệm.


Thông số kỹ thuật chiếm một phần nhỏ của mã nhưng thể hiện giá trị quan trọng để hiểu bài kiểm tra, trong khi Mã hỗ trợ, chiếm phần lớn hơn, thể hiện ít giá trị hơn và lặp đi lặp lại cho mỗi khai báo mô phỏng. Mã này được thiết kế để sử dụng với MockRestServiceServer. Tham khảo ví dụ trên WireMock , người ta có thể thấy mô hình tương tự: thông số kỹ thuật gần như giống hệt nhau và Mã hỗ trợ khác nhau.


Mục đích của bài viết này là đưa ra các khuyến nghị thực tế cho việc viết bài kiểm thử theo cách tập trung vào đặc tả và Mã hỗ trợ được xếp sau.

Kịch bản trình diễn

Đối với kịch bản thử nghiệm của chúng tôi, tôi đề xuất một bot Telegram giả định có khả năng chuyển tiếp các yêu cầu tới API OpenAI và gửi phản hồi lại cho người dùng.

Các hợp đồng tương tác với các dịch vụ được mô tả một cách đơn giản để làm nổi bật logic chính của hoạt động. Dưới đây là sơ đồ tuần tự thể hiện kiến trúc ứng dụng. Tôi hiểu rằng thiết kế có thể đặt ra câu hỏi từ góc độ kiến trúc hệ thống, nhưng vui lòng tiếp cận vấn đề này với sự hiểu biết—mục tiêu chính ở đây là thể hiện cách tiếp cận nhằm nâng cao khả năng hiển thị trong các thử nghiệm.

Đề xuất

Bài viết này thảo luận về các khuyến nghị thực tế sau đây cho bài kiểm tra viết:

  • Sử dụng trình bao bọc DSL để làm việc với mô hình.
  • Sử dụng JsonAssert để xác minh kết quả.
  • Lưu trữ thông số kỹ thuật của các tương tác bên ngoài trong tệp JSON.
  • Sử dụng các tập tin Pact.

Sử dụng Trình bao bọc DSL để mô phỏng

Việc sử dụng trình bao bọc DSL cho phép ẩn mã giả soạn sẵn và cung cấp giao diện đơn giản để làm việc với thông số kỹ thuật. Điều quan trọng cần nhấn mạnh là những gì được đề xuất không phải là một DSL cụ thể mà là một cách tiếp cận chung mà nó thực hiện. Một ví dụ kiểm tra đã sửa bằng cách sử dụng DSL được trình bày bên dưới ( văn bản kiểm tra đầy đủ ).

 setup: def openaiRequestCaptor = restExpectation.openai.completions(withSuccess("{...}")) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 1

Ví dụ: trong đó phương thức restExpectation.openai.completions được mô tả như sau:

 public interface OpenaiMock { /** * This method configures the mock request to the following URL: {@code https://api.openai.com/v1/chat/completions} */ RequestCaptor completions(DefaultResponseCreator responseCreator); }

Việc có nhận xét về phương thức cho phép khi di chuột qua tên phương thức trong trình chỉnh sửa mã để nhận trợ giúp, bao gồm cả việc xem URL sẽ bị mô phỏng.

Trong quá trình triển khai được đề xuất, việc khai báo phản hồi từ mô hình được thực hiện bằng cách sử dụng các phiên bản ResponseCreator , cho phép các phiên bản tùy chỉnh, chẳng hạn như:

 public static ResponseCreator withResourceAccessException() { return (request) -> { throw new ResourceAccessException("Error"); }; }

Một thử nghiệm ví dụ cho các trường hợp không thành công chỉ định một tập hợp phản hồi được hiển thị bên dưới:

 import static org.springframework.http.HttpStatus.FORBIDDEN setup: def openaiRequestCaptor = restExpectation.openai.completions(openaiResponse) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 0 where: openaiResponse | _ withResourceAccessException() | _ withStatus(FORBIDDEN) | _

Đối với WireMock, mọi thứ đều giống nhau, ngoại trừ việc hình thành phản hồi hơi khác một chút ( mã kiểm tra , mã lớp phản hồi của nhà máy ).

Sử dụng chú thích @Language("JSON") để tích hợp IDE tốt hơn

Khi triển khai DSL, có thể chú thích các tham số phương thức bằng @Language("JSON") để bật hỗ trợ tính năng ngôn ngữ cho các đoạn mã cụ thể trong IntelliJ IDEA. Ví dụ: với JSON, trình soạn thảo sẽ coi tham số chuỗi là mã JSON, kích hoạt các tính năng như đánh dấu cú pháp, tự động hoàn thành, kiểm tra lỗi, điều hướng và tìm kiếm cấu trúc. Dưới đây là ví dụ về cách sử dụng chú thích:

 public static DefaultResponseCreator withSuccess(@Language("JSON") String body) { return MockRestResponseCreators.withSuccess(body, APPLICATION_JSON); }

Đây là giao diện của nó trong trình chỉnh sửa:

Sử dụng JsonAssert để xác minh kết quả

Thư viện JSONAssert được thiết kế để đơn giản hóa việc kiểm tra cấu trúc JSON. Nó cho phép các nhà phát triển dễ dàng so sánh các chuỗi JSON dự kiến và thực tế với mức độ linh hoạt cao, hỗ trợ nhiều chế độ so sánh khác nhau.

Điều này cho phép di chuyển từ mô tả xác minh như thế này

 openaiRequestCaptor.body.model == "gpt-3.5-turbo" openaiRequestCaptor.body.messages.size() == 1 openaiRequestCaptor.body.messages[0].role == "user" openaiRequestCaptor.body.messages[0].content == "Hello!"

đến một cái gì đó như thế này

 assertEquals("""{ "model": "gpt-3.5-turbo", "messages": [{ "role": "user", "content": "Hello!" }] }""", openaiRequestCaptor.bodyString, false)

Theo tôi, ưu điểm chính của cách tiếp cận thứ hai là nó đảm bảo tính nhất quán trong cách trình bày dữ liệu trên các bối cảnh khác nhau - trong tài liệu, nhật ký và kiểm tra. Điều này giúp đơn giản hóa đáng kể quá trình kiểm tra, mang lại sự linh hoạt trong việc so sánh và độ chính xác trong chẩn đoán lỗi. Vì vậy, chúng tôi không chỉ tiết kiệm thời gian viết và duy trì các bài kiểm tra mà còn cải thiện khả năng đọc và tính thông tin của chúng.

Khi làm việc trong Spring Boot, bắt đầu từ ít nhất là phiên bản 2, không cần phụ thuộc bổ sung để làm việc với thư viện, vì org.springframework.boot:spring-boot-starter-test đã bao gồm phụ thuộc vào org.skyscreamer:jsonassert .

Lưu trữ thông số kỹ thuật của các tương tác bên ngoài trong tệp JSON

Một quan sát mà chúng tôi có thể đưa ra là các chuỗi JSON chiếm một phần đáng kể trong quá trình kiểm tra. Có nên giấu chúng không? Có và không. Điều quan trọng là phải hiểu điều gì mang lại nhiều lợi ích hơn. Việc ẩn chúng làm cho bài kiểm tra trở nên nhỏ gọn hơn và đơn giản hóa việc nắm bắt bản chất của bài kiểm tra ngay từ cái nhìn đầu tiên. Mặt khác, để phân tích kỹ lưỡng, một phần thông tin quan trọng về đặc điểm kỹ thuật của tương tác bên ngoài sẽ bị ẩn, đòi hỏi phải thực hiện nhiều thao tác nhảy qua các tệp. Quyết định phụ thuộc vào sự thuận tiện: hãy làm những gì bạn cảm thấy thoải mái hơn.

Nếu bạn chọn lưu trữ chuỗi JSON trong tệp, một tùy chọn đơn giản là giữ riêng phản hồi và yêu cầu trong tệp JSON. Dưới đây là mã kiểm tra ( phiên bản đầy đủ ) thể hiện tùy chọn triển khai:

 setup: def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json"))) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 1

Phương thức fromFile chỉ đơn giản đọc một chuỗi từ một tệp trong thư mục src/test/resources và không mang bất kỳ ý tưởng mang tính cách mạng nào nhưng vẫn có sẵn trong kho dự án để tham khảo.

Đối với phần biến của chuỗi, bạn nên sử dụng thay thế bằng org.apache.commons.text.StringSubstitutor và chuyển một tập hợp các giá trị khi mô tả mô hình, ví dụ:

 setup: def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json", [content: "Hello! How can I assist you today?"])))

Trường hợp phần thay thế trong tệp JSON trông như thế này:

 ... "message": { "role": "assistant", "content": "${content:-Hello there, how may I assist you today?}" }, ...

Thách thức duy nhất đối với các nhà phát triển khi áp dụng phương pháp lưu trữ tệp là phát triển sơ đồ vị trí tệp thích hợp trong tài nguyên thử nghiệm và sơ đồ đặt tên. Bạn rất dễ mắc lỗi có thể khiến trải nghiệm làm việc với những tệp này trở nên tồi tệ hơn. Một giải pháp cho vấn đề này có thể là sử dụng các thông số kỹ thuật, chẳng hạn như các thông số kỹ thuật từ Pact, sẽ được thảo luận sau.

Khi sử dụng phương pháp được mô tả trong các bài kiểm tra được viết bằng Groovy, bạn có thể gặp phải sự bất tiện: không có hỗ trợ nào trong IntelliJ IDEA để điều hướng đến tệp từ mã, nhưng dự kiến hỗ trợ cho chức năng này sẽ được thêm vào trong tương lai . Trong các thử nghiệm viết bằng Java, điều này hoạt động rất tốt.

Sử dụng tệp hợp đồng hiệp ước

Hãy bắt đầu với thuật ngữ.


Kiểm tra hợp đồng là một phương pháp kiểm tra các điểm tích hợp trong đó mỗi ứng dụng được kiểm tra riêng biệt để xác nhận rằng các tin nhắn mà nó gửi hoặc nhận tuân thủ sự hiểu biết lẫn nhau được ghi trong "hợp đồng". Cách tiếp cận này đảm bảo rằng sự tương tác giữa các phần khác nhau của hệ thống đáp ứng được mong đợi.


Hợp đồng trong bối cảnh thử nghiệm hợp đồng là một tài liệu hoặc thông số kỹ thuật ghi lại thỏa thuận về định dạng và cấu trúc của các tin nhắn (yêu cầu và phản hồi) được trao đổi giữa các ứng dụng. Nó làm cơ sở để xác minh rằng mỗi ứng dụng có thể xử lý chính xác dữ liệu được gửi và nhận bởi những ứng dụng khác trong quá trình tích hợp.


Hợp đồng được thiết lập giữa người tiêu dùng (ví dụ: khách hàng muốn truy xuất một số dữ liệu) và nhà cung cấp (ví dụ: API trên máy chủ cung cấp dữ liệu mà khách hàng cần).


Thử nghiệm hướng đến người tiêu dùng là một cách tiếp cận thử nghiệm hợp đồng trong đó người tiêu dùng tạo hợp đồng trong quá trình chạy thử nghiệm tự động của họ. Các hợp đồng này được chuyển cho nhà cung cấp, sau đó họ sẽ chạy bộ thử nghiệm tự động của mình. Mỗi yêu cầu có trong tệp hợp đồng sẽ được gửi đến nhà cung cấp và phản hồi nhận được sẽ được so sánh với phản hồi dự kiến được chỉ định trong tệp hợp đồng. Nếu cả hai phản hồi đều khớp nhau, điều đó có nghĩa là người tiêu dùng và nhà cung cấp dịch vụ tương thích.


Cuối cùng là Hiệp ước. Pact là một công cụ thực hiện các ý tưởng thử nghiệm hợp đồng do người tiêu dùng định hướng. Nó hỗ trợ thử nghiệm cả tích hợp HTTP và tích hợp dựa trên thông báo, tập trung vào phát triển thử nghiệm mã đầu tiên.

Như tôi đã đề cập trước đó, chúng ta có thể sử dụng các công cụ và thông số kỹ thuật hợp đồng của Pact cho nhiệm vụ của mình. Việc triển khai có thể trông như thế này ( mã kiểm tra đầy đủ ):

 setup: def openaiRequestCaptor = restExpectation.openai.completions(fromContract("openai/SuccessfulCompletion-Hello.json")) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 1

Tệp hợp đồng có sẵn để xem xét .

Ưu điểm của việc sử dụng tệp hợp đồng là chúng không chỉ chứa nội dung yêu cầu và phản hồi mà còn chứa các thành phần khác của đặc tả tương tác bên ngoài—đường dẫn yêu cầu, tiêu đề và trạng thái phản hồi HTTP, cho phép mô phỏng được mô tả đầy đủ dựa trên hợp đồng đó.

Điều quan trọng cần lưu ý là, trong trường hợp này, chúng tôi giới hạn việc thử nghiệm theo hợp đồng và không mở rộng sang thử nghiệm theo nhu cầu của người tiêu dùng. Tuy nhiên, ai đó có thể muốn khám phá Pact sâu hơn.

Phần kết luận

Bài viết này xem xét các khuyến nghị thực tế để nâng cao khả năng hiển thị và hiệu quả của các thử nghiệm tích hợp trong bối cảnh phát triển với Spring Framework. Mục tiêu của tôi là tập trung vào tầm quan trọng của việc xác định rõ ràng các thông số kỹ thuật của các tương tác bên ngoài và giảm thiểu mã soạn sẵn. Để đạt được mục tiêu này, tôi đã đề xuất sử dụng trình bao bọc DSL và JsonAssert, lưu trữ thông số kỹ thuật trong tệp JSON và làm việc với các hợp đồng thông qua Pact. Các phương pháp được mô tả trong bài viết nhằm mục đích đơn giản hóa quá trình viết và duy trì các bài kiểm thử, cải thiện khả năng đọc của chúng và quan trọng nhất là nâng cao chất lượng của bản thân bài kiểm thử bằng cách phản ánh chính xác sự tương tác giữa các thành phần hệ thống.


Liên kết tới kho lưu trữ dự án minh họa các thử nghiệm - sandbox/bot .


Cảm ơn bạn đã quan tâm đến bài viết và chúc bạn may mắn trong việc viết các bài kiểm tra hiệu quả và rõ ràng!