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

如何在单元测试中自动启动Cloud Spanner Emulator并重建数据库?

解决方案:自动启动Spanner Emulator并在测试中重建数据库

一、自动启动Spanner Emulator

借助测试框架的生命周期钩子(比如JUnit的@BeforeAll),直接在代码中调用系统命令启动模拟器,同时处理进程启停和环境变量配置:

Java + JUnit 5 示例代码

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class SpannerEmulatorTestBase {
    private static Process emulatorProcess;

    @BeforeAll
    static void startSpannerEmulator() throws IOException, InterruptedException {
        // 启动模拟器,指定端口
        emulatorProcess = new ProcessBuilder("gcloud", "emulators", "spanner", "start", "--host-port=localhost:9010")
                .redirectErrorStream(true)
                .start();

        // 等待模拟器初始化完成,可根据实际情况调整等待时长
        TimeUnit.SECONDS.sleep(3);

        // 设置环境变量,让Spanner客户端自动指向模拟器
        System.setProperty("SPANNER_EMULATOR_HOST", "localhost:9010");
    }

    @AfterAll
    static void stopSpannerEmulator() {
        if (emulatorProcess != null) {
            emulatorProcess.destroy();
            try {
                emulatorProcess.waitFor(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

二、每次测试Setup阶段重建数据库

有两种常用方式实现测试前的数据库重置:

方式1:直接调用gcloud命令加载DDL文件

在@BeforeEach中先清理旧实例/数据库,再重新创建并加载DDL:

import org.junit.jupiter.api.BeforeEach;
import java.io.IOException;

public class YourSpannerTest extends SpannerEmulatorTestBase {
    @BeforeEach
    void setupDatabase() throws IOException, InterruptedException {
        // 清理已有资源
        new ProcessBuilder("gcloud", "spanner", "databases", "delete", "test-db", "--instance=test-instance", "--quiet")
                .inheritIO()
                .start()
                .waitFor();
        new ProcessBuilder("gcloud", "spanner", "instances", "delete", "test-instance", "--quiet")
                .inheritIO()
                .start()
                .waitFor();

        // 创建实例和数据库,加载指定DDL文件
        new ProcessBuilder("gcloud", "spanner", "instances", "create", "test-instance", "--config=emulator-config", "--description=Test Instance", "--nodes=1")
                .inheritIO()
                .start()
                .waitFor();
        new ProcessBuilder("gcloud", "spanner", "databases", "create", "test-db", "--instance=test-instance", "--ddl-file=src/test/resources/schema.ddl")
                .inheritIO()
                .start()
                .waitFor();
    }

    // 测试用例编写...
}

方式2:结合Liquibase重置数据库

如果用Liquibase管理schema,可直接调用其API执行清空和更新操作:

import liquibase.Liquibase;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
import org.junit.jupiter.api.BeforeEach;
import java.sql.Connection;
import java.sql.DriverManager;

public class LiquibaseSpannerTest extends SpannerEmulatorTestBase {
    private static final String DB_URL = "jdbc:cloudspanner://localhost:9010/projects/test-project/instances/test-instance/databases/test-db;useEmulator=true";

    @BeforeEach
    void resetDatabase() throws Exception {
        try (Connection connection = DriverManager.getConnection(DB_URL)) {
            JdbcConnection liquibaseConn = new JdbcConnection(connection);
            Liquibase liquibase = new Liquibase("db/changelog/db.changelog-master.yaml", new ClassLoaderResourceAccessor(), liquibaseConn);
            
            // 清空数据库并重新执行所有变更集
            liquibase.dropAll();
            liquibase.update("");
        }
    }

    // 测试用例编写...
}

三、关键注意事项

  • 确保已安装gcloud CLI并添加Spanner Emulator组件:执行gcloud components install cloud-spanner-emulator完成安装
  • 测试结束后必须销毁模拟器进程,避免端口占用或残留进程
  • 根据机器性能调整模拟器启动后的等待时间,确保初始化完成再执行数据库操作
  • 若使用构建工具(Maven/Gradle),也可通过插件(如Maven Exec Plugin)在测试阶段自动启动模拟器,但代码层面的控制更灵活

内容的提问来源于stack exchange,提问作者Naren Mehra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 07:26:28