单元测试指南
配置测试依赖
kotlin
dependencies {
testImplementation("me.ahoo.cocache:cocache-test:5.0.0")
}cocache-test 会带入 JUnit 5、fluent-assert 和 cocache-core。每个规格都是抽象 JUnit 类:继承它并实现工厂方法即可。
mermaid
flowchart LR
Impl["Your implementation"] --> Pick{"What is it?"}
Pick -->|"L2 store"| CSC["ClientSideCacheSpec"]
Pick -->|"L1 store"| DC["DistributedCacheSpec"]
Pick -->|"Cache<K, V>"| C["CacheSpec"]
Pick -->|"L1 + channel end-to-end"| CO["DefaultCoherentCacheSpec<br>MultipleInstanceSyncSpec"]
Pick -->|"event channel"| EB["CacheEvictedEventBusSpec"]
style Impl fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style Pick fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style CSC fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style DC fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style CO fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style EB fill:#2d333b,stroke:#6d5dfc,color:#e6edf3测试存储(L2 / L1)
kotlin
internal class CaffeineClientSideCacheTest : ClientSideCacheSpec<String>() {
override fun createCacheStore(): ClientSideCache<String> = CaffeineClientSideCache.build()
override fun createCacheEntry(): Pair<String, String> =
UUID.randomUUID().toString() to UUID.randomUUID().toString()
}对于从服务端过期时间重建 ttlAt 的存储(Redis),覆写 open 的 setWithTtlAt / setMissingWithTtlAt,允许 ±1 秒漂移:
kotlin
@Test
override fun setWithTtlAt() {
val (key, value) = createCacheEntry()
val cacheValue = CacheValue.of(value, TtlAt.at(10))
cacheStore.setCache(key, cacheValue)
requireNotNull(cacheStore.getCache(key)).ttlAt.assert().isCloseTo(cacheValue.ttlAt, Offset.offset(1))
}测试缓存
kotlin
class ProxyCacheTest : CacheSpec<String, String>() {
override fun createCache(): Cache<String, String> = createProxyCache<MockCache>()
override fun createCacheEntry() = UUID.randomUUID().toString() to UUID.randomUUID().toString()
}测试一致性
DefaultCoherentCacheSpec 用你提供的组件构建 DefaultCoherentCache。它的默认 CacheSource 由受保护的 loader 驱动,并用 sourceCalls 统计调用次数。
kotlin
internal class RedisDefaultCoherentCacheTest : DefaultCoherentCacheSpec<String, String>() {
private lateinit var redis: RedisTestSupport
@BeforeEach
override fun setup() { redis = RedisTestSupport(); super.setup() }
@AfterEach
override fun tearDown() { super.tearDown(); redis.close() }
override fun createKeyConverter(): KeyConverter<String> = ToStringKeyConverter("coherent-test:")
override fun createClientSideCache(): ClientSideCache<String> = MapClientSideCache()
override fun createDistributedCache(): DistributedCache<String> =
RedisDistributedCache(redis.redisTemplate, StringToStringCodecExecutor(redis.redisTemplate))
override fun createCacheEvictedEventBus(): CacheEvictedEventBus = redis.newEventBus()
override fun createCacheName(): String = "RedisDefaultCoherentCacheTest-" + UUID.randomUUID()
override fun createCacheEntry() = UUID.randomUUID().toString() to UUID.randomUUID().toString()
}覆写 JUnit 生命周期方法时必须重复标注
@BeforeEach/@AfterEach,否则 JUnit 不会执行该覆写方法。
异步通道会异步投递(重新)订阅时的 onReset,迟到的重置会在测试中途清空 L2。使用 Spring Data Redis 时,在测试中给 RedisMessageListenerContainer 配置 SyncTaskExecutor,这样 register() 返回时重置已经执行完。
mermaid
sequenceDiagram
autonumber
participant Spec as DefaultCoherentCacheSpec
participant F as DefaultCoherentCacheFactory
participant Bus as CacheEvictedEventBus
participant C as DefaultCoherentCache
Spec->>F: create(configuration)
F->>Bus: register(cache)
Bus-->>C: onReset() (sync executor: before register returns)
Spec->>C: run test
Spec->>C: close() in @AfterEach测试事件通道
kotlin
class LocalCacheEvictedEventBusTest : CacheEvictedEventBusSpec() {
override fun createCacheEvictedEventBus(): CacheEvictedEventBus = LocalCacheEvictedEventBus()
}Fluent Assert 与 mockk
kotlin
import me.ahoo.test.asserts.assert
cache[key].assert().isEqualTo(value)
requireNotNull(cache.getCache(key)).isMissing.assert().isTrue()
runCatching { cache[key] }.exceptionOrNull().assert().isSameAs(failure)- 禁止使用 AssertJ
assertThat();Offset.offset(n)作为参数可以使用。 assert()接受可空接收者。优先用requireNotNull(...),而不是!!。- 每个模块的测试 classpath 都有 mockk,例如
mockk<DistributedCache<String>>(relaxUnitFun = true)。
测试命令
bash
./gradlew :cocache-core:test
./gradlew :cocache-core:test --tests "me.ahoo.cache.consistency.DefaultCoherentCacheTest"
./gradlew :cocache-spring-redis:test # 需要 localhost:6379 的 Redis