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

Simple Aged Cache的getExpired单元测试失败,求解决建议

Simple Aged Cache过期清理失效问题分析与解决

问题描述

实现了一个Simple Aged Cache,其余单元测试均通过,仅getExpired测试用例失败。测试场景为:向缓存存入两个条目,将时钟偏移3000毫秒后,预期缓存大小为1,但实际返回2。尝试在put方法前后调用cleanExpiredRecords方法,问题仍未解决。

缓存实现代码

package io.collective;

import java.time.Clock;
import java.util.HashMap;
import java.util.Map;

public class SimpleAgedCache {
    private final Clock clock;
    private final Map<Object, CacheEntry> cacheMap;

    public SimpleAgedCache(Clock clock) {
        this.clock = clock;
        this.cacheMap = new HashMap<>();
    }

    public SimpleAgedCache() {
        this(Clock.system(Clock.systemDefaultZone().getZone()));
    }

    public void put(Object key, Object value, int retentionInMillis) {
        if (key != null && retentionInMillis > 0){
            long expirationTime = clock.millis() + retentionInMillis;
            cacheMap.put(key, new CacheEntry(value, expirationTime));
        }
    }

    public boolean isEmpty() {
        return cacheMap.isEmpty();
    }

    public int size() {
        return cacheMap.size();
    }

    public Object get(Object key) {
        cleanExpiredRecords();
        CacheEntry entry = cacheMap.get(key);
        if (entry != null){
            return entry.value;
        }
        return null;
    }

    private void cleanExpiredRecords(){
        long currentTime = clock.millis();
        cacheMap.entrySet().removeIf(entry -> entry.getValue().isExpired(currentTime));
    }

    private static class CacheEntry{
        Object value;
        long expirationTime;

        CacheEntry(Object value, long expirationTime){
            this.value = value;
            this.expirationTime = expirationTime;
        }

        boolean isExpired(long currentTime){
            return currentTime >= expirationTime;
        }

        boolean isNotExpired(long currentTime){
            return !isExpired(currentTime);
        }
    }
}

失败的测试用例

@Test
public void getExpired() {
    TestClock clock = new TestClock();

    SimpleAgedCache expired = new SimpleAgedCache(clock);
    expired.put("aKey", "aValue", 2000);
    expired.put("anotherKey", "anotherValue", 4000);

    clock.offset(Duration.ofMillis(3000));

    assertEquals(1, expired.size());
    assertEquals("anotherValue", expired.get("anotherKey"));
}

问题原因

核心问题在于**size()方法未触发过期条目清理**:

  • 当前只有get()方法会调用cleanExpiredRecords()清理过期数据
  • 测试用例中先调用expired.size(),此时缓存中过期的aKey条目并未被清理,直接返回cacheMap的原始大小2,导致断言失败
  • 后续调用get()虽然会清理,但此时第一个断言已经失败

解决建议

  1. 修改size()方法,添加过期清理逻辑:
public int size() {
    cleanExpiredRecords();
    return cacheMap.size();
}
  1. 同步修改isEmpty()方法,避免出现类似的状态不一致问题:
public boolean isEmpty() {
    cleanExpiredRecords();
    return cacheMap.isEmpty();
}
  1. 可选优化:如果需要更高效的过期清理,可以考虑后台定时清理策略,但当前场景下,在查询缓存状态前主动清理已经能满足测试需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:53:14