You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试交换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 11:53:22