Java智能合约集成测试的"金钟罩"构建指南

一、为什么集成测试是Java智能合约的"金钟罩"?

在Java智能合约开发中,单元测试只是"快递站没贴标签",集成测试才是"快递箱装GPS",而安全测试则是"快递员堵在高速"。没有集成测试,你的智能合约就像没有GPS的快递,随时可能"迷路"。

集成测试的真正价值:

  • 模拟真实区块链环境
  • 验证合约间交互
  • 检测安全漏洞
  • 保证业务逻辑正确

二、环境准备:构建"金钟罩"的基础

让我们从搭建测试环境开始,这是构建"金钟罩"的第一步。

1. Maven依赖配置(pom.xml)
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>smart-contract-testing</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <fabric.sdk.version>2.5.1</fabric.sdk.version>
        <junit.jupiter.version>5.10.0</junit.jupiter.version>
        <ganache.version>1.0.0</ganache.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    
    <dependencies>
        <!-- JUnit 5:单元测试框架 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- Hyperledger Fabric SDK -->
        <dependency>
            <groupId>org.hyperledger.fabric-sdk-java</groupId>
            <artifactId>fabric-sdk-java</artifactId>
            <version>${fabric.sdk.version}</version>
        </dependency>
        
        <!-- Ganache:本地区块链模拟器 -->
        <dependency>
            <groupId>com.trufflesuite</groupId>
            <artifactId>ganache</artifactId>
            <version>${ganache.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- Lombok:简化代码编写 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- 用于测试的Fabric网络配置 -->
        <dependency>
            <groupId>org.hyperledger.fabric-sdk-java</groupId>
            <artifactId>fabric-sdk-java-test</artifactId>
            <version>${fabric.sdk.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

注释: 这个依赖配置是构建"金钟罩"的基础:

  • JUnit 5:集成测试的核心框架
  • Fabric SDK:与区块链交互的桥梁
  • Ganache:模拟区块链环境,测试不依赖真实网络
  • Lombok:简化代码,提高可读性

关键点: 确保Fabric SDK版本与区块链平台一致,就像快递站的智能派送系统需要匹配。

2. 测试环境初始化(Ganache启动)
import com.trufflesuite.ganache.Ganache;
import com.trufflesuite.ganache.GanacheConfig;
import com.trufflesuite.ganache.GanacheNode;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.NetworkConfig;
import org.hyperledger.fabric.sdk.SdkConfiguration;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric.sdk.security.CryptoSuiteFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 集成测试环境初始化
 * 这是构建"金钟罩"的基石,确保测试环境与真实环境一致
 */
@ExtendWith(TestcontainersExtension.class)
public class IntegrationTestBase {
    
    // 使用Testcontainers启动Ganache,模拟区块链网络
    @Container
    public static GenericContainer<?> ganacheContainer = new GenericContainer<>(DockerImageName.parse("trufflesuite/ganache-cli"))
        .withExposedPorts(8545)
        .withCommand("--gasLimit 10000000000000000");
    
    // 用于存储测试配置
    private static final Map<String, Object> TEST_CONFIG = new HashMap<>();
    
    // 用于存储测试客户端
    private static HFClient client;
    
    // 用于存储测试通道
    private static Channel channel;
    
    // 用于存储测试用户
    private static User testUser;
    
    @BeforeAll
    public static void setUp() throws Exception {
        // 1. 启动Ganache容器,模拟区块链网络
        // 注意:这里使用Testcontainers,而不是手动启动Ganache
        System.out.println("Starting Ganache container...");
        ganacheContainer.start();
        
        // 2. 配置Fabric SDK
        System.out.println("Configuring Fabric SDK...");
        CryptoSuite cryptoSuite = CryptoSuiteFactory.getCryptoSuite();
        SdkConfiguration sdkConfig = SdkConfiguration.create(
            new File("src/test/resources/fabric-config.yaml"),
            new File("src/test/resources/fabric-ca-config.yaml")
        );
        
        // 3. 创建HFClient
        client = HFClient.createNewInstance();
        client.setCryptoSuite(cryptoSuite);
        client.setConfig(sdkConfig);
        
        // 4. 连接到Ganache
        System.out.println("Connecting to Ganache at: http://localhost:" + ganacheContainer.getMappedPort(8545));
        NetworkConfig networkConfig = NetworkConfig.fromJsonFile(new File("src/test/resources/network-config.json"));
        client.setNetworkConfig(networkConfig);
        
        // 5. 创建测试用户
        testUser = client.createUser("testUser", "Org1", "admin", "adminpw");
        
        // 6. 加载测试通道
        System.out.println("Loading test channel...");
        channel = client.loadChannelFromConfig("mychannel", networkConfig, testUser);
        
        // 7. 初始化测试配置
        TEST_CONFIG.put("ganacheUrl", "http://localhost:" + ganacheContainer.getMappedPort(8545));
        TEST_CONFIG.put("channel", channel);
        TEST_CONFIG.put("client", client);
        TEST_CONFIG.put("user", testUser);
        
        System.out.println("Test environment initialized successfully");
    }
    
    /**
     * 获取测试配置
     * @return 测试配置
     */
    public static Map<String, Object> getTestConfig() {
        return TEST_CONFIG;
    }
    
    /**
     * 获取HFClient实例
     * @return HFClient实例
     */
    public static HFClient getClient() {
        return client;
    }
    
    /**
     * 获取测试通道
     * @return 测试通道
     */
    public static Channel getChannel() {
        return channel;
    }
    
    /**
     * 获取测试用户
     * @return 测试用户
     */
    public static User getUser() {
        return testUser;
    }
}

注释: 这个初始化代码是构建"金钟罩"的关键:

  • Testcontainers:自动管理Ganache容器,确保测试环境一致
  • Fabric SDK配置:正确设置与区块链交互的参数
  • 测试用户:模拟真实用户在区块链上的操作
  • 通道加载:确保测试在正确的区块链通道上进行

关键点: 测试环境必须与生产环境一致,就像快递站的智能派送系统需要匹配。

三、集成测试场景:从"纸糊城堡"到"金钟罩"

1. 基础场景:合约部署与简单调用
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.TransactionRequest;
import org.hyperledger.fabric.sdk.exception.ChaincodeEndorsementPolicyNotMatchedException;
import org.hyperledger.fabric.sdk.exception.ChaincodeInstantiationException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.hyperledger.fabric.sdk.query.ChaincodeQueryRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 智能合约基础功能测试
 * 测试合约的部署和基本功能调用
 */
@ExtendWith(IntegrationTestBase.class)
public class SmartContractBasicTest {
    
    private static final Logger logger = LoggerFactory.getLogger(SmartContractBasicTest.class);
    
    /**
     * 测试合约部署
     * 这是"金钟罩"的第一道防线,确保合约能正确部署到区块链
     */
    @Test
    public void testContractDeployment() throws Exception {
        // 1. 获取测试配置
        Map<String, Object> config = IntegrationTestBase.getTestConfig();
        HFClient client = (HFClient) config.get("client");
        Channel channel = (Channel) config.get("channel");
        User user = (User) config.get("user");
        
        // 2. 准备合约部署参数
        ChaincodeID chaincodeID = ChaincodeID.newBuilder()
            .name("mytoken")
            .version("1.0")
            .path("src/main/java/com/example/token")
            .build();
        
        // 3. 创建部署请求
        TransactionRequest transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(chaincodeID);
        transactionRequest.setFcn("init");
        transactionRequest.setArgs(Arrays.asList("1000"));
        
        // 4. 执行部署
        logger.info("Deploying contract: mytoken");
        CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
        
        // 5. 等待部署完成
        String deployTxId = deployFuture.get(30, TimeUnit.SECONDS);
        logger.info("Contract deployed with transaction ID: {}", deployTxId);
        
        // 6. 验证部署
        assertTrue(deployTxId != null && !deployTxId.isEmpty(), "Contract deployment failed");
    }
    
    /**
     * 测试合约基本功能:查询余额
     * 验证合约的查询功能是否正常工作
     */
    @Test
    public void testQueryBalance() throws Exception {
        // 1. 获取测试配置
        Map<String, Object> config = IntegrationTestBase.getTestConfig();
        HFClient client = (HFClient) config.get("client");
        Channel channel = (Channel) config.get("channel");
        User user = (User) config.get("user");
        
        // 2. 创建查询请求
        ChaincodeQueryRequest queryRequest = client.newQueryRequest();
        queryRequest.setChaincodeID(ChaincodeID.newBuilder()
            .name("mytoken")
            .version("1.0")
            .build());
        queryRequest.setFcn("queryBalance");
        queryRequest.setArgs(Arrays.asList("user1"));
        
        // 3. 执行查询
        logger.info("Querying balance for user1");
        CompletableFuture<byte[]> queryFuture = channel.query(queryRequest, user);
        byte[] result = queryFuture.get(30, TimeUnit.SECONDS);
        
        // 4. 验证查询结果
        String balance = new String(result);
        assertEquals("1000", balance, "Initial balance should be 1000");
        logger.info("Balance query successful: {}", balance);
    }
    
    /**
     * 测试合约基本功能:转账
     * 验证合约的转账功能是否正常工作
     */
    @Test
    public void testTransfer() throws Exception {
        // 1. 获取测试配置
        Map<String, Object> config = IntegrationTestBase.getTestConfig();
        HFClient client = (HFClient) config.get("client");
        Channel channel = (Channel) config.get("channel");
        User user = (User) config.get("user");
        
        // 2. 创建转账请求
        ChaincodeID chaincodeID = ChaincodeID.newBuilder()
            .name("mytoken")
            .version("1.0")
            .build();
        
        TransactionRequest transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(chaincodeID);
        transactionRequest.setFcn("transfer");
        transactionRequest.setArgs(Arrays.asList("user1", "user2", "100"));
        
        // 3. 执行转账
        logger.info("Transferring 100 tokens from user1 to user2");
        CompletableFuture<String> transferFuture = channel.sendTransaction(transactionRequest, user);
        String transferTxId = transferFuture.get(30, TimeUnit.SECONDS);
        
        // 4. 验证转账
        assertTrue(transferTxId != null && !transferTxId.isEmpty(), "Transfer transaction failed");
        
        // 5. 查询转账后余额
        ChaincodeQueryRequest queryRequest = client.newQueryRequest();
        queryRequest.setChaincodeID(chaincodeID);
        queryRequest.setFcn("queryBalance");
        queryRequest.setArgs(Arrays.asList("user1"));
        
        CompletableFuture<byte[]> queryFuture = channel.query(queryRequest, user);
        byte[] result = queryFuture.get(30, TimeUnit.SECONDS);
        String balance = new String(result);
        
        // 6. 验证余额变化
        assertEquals("900", balance, "Balance after transfer should be 900");
        logger.info("Balance after transfer: {}", balance);
    }
}

注释: 这个测试案例展示了"金钟罩"的最基本防线:

  • 合约部署:确保合约能正确部署到区块链
  • 查询功能:验证合约的查询功能是否正常工作
  • 转账功能:验证合约的业务逻辑是否正常工作
  • 结果验证:确保测试结果符合预期

关键点: 测试必须覆盖所有关键业务场景,就像快递站需要覆盖所有派送路线。

2. 高级场景:安全漏洞检测
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.TransactionRequest;
import org.hyperledger.fabric.sdk.exception.ChaincodeEndorsementPolicyNotMatchedException;
import org.hyperledger.fabric.sdk.exception.ChaincodeInstantiationException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 智能合约安全漏洞测试
 * 测试合约是否存在常见的安全漏洞
 */
@ExtendWith(IntegrationTestBase.class)
public class SmartContractSecurityTest {
    
    private static final Logger logger = LoggerFactory.getLogger(SmartContractSecurityTest.class);
    
    /**
     * 测试重入攻击
     * 重入攻击是智能合约的常见安全漏洞
     * 验证合约是否能防止重入攻击
     */
    @Test
    public void testReentrancyAttack() throws Exception {
        // 1. 获取测试配置
        Map<String, Object> config = IntegrationTestBase.getTestConfig();
        HFClient client = (HFClient) config.get("client");
        Channel channel = (Channel) config.get("channel");
        User user = (User) config.get("user");
        
        // 2. 部署测试合约(包含重入漏洞)
        ChaincodeID vulnerableChaincodeID = ChaincodeID.newBuilder()
            .name("vulnerableToken")
            .version("1.0")
            .path("src/main/java/com/example/vulnerableToken")
            .build();
        
        TransactionRequest transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(vulnerableChaincodeID);
        transactionRequest.setFcn("init");
        transactionRequest.setArgs(Arrays.asList("1000"));
        
        CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
        String deployTxId = deployFuture.get(30, TimeUnit.SECONDS);
        
        // 3. 创建攻击者用户
        User attacker = client.createUser("attacker", "Org1", "attacker", "attackerpw");
        
        // 4. 执行重入攻击
        logger.info("Attempting reentrancy attack on vulnerable contract");
        CompletableFuture<String> attackFuture = executeReentrancyAttack(channel, attacker, vulnerableChaincodeID);
        String attackTxId = attackFuture.get(30, TimeUnit.SECONDS);
        
        // 5. 验证攻击是否成功
        // 重入攻击应该失败,因为合约已修复
        try {
            attackFuture.get(30, TimeUnit.SECONDS);
            fail("Reentrancy attack should have failed");
        } catch (ExecutionException e) {
            // 预期的异常
            logger.info("Reentrancy attack failed as expected");
        }
    }
    
    /**
     * 执行重入攻击
     * @param channel 区块链通道
     * @param attacker 攻击者用户
     * @param chaincodeID 合约ID
     * @return 交易ID
     */
    private CompletableFuture<String> executeReentrancyAttack(Channel channel, User attacker, ChaincodeID chaincodeID) throws Exception {
        TransactionRequest transactionRequest = new TransactionRequest();
        transactionRequest.setChaincodeID(chaincodeID);
        transactionRequest.setFcn("transfer");
        transactionRequest.setArgs(Arrays.asList("attacker", "attacker", "100"));
        
        return channel.sendTransaction(transactionRequest, attacker);
    }
    
    /**
     * 测试整数溢出
     * 整数溢出是智能合约的常见安全漏洞
     * 验证合约是否能防止整数溢出
     */
    @Test
    public void testIntegerOverflow() throws Exception {
        // 1. 获取测试配置
        Map<String, Object> config = IntegrationTestBase.getTestConfig();
        HFClient client = (HFClient) config.get("client");
        Channel channel = (Channel) config.get("channel");
        User user = (User) config.get("user");
        
        // 2. 部署测试合约(包含整数溢出漏洞)
        ChaincodeID vulnerableChaincodeID = ChaincodeID.newBuilder()
            .name("vulnerableToken")
            .version("1.0")
            .path("src/main/java/com/example/vulnerableToken")
            .build();
        
        TransactionRequest transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(vulnerableChaincodeID);
        transactionRequest.setFcn("init");
        transactionRequest.setArgs(Arrays.asList("1000"));
        
        CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
        String deployTxId = deployFuture.get(30, TimeUnit.SECONDS);
        
        // 3. 尝试触发整数溢出
        logger.info("Attempting integer overflow attack");
        transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(vulnerableChaincodeID);
        transactionRequest.setFcn("transfer");
        transactionRequest.setArgs(Arrays.asList("user1", "user2", "10000000000000000000"));
        
        // 4. 验证是否抛出异常
        try {
            channel.sendTransaction(transactionRequest, user).get(30, TimeUnit.SECONDS);
            fail("Integer overflow attack should have failed");
        } catch (ExecutionException e) {
            // 预期的异常
            logger.info("Integer overflow attack failed as expected");
        }
    }
    
    /**
     * 测试权限控制
     * 验证合约是否正确实施权限控制
     */
    @Test
    public void testPermissionControl() throws Exception {
        // 1. 获取测试配置
        Map<String, Object> config = IntegrationTestBase.getTestConfig();
        HFClient client = (HFClient) config.get("client");
        Channel channel = (Channel) config.get("channel");
        User user = (User) config.get("user");
        
        // 2. 部署测试合约(包含权限控制)
        ChaincodeID chaincodeID = ChaincodeID.newBuilder()
            .name("permissionToken")
            .version("1.0")
            .path("src/main/java/com/example/permissionToken")
            .build();
        
        TransactionRequest transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(chaincodeID);
        transactionRequest.setFcn("init");
        transactionRequest.setArgs(Arrays.asList("1000"));
        
        CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
        String deployTxId = deployFuture.get(30, TimeUnit.SECONDS);
        
        // 3. 创建普通用户
        User normalUser = client.createUser("normalUser", "Org1", "normaluser", "normaluserpw");
        
        // 4. 尝试执行需要管理员权限的操作
        logger.info("Attempting to execute admin operation with normal user");
        transactionRequest = client.createTransactionRequest();
        transactionRequest.setChaincodeID(chaincodeID);
        transactionRequest.setFcn("adminFunction");
        transactionRequest.setArgs(Arrays.asList("param"));
        
        // 5. 验证是否抛出异常
        try {
            channel.sendTransaction(transactionRequest, normalUser).get(30, TimeUnit.SECONDS);
            fail("Admin operation should have failed for normal user");
        } catch (ExecutionException e) {
            // 预期的异常
            logger.info("Admin operation failed as expected for normal user");
        }
    }
}

注释: 这个测试案例展示了"金钟罩"的高级防线:

  • 重入攻击测试:验证合约是否能防止重入攻击
  • 整数溢出测试:验证合约是否能防止整数溢出
  • 权限控制测试:验证合约是否正确实施权限控制
  • 异常处理:确保测试能正确捕获和处理异常

关键点: 安全测试必须覆盖所有已知的安全漏洞,就像快递站需要覆盖所有安全检查点。

3. 场景分析:从"纸糊城堡"到"金钟罩"的转变

场景1:合约部署失败

@Test
public void testContractDeploymentFailure() throws Exception {
    // 1. 获取测试配置
    Map<String, Object> config = IntegrationTestBase.getTestConfig();
    HFClient client = (HFClient) config.get("client");
    Channel channel = (Channel) config.get("channel");
    User user = (User) config.get("user");
    
    // 2. 准备一个错误的合约路径
    ChaincodeID chaincodeID = ChaincodeID.newBuilder()
        .name("mytoken")
        .version("1.0")
        .path("src/main/java/com/example/invalidToken") // 错误的路径
        .build();
    
    // 3. 创建部署请求
    TransactionRequest transactionRequest = client.createTransactionRequest();
    transactionRequest.setChaincodeID(chaincodeID);
    transactionRequest.setFcn("init");
    transactionRequest.setArgs(Arrays.asList("1000"));
    
    // 4. 执行部署
    logger.info("Deploying contract with invalid path");
    CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
    
    // 5. 验证部署失败
    try {
        deployFuture.get(30, TimeUnit.SECONDS);
        fail("Contract deployment should have failed with invalid path");
    } catch (ExecutionException e) {
        // 预期的异常
        logger.info("Contract deployment failed as expected with invalid path");
        assertTrue(e.getCause() instanceof ChaincodeInstantiationException,
            "Expected ChaincodeInstantiationException, but got: " + e.getCause().getClass().getName());
    }
}

分析: 这个测试案例验证了合约部署失败的场景。在实际生产环境中,合约部署失败是常见问题,但如果没有测试,可能导致严重后果。通过这个测试,我们确保了合约部署过程的健壮性。

场景2:并发交易处理

@Test
public void testConcurrentTransactions() throws Exception {
    // 1. 获取测试配置
    Map<String, Object> config = IntegrationTestBase.getTestConfig();
    HFClient client = (HFClient) config.get("client");
    Channel channel = (Channel) config.get("channel");
    User user = (User) config.get("user");
    
    // 2. 部署合约
    ChaincodeID chaincodeID = ChaincodeID.newBuilder()
        .name("concurrentToken")
        .version("1.0")
        .path("src/main/java/com/example/concurrentToken")
        .build();
    
    TransactionRequest transactionRequest = client.createTransactionRequest();
    transactionRequest.setChaincodeID(chaincodeID);
    transactionRequest.setFcn("init");
    transactionRequest.setArgs(Arrays.asList("1000"));
    
    CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
    String deployTxId = deployFuture.get(30, TimeUnit.SECONDS);
    
    // 3. 创建多个并发交易
    logger.info("Executing concurrent transactions");
    int numThreads = 10;
    int amount = 100;
    
    // 4. 创建线程池
    ExecutorService executor = Executors.newFixedThreadPool(numThreads);
    List<CompletableFuture<String>> futures = new ArrayList<>();
    
    // 5. 提交并发交易
    for (int i = 0; i < numThreads; i++) {
        futures.add(executor.submit(() -> {
            try {
                TransactionRequest txRequest = client.createTransactionRequest();
                txRequest.setChaincodeID(chaincodeID);
                txRequest.setFcn("transfer");
                txRequest.setArgs(Arrays.asList("user1", "user2", String.valueOf(amount)));
                return channel.sendTransaction(txRequest, user).get(30, TimeUnit.SECONDS);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }));
    }
    
    // 6. 等待所有交易完成
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(60, TimeUnit.SECONDS);
    
    // 7. 验证最终余额
    ChaincodeQueryRequest queryRequest = client.newQueryRequest();
    queryRequest.setChaincodeID(chaincodeID);
    queryRequest.setFcn("queryBalance");
    queryRequest.setArgs(Arrays.asList("user1"));
    
    CompletableFuture<byte[]> queryFuture = channel.query(queryRequest, user);
    byte[] result = queryFuture.get(30, TimeUnit.SECONDS);
    String balance = new String(result);
    
    // 8. 验证余额
    assertEquals("0", balance, "Balance after concurrent transactions should be 0");
    logger.info("Balance after concurrent transactions: {}", balance);
}

分析: 这个测试案例验证了并发交易处理场景。在实际生产环境中,并发交易是常见情况,但如果没有测试,可能导致余额不一致等严重问题。通过这个测试,我们确保了合约在高并发情况下的正确性。

四、最佳实践:构建"金钟罩"的终极指南

1. 测试覆盖率
/**
 * 确保测试覆盖率
 * 这是"金钟罩"的终极指标,确保所有代码路径都被覆盖
 */
@Test
public void testCoverage() throws Exception {
    // 1. 获取测试配置
    Map<String, Object> config = IntegrationTestBase.getTestConfig();
    HFClient client = (HFClient) config.get("client");
    Channel channel = (Channel) config.get("channel");
    User user = (User) config.get("user");
    
    // 2. 部署测试合约
    ChaincodeID chaincodeID = ChaincodeID.newBuilder()
        .name("coverageToken")
        .version("1.0")
        .path("src/main/java/com/example/coverageToken")
        .build();
    
    TransactionRequest transactionRequest = client.createTransactionRequest();
    transactionRequest.setChaincodeID(chaincodeID);
    transactionRequest.setFcn("init");
    transactionRequest.setArgs(Arrays.asList("1000"));
    
    CompletableFuture<String> deployFuture = channel.deployTransaction(transactionRequest, user);
    String deployTxId = deployFuture.get(30, TimeUnit.SECONDS);
    
    // 3. 执行所有测试用例
    // 这里省略了具体的测试用例,实际中应该覆盖所有路径
    logger.info("Running all test cases to ensure coverage");
    
    // 4. 验证测试覆盖率
    // 使用JaCoCo或其他工具生成覆盖率报告
    // 这里只是模拟验证
    assertTrue(true, "All test cases executed successfully");
    
    // 5. 生成覆盖率报告
    logger.info("Generating coverage report");
    // 实际中使用JaCoCo生成覆盖率报告
    // 例如:mvn jacoco:report
}

最佳实践: 确保测试覆盖率至少达到80%,这是"金钟罩"的最低标准。

2. 测试数据管理
/**
 * 测试数据管理
 * 确保测试数据的隔离和可重用性
 */
public class TestDataManager {
    
    private static final Map<String, String> testData = new HashMap<>();
    
    /**
     * 获取测试数据
     * @param key 数据键
     * @return 数据值
     */
    public static String getTestData(String key) {
        if (!testData.containsKey(key)) {
            // 从数据库或文件加载测试数据
            // 这里模拟加载
            testData.put(key, "test_data_" + key);
        }
        return testData.get(key);
    }
    
    /**
     * 清理测试数据
     * 确保测试数据不会影响后续测试
     */
    public static void cleanupTestData() {
        testData.clear();
    }
}

最佳实践: 测试数据应该与测试用例分离,确保测试的独立性和可重复性。

3. 测试环境管理
/**
 * 测试环境管理
 * 确保测试环境的隔离和一致性
 */
public class TestEnvironmentManager {
    
    private static boolean isEnvironmentInitialized = false;
    
    /**
     * 初始化测试环境
     * @throws Exception 初始化失败
     */
    public static synchronized void initializeEnvironment() throws Exception {
        if (isEnvironmentInitialized) {
            return;
        }
        
        // 1. 启动Ganache
        // 2. 配置Fabric SDK
        // 3. 创建测试用户
        // 4. 加载测试通道
        
        isEnvironmentInitialized = true;
    }
    
    /**
     * 清理测试环境
     * 确保测试环境不会影响后续测试
     */
    public static synchronized void cleanupEnvironment() {
        isEnvironmentInitialized = false;
        // 1. 停止Ganache
        // 2. 清理测试数据
        // 3. 重置测试状态
    }
}

最佳实践: 测试环境应该在每个测试用例前后自动初始化和清理,确保测试的独立性和可重复性。

五、 从"纸糊城堡"到"金钟罩"的蜕变

在Java智能合约开发中,集成测试不是可选项,而是生存必需品。没有集成测试,你的智能合约就像没有GPS的快递,随时可能"迷路"。

"金钟罩"构建指南:

  1. 环境准备:确保测试环境与生产环境一致
  2. 基础测试:验证合约的基本功能
  3. 安全测试:检测常见安全漏洞
  4. 场景分析:覆盖所有关键业务场景
  5. 最佳实践:确保测试的覆盖率、数据管理和环境管理
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐