例如,这样的地图访问:
func (pool *fPool) fetch(url string) *ResultPromise { pool.cacheLock.RLock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.RUnlock() return rp } pool.cacheLock.RUnlock() pool.cacheLock.Lock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.Unlock() // Skip adding url if someone snuck it in between RUnlock an Lock return rp } rp := newPromise() pool.cache[url] = rp pool.cacheLock.Unlock() pool.c <- fetchWork{rp, url} return rp }
在此,第二if条件的内容不包括在内。但是,通过放置断点,最终无法进入该块。
if
该示例不是人为设计的,因为:
RLock
pool.c <- fetchWork{rp, url}
pool.cacheLock.Lock()
覆盖该分支的一种方法是模拟pool.cacheLock.Lock(),模拟版本可以将插入url到地图中。因此,在此调用之后再次检查,将找到并执行将进入第二条if语句的主体。
url
模拟的一种方法pool.cacheLock.Lock()是创建pool.cacheLock一个接口,在测试中,您可以设置一个模拟值,该Lock()方法的方法将“脏插入”到映射中。
pool.cacheLock
Lock()
这是使用以下接口的代码的简化版本pool.cacheLock:
type rwmutex interface { Lock() RLock() RUnlock() Unlock() } type fPool struct { cache map[string]string cacheLock rwmutex } func (pool *fPool) fetch(url string) string { pool.cacheLock.RLock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.RUnlock() return rp } pool.cacheLock.RUnlock() pool.cacheLock.Lock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.Unlock() // Skip adding url if someone snuck it in between RUnlock an Lock return rp } rp := url + "~data" pool.cache[url] = rp pool.cacheLock.Unlock() return rp }
它的正常用法是:
pool := fPool{ cache: map[string]string{}, cacheLock: &sync.RWMutex{}, } fmt.Println(pool.fetch("http://google.com"))
还有一个测试用例将触发第二个主体if:
type testRwmutex struct { sync.RWMutex // Embed RWMutex so we don't have to implement everything customLock func() } func (trw *testRwmutex) Lock() { trw.RWMutex.Lock() if trw.customLock != nil { trw.customLock() } } func TestFPoolFetch(t *testing.T) { trw := &testRwmutex{RWMutex: sync.RWMutex{}} pool := &fPool{ cache: map[string]string{}, cacheLock: trw, } exp := "http://google.com~test" trw.customLock = func() { pool.cache["http://google.com"] = exp } if got := pool.fetch("http://google.com"); got != exp { t.Errorf("Expected: %s, got: %s", exp, got) } }
模拟的另一种方法pool.cacheLock.Lock()是将该功能“外包”给一个函数类型的字段,该测试可以替换为一个函数,该函数除了调用此函数外,还进行“脏插入”。
再次简化示例:
func NewFPool() *fPool { mu := &sync.RWMutex{} return &fPool{ cache: map[string]string{}, cacheLock: mu, lock: mu.Lock, } } type fPool struct { cache map[string]string cacheLock *sync.RWMutex lock func() } func (pool *fPool) fetch(url string) string { pool.cacheLock.RLock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.RUnlock() return rp } pool.cacheLock.RUnlock() pool.lock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.Unlock() // Skip adding url if someone snuck it in between RUnlock an Lock return rp } rp := url + "~data" pool.cache[url] = rp pool.cacheLock.Unlock() return rp }
正常用法是:
pool := NewFPool() fmt.Println(pool.fetch("http://google.com"))
func TestFPoolFetch(t *testing.T) { pool := NewFPool() oldLock := pool.lock exp := "http://google.com~test" pool.lock = func() { oldLock() pool.cache["http://google.com"] = exp } if got := pool.fetch("http://google.com"); got != exp { t.Errorf("Expected: %s, got: %s", exp, got) } }
test
这里的想法是,为了支持简单测试,您可以test在实现中构建一个简单标志fPool(例如可以是的字段fPool),并且您要测试的代码会故意检查该标志:
fPool
type fPool struct { cache map[string]string cacheLock *sync.RWMutex test bool } func (pool *fPool) fetch(url string) string { pool.cacheLock.RLock() if rp, pres := pool.cache[url]; pres { pool.cacheLock.RUnlock() return rp } pool.cacheLock.RUnlock() pool.cacheLock.Lock() if rp, pres := pool.cache[url]; pres || pool.test { pool.cacheLock.Unlock() // Skip adding url if someone snuck it in between RUnlock an Lock return rp } rp := url + "~data" pool.cache[url] = rp pool.cacheLock.Unlock() return rp }
现在,如果您想测试2nd的主体if,您要做的就是:
func TestFPoolFetch(t *testing.T) { pool := NewFPool() pool.test = true exp := "" if got := pool.fetch("http://google.com"); got != exp { t.Errorf("Expected: %s, got: %s", exp, got) } }