如何在单元测试中自动启动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(""); } } // 测试用例编写... }
三、关键注意事项
- 确保已安装
gcloudCLI并添加Spanner Emulator组件:执行gcloud components install cloud-spanner-emulator完成安装 - 测试结束后必须销毁模拟器进程,避免端口占用或残留进程
- 根据机器性能调整模拟器启动后的等待时间,确保初始化完成再执行数据库操作
- 若使用构建工具(Maven/Gradle),也可通过插件(如Maven Exec Plugin)在测试阶段自动启动模拟器,但代码层面的控制更灵活
内容的提问来源于stack exchange,提问作者Naren Mehra
相关产品推荐
相关产品推荐

