如何测试不太可能的并发场景?
How to test unlikely concurrent scenarios?
例如地图访问是这样的:
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
条件的内容。但是,通过放置断点,在该块中结束是微不足道的。
示例并非人为设计,因为:
- 如果我们跳过
RLock
,当工作负载主要是读取时,映射将被不必要地锁定。
- 如果我们跳过第二个
if
,最昂贵的工作(在这种情况下由 pool.c <- fetchWork{rp, url}
处理)可能会针对同一个密钥发生多次,这是不可接受的。
我。嘲笑 pool.cacheLock.Lock()
覆盖该分支的一种方法是模拟 pool.cacheLock.Lock()
,模拟版本可以将 url
插入地图。所以在这个调用之后再次检查,会发现并执行会进入第2条if
语句的主体。
使用接口模拟
模拟 pool.cacheLock.Lock()
的一种方法是使 pool.cacheLock
成为一个接口,在测试中您可以设置一个模拟值,其 Lock()
方法将执行 "dirty insert"进入地图。
这是您的代码的简化版本,它使用 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()
的方法是 "outsource" 将此功能添加到函数类型的字段中,测试可以替换为一个函数,该函数除了调用此函数外,还执行 "dirty insert".
还是你的简化示例:
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"))
还有一个将触发第二个正文的测试用例if
:
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
标志
这里的想法是,为了支持简单的测试,你在 fPool
的实现中构建一个简单的 test
标志(例如,它可以是 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
}
现在如果你想测试第二个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)
}
}
例如地图访问是这样的:
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
条件的内容。但是,通过放置断点,在该块中结束是微不足道的。
示例并非人为设计,因为:
- 如果我们跳过
RLock
,当工作负载主要是读取时,映射将被不必要地锁定。 - 如果我们跳过第二个
if
,最昂贵的工作(在这种情况下由pool.c <- fetchWork{rp, url}
处理)可能会针对同一个密钥发生多次,这是不可接受的。
我。嘲笑 pool.cacheLock.Lock()
覆盖该分支的一种方法是模拟 pool.cacheLock.Lock()
,模拟版本可以将 url
插入地图。所以在这个调用之后再次检查,会发现并执行会进入第2条if
语句的主体。
使用接口模拟
模拟 pool.cacheLock.Lock()
的一种方法是使 pool.cacheLock
成为一个接口,在测试中您可以设置一个模拟值,其 Lock()
方法将执行 "dirty insert"进入地图。
这是您的代码的简化版本,它使用 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()
的方法是 "outsource" 将此功能添加到函数类型的字段中,测试可以替换为一个函数,该函数除了调用此函数外,还执行 "dirty insert".
还是你的简化示例:
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"))
还有一个将触发第二个正文的测试用例if
:
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
标志
这里的想法是,为了支持简单的测试,你在 fPool
的实现中构建一个简单的 test
标志(例如,它可以是 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
}
现在如果你想测试第二个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)
}
}