如何测试不太可能的并发场景?

Mihail Malostanidis:

例如,这样的地图访问:

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条件的内容不包括在内。但是,通过放置断点,最终无法进入该块。

该示例不是人为设计的,因为:

  1. 如果我们跳过RLock,则在主要读取工作负载时,地图将被不必要地锁定。
  2. 如果我们跳过第二个if,那么pool.c <- fetchWork{rp, url}对于同一密钥,最昂贵的工作(在这种情况下,是由处理)可能会发生多次,这是不可接受的。
icza:

I.模拟 pool.cacheLock.Lock()

覆盖该分支的一种方法是模拟pool.cacheLock.Lock()模拟版本可以将插入url到地图中。因此,在此调用之后再次检查,将找到并执行将进入第二条if语句的主体

使用界面模拟

模拟的一种方法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"))

还有一个测试用例将触发第二个主体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标志

这里的想法是,为了支持简单测试,您可以test在实现中构建一个简单标志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)
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

创建情况不太可能发生的单元测试是值得的麻烦?

Linux内核中可能的/不太可能的宏如何工作,它们的好处是什么?

如何在if-else语句中使用C ++ 20的可能/不太可能属性

[[可能]]和[[不太可能]]影响程序汇编的简单示例?

复合布尔值与不太可能/可能的性能

虚拟函数是否不太可能导致堆栈溢出?

随机令牌生成-发生了可能不太可能的冲突

C ++ 20:[[可能]],[[不太可能]]和__builtin_expect之间的区别?

.text。在ELF目标文件中不太可能意味着什么?

在用户输入中不太可能找到Java中的Character.LINE_SEPARATOR?

pd.to_datetime ValueError:给定的日期字符串不太可能是日期时间

可以/不太可能在用户空间代码中使用宏吗?

equals()的参数类型不太可能:字符串似乎与MemberSearchRequest不相关

如果不太可能发生硬崩溃错误,是否应该使用?

在类似于Google位置记录的应用程序中检测“不太可能”的位置

我是否不太可能从USB闪存驱动器恢复数据?

内核中可能发生的呼叫和不太可能发生的呼叫之间有什么区别?

为什么宏不可能和不太可能对ARM汇编代码产生任何影响?

将表达式的仅一部分标记为“可能”(/)/“不太可能()”是否合理

克服对equals()方法不太可能的参数类型优雅的方式:流<字符串>似乎是无关的字符串

DynamoDB 单表设计中的分区键或排序键是否应该只包含不太可能更改的字段?

gcc -O3标志导致-Winline“不太可能发生调用,并且代码大小会增加”警告

这可能会失败哪些不太明显的测试?

Unity中非常奇怪的错误(或在Gimp和Skype中不太可能出现)-一些Windows不显示任何内容

如果iOS应用不是由Interface Builder制作的,那么它是否不太可能受到操作系统更新的负面影响?

SQL,如何测试并发事务

如何用片段场景测试片段?

如何在.NET中测试并发方案?

如何测试并发和锁定golang?