如何测试交换Dictionary的类间契约?能否用Pact实现契约变更通知?
可以用Pact实现契约破坏检测需求
完全可以用Pact实现这个需求。Pact的核心能力就是通过消费者驱动的契约测试,确保组件之间的交互契约被严格遵守,一旦提供者破坏契约(比如修改输入输出的结构、字段名),就能在测试阶段及时触发失败通知。
具体实现步骤
1. 消费者端定义契约
消费者团队先编写测试,明确自己期望的交互规则:
- 调用
MoneyTransfer.toIBAN时,输入的Dictionary必须包含amount(数值类型)和iban(字符串类型)两个键 - 输出的
Dictionary必须包含commission(数值类型)键
示例消费者测试代码(基于Pact-JVM):
import au.com.dius.pact.consumer.dsl.PactDslJsonBody; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; import au.com.dius.pact.core.model.annotations.Pact; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.util.Dictionary; import java.util.Hashtable; import static org.junit.jupiter.api.Assertions.assertNotNull; @ExtendWith(PactConsumerTestExt.class) public class MoneyTransferConsumerTest { @Pact(consumer = "MoneyTransferConsumer") public RequestResponsePact createPact(PactDslJsonBody builder) { // 定义输入结构(对应Dictionary的键值对) PactDslJsonBody inputBody = builder .numberType("amount", 100.0) .stringType("iban", "DE89370400440532013000"); // 定义输出结构 PactDslJsonBody outputBody = builder .numberType("commission", 5.0); return builder .given("transfer request is valid") .uponReceiving("a request to transfer money to IBAN") .path("/transfer/toIBAN") .body(inputBody) .method("POST") .willRespondWith() .status(200) .body(outputBody) .toPact(); } @Test @PactTestFor(pactMethod = "createPact") public void testToIBAN() { // 模拟调用提供者的MoneyTransfer类 MoneyTransfer provider = new MoneyTransfer(); Dictionary<String, Object> input = new Hashtable<>(); input.put("amount", 100.0); input.put("iban", "DE89370400440532013000"); Dictionary<String, Object> output = provider.toIBAN(input); assertNotNull(output.get("commission")); } }
2. 提供者端验证契约
提供者团队加载消费者生成的契约文件,运行验证测试,确保实现完全符合契约要求:
import au.com.dius.pact.provider.junit5.HttpTestTarget; import au.com.dius.pact.provider.junit5.PactVerificationContext; import au.com.dius.pact.provider.junit5.PactVerificationExtension; import au.com.dius.pact.provider.junitsupport.Provider; import au.com.dius.pact.provider.junitsupport.loader.PactFolder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import java.util.Dictionary; import java.util.Hashtable; @Provider("MoneyTransferProvider") @PactFolder("pacts") // 存放消费者生成的契约文件的目录 @ExtendWith(PactVerificationExtension.class) public class MoneyTransferProviderTest { private MoneyTransfer moneyTransfer; @BeforeEach void setup() { moneyTransfer = new MoneyTransfer(); } @BeforeEach void setTarget(PactVerificationContext context) { // 类方法交互用本地测试目标 context.setTarget(new HttpTestTarget("localhost", 8080, "/")); } // 验证输入输出是否符合契约要求 @TestTemplate @ExtendWith(PactVerificationExtension.class) void verifyPact(PactVerificationContext context) { context.verifyInteraction(interaction -> { // 解析契约中的输入参数 Dictionary<String, Object> input = new Hashtable<>(); input.put("amount", interaction.getRequest().getBody().getValueAsMap().get("amount")); input.put("iban", interaction.getRequest().getBody().getValueAsMap().get("iban")); // 调用实际的提供者方法 Dictionary<String, Object> output = moneyTransfer.toIBAN(input); // 将结果返回给Pact验证 interaction.getResponse().setBody(output); return true; }); } }
3. 契约破坏时的检测
当开发者将代码从String iban = input.get("iban").toString();改为String iban = input.get("account no").toString();时:
- 消费者契约明确要求输入包含
iban键,提供者验证时Pact会按契约传入带iban的输入,此时提供者代码尝试获取account no键会触发NullPointerException - 即使未抛出异常,提供者实现也不再匹配消费者预期(消费者仍传
iban,但提供者期望account no),契约验证会直接失败,及时通知团队契约已被破坏
注意事项
- 虽然Pact常用于HTTP服务交互,但通过自定义逻辑,完全适配内部类之间的
Dictionary交互场景 - 契约文件需在消费者和提供者团队间共享(比如通过版本控制仓库),确保双方使用同一套规则
内容的提问来源于stack exchange,提问作者Cagin Uludamar
相关产品推荐
相关产品推荐

