Mục lục
Trong bài biết này chúng ta sẽ cùng nhau tìm hiểu mô hình extensions được giới thiệu trong Junit 5. Cơ chế này cho phép chúng ta mở rộng hành vi của test-classes và test-method.
Trong các phiên bản trước như Junit 4, cung cấp 2 cơ chế cho phép mở rộng là: test runner và rules. Để đơn giản hoá Junit 5 đã giới thiệu cơ chế mới thay thế được gọi là Extension API. Vậy Extension API có gì mới và cách sử dụng như thế nào, chúng ta sẽ tìm hiểu ở các phần tiếp theo.
Junit 5 Extension Model
Junit 5 extensions là các hành vi mở rộng liên quan đến một sư kiện nhất định trong quá trình thực thi một unit-test.
Quá trình thực thi một unit-test sẽ trải qua các giai đoạn khác nhau, với mỗi giai đoạn chúng ta có thể đăng ký một extension và chúng sẽ được Junit engine gọi khi đạt đến giai đoạn này.
Tổng thể, có 5 kiểu extension chính:
- test instance post-processing
- conditional test execution
- life-cycle callbacks
- parameters resolution
- exception handling
Chúng ta sẽ tìm hiểu lần lượt từng loại trên để xem khi nào cần và cách sử dụng chúng.
Maven dependency
Trước tiên chúng ta cần thêm dependency vào project maven. Thư việc chính chúng ta cần là junit-jupiter-engine.
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.4.2</version> <scope>test</scope> </dependency>
Tiếp theo, chúng ta sẽ cần đến 2 thư viện log4j và h2database hỗ trợ cho các ví dụ ở phần sau:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.196</version> </dependency>
Các phiên bản mới nhất junit-jupiter-engine, h2 và log4j-core có thể download tại Maven Central.
Tạo Junit 5 Extensions
Để khởi tạo một Junit 5 extension, chúng ta có thể định nghĩa một class implement từ một hoặc nhiều extension interface mà Junit 5 cung cấp. Tất cả các interface này đều thừa kế từ Extension marker interfce.
Test Instance Post Processor Extension
Extension loại này sẽ thực thi sau khi 1 instance của unit-test được khởi tạo. Để tạo ra test-instance-post-processor extension chúng ta cần implement TestInstancePostProcessor interface và override postProcessTestInstance() method.
Thông thường test-instance-post-processor được dùng để inject các dependency vào test instance. Ví dụ tạo một extension dùng để khởi tạo logger object.
package extensions.testinstancepostprocessor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; public class LoggingExtension implements TestInstancePostProcessor { @Override public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) throws Exception { Logger logger = LogManager.getLogger(testInstance.getClass()); testInstance.getClass() .getMethod("setLogger", Logger.class) .invoke(testInstance, logger); } }
Chúng ta có thể thấy, postProcessTestInstance() method khởi tạo một logger object và tiến hành gọi setLogger() method để gán logger object vừa khởi tạo cho test instance sử dụng cơ chế reflection.
Để sử dụng LoggingExtension, chúng ta có thể đăng ký nó với test-class hoặc test-method với @ExtendWith annotation.
package extensions.testinstancepostprocessor; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(LoggingExtension.class) public class LoggingExtensionTest { private Logger logger; @Test public void testLoggingExtension() { logger.error("testLoggingExtension"); } public void setLogger(Logger logger) { this.logger = logger; } }
Output: [main] ERROR extensions.testinstancepostprocessor.LoggingExtensionTest – testLoggingExtension
Note: Chúng ta phải định nghĩa setLogger() để LoggingExtension có thể sử dụng, nếu không chương trình sẽ ném ra NoSuchMethodException.
Conditional Test Execution
Junit 5 cung cấp một kiểu extension cho phép kiểm soát liệu 1 unit-test có thể được chạy hay không tuỳ thuộc vào điều kiện đã được triển khai trước đó với ExecutionCondition interface.
Ví dụ tạo EnvironmentExtension class triển khai ExecutionCondition và override evaluateExecutionCondition() method. EnvironmentExtension sẽ đọc cấu hình từ application.properties và kiểm tra, nếu env = qa thì unit-test sẽ bị bỏ qua.
package extensions.conditionaltest; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class EnvironmentExtension implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition( ExtensionContext context) { Properties props = new Properties(); try { props.load(new FileReader("application.properties")); } catch (IOException e) { e.printStackTrace(); } String env = props.getProperty("env"); if ("qa".equalsIgnoreCase(env)) { return ConditionEvaluationResult .disabled("Test disabled on QA environment"); } return ConditionEvaluationResult.enabled( "Test enabled on QA environment"); } }
Để lược bỏ một test-class hoặc hay một test-method, chúng ta cần cấu hình env = qa và đặt EnvironmentExtension tương ứng.
// application.properties env=qa
EnvironmentExtensionTest sẽ bị lược bỏ khi thực thi.
package extensions.conditionaltest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(EnvironmentExtension.class) public class EnvironmentExtensionTest { @Test public void testEnvironmentExtension() { assertTrue(true); } }
Lifecycle callbacks
Lifecylcle callbacks là một tập các extension liên quan đến vòng đời của unit-test có thể được định nghĩa bằng cách implement các interface sau:
- BeforeAllCallback và AfterAllCallback – thực thi trước và sau khi tất cả test-method thực thi xong.
- BeforeEachCallBack và AfterEachCallback – thực thi trước và sau mỗi test-method thực thi xong.
- BeforeTestExecutionCallback vàAfterTestExecutionCallback – thực thi trước và sau test-method bắt đầu thực thi.
Nếu test-class cũng định nghĩa các method trong vòng đời của nó kết hợp với Lifecycle Callbacks thì thứ tự thực thi là:
- BeforeAllCallback
- BeforeAll
- BeforeEachCallback
- BeforeEach
- BeforeTestExecutionCallback
- Test
- AfterTestExecutionCallback
- AfterEach
- AfterEachCallback
- AfterAll
- AfterAllCallback
Nguồn tham khảo