基于Cucumber的Spring Boot GemFire客户端/服务端拓扑集成测试咨询
Spring Boot + Apache Geode Cucumber集成测试实现方案
核心问题破局:规避同一JVM的Peer/Client Cache冲突
同一JVM内无法同时运行带Locator的Peer Cache(服务端)和Client Cache(客户端),因此必须将服务端集群与客户端测试逻辑隔离在不同进程,以下两种方案均可落地:
方案1:基于Spring Data Geode手动实现进程隔离
替代失效的ForkingClientServerIntegrationTestsSupport类,通过自定义进程启动实现服务端与客户端分离:
1. 编写嵌入式服务端配置
@Configuration @ClusterConfiguration(locatorCount = 1, serverCount = 1) @ImportResource("classpath:cluster.xml") // 加载你的Region定义文件 public class EmbeddedGeodeServerConfig { // 注解自动启动Locator和Server,无需额外代码 }
2. 服务端启动入口类
单独创建主类用于启动嵌入式集群,测试时fork该进程:
public class GeodeServerTestRunner { public static void main(String[] args) { SpringApplication.run(EmbeddedGeodeServerConfig.class, args); } }
3. 客户端测试配置
禁用自动配置的Peer Cache,强制创建ClientCache并连接嵌入式集群:
@SpringBootTest(properties = { "spring.data.gemfire.cache.type=CLIENT", "spring.data.gemfire.locator.host=localhost", "spring.data.gemfire.locator.port=10334" // Geode默认Locator端口 }) @CucumberContextConfiguration public class GeodeClientTestConfig { @Autowired private Region<String, Object> testRegion; // 注入测试用Region }
4. Cucumber钩子管理服务端进程
在测试前后启动/停止服务端进程:
public class GeodeTestHooks { private static Process serverProcess; @BeforeAll public static void startGeodeServer() throws IOException { String javaHome = System.getProperty("java.home"); String classpath = System.getProperty("java.class.path"); ProcessBuilder processBuilder = new ProcessBuilder( javaHome + "/bin/java", "-cp", classpath, "com.yourpackage.GeodeServerTestRunner" ); serverProcess = processBuilder.start(); // 等待集群启动完成,可根据实际情况调整时长 try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @AfterAll public static void stopGeodeServer() { if (serverProcess != null) { serverProcess.destroy(); } } }
方案2:用Testcontainers运行容器化Geode集群(更稳定)
无需手动管理进程,直接用容器化集群模拟生产环境:
1. 添加Testcontainers依赖
<dependency> <groupId>org.testcontainers</groupId> <artifactId>apache-geode</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency>
2. 客户端测试配置
@SpringBootTest(properties = { "spring.data.gemfire.cache.type=CLIENT" }) @CucumberContextConfiguration public class GeodeClientTestConfig { @Container private static final ApacheGeodeContainer geodeContainer = new ApacheGeodeContainer("apachegeode/geode:1.15.0") .withClusterXmlMounted(Paths.get("src/test/resources/cluster.xml")); // 挂载Region定义文件 @DynamicPropertySource static void registerGeodeProperties(DynamicPropertyRegistry registry) { registry.add("spring.data.gemfire.locator.host", geodeContainer::getLocatorHost); registry.add("spring.data.gemfire.locator.port", geodeContainer::getLocatorPort); } @Autowired private Region<String, Object> testRegion; }
缓存CRUD测试用例实现
Feature文件(src/test/resources/features/cache_operations.feature)
Feature: Geode Cache CRUD Operations Scenario: 创建并查询缓存条目 Given 缓存中存在键为"testKey"、值为"testValue"的条目 When 根据键"testKey"查询条目 Then 查询结果应为"testValue" Scenario: 删除缓存条目 Given 缓存中存在键为"deleteKey"、值为"deleteValue"的条目 When 根据键"deleteKey"删除条目 Then 缓存中不存在键为"deleteKey"的条目
Step定义类
public class CacheOperationsSteps { @Autowired private Region<String, Object> testRegion; @Given("缓存中存在键为{string}、值为{string}的条目") public void createCacheEntry(String key, String value) { testRegion.put(key, value); } @When("根据键{string}查询条目") public void retrieveCacheEntry(String key) { ScenarioContext.getCurrentContext().put("retrievedValue", testRegion.get(key)); } @Then("查询结果应为{string}") public void verifyRetrievedValue(String expectedValue) { String actualValue = (String) ScenarioContext.getCurrentContext().get("retrievedValue"); assertEquals(expectedValue, actualValue); } @When("根据键{string}删除条目") public void deleteCacheEntry(String key) { testRegion.remove(key); } @Then("缓存中不存在键为{string}的条目") public void verifyEntryDeleted(String key) { assertNull(testRegion.get(key)); } }
关键注意事项
- cluster.xml验证:确保文件放在
src/test/resources下,服务端启动后可通过日志检查Region是否成功创建。 - 端口冲突处理:手动fork进程时,可配置服务端使用随机端口,避免本地端口占用:
之后通过服务端日志或端口检测工具获取实际端口,传递给客户端配置。@Bean public LocatorFactoryBean locator() { LocatorFactoryBean locator = new LocatorFactoryBean(); locator.setPort(0); // 随机分配端口 return locator; } - 启动等待时长:手动启动服务端时,需根据机器性能调整等待时间,避免客户端连接失败。
内容的提问来源于stack exchange,提问作者pradhan
相关产品推荐
相关产品推荐

