仅在测试文件中屏蔽程序包名称

时间:2017-04-25 19:59:54

标签: unit-testing go

为了追求100%的单元测试覆盖率,我们在我们的一个功能中尝试了几行。相关函数调用运行时包:

// functionName returns a string representing the function name of the function n stack frames above the caller.
// if n = 0, the name of the function calling functionName() will be returned.
func functionName(n int) string {
    pc, _, _, ok := runtime.Caller(n + 1)
    if !ok {
        return "unknown function"
    }
    me := runtime.FuncForPC(pc)
    if me == nil {
        return "unknown function"
    }

    split := strings.Split(me.Name(), ".")
    if len(split) == 0 {
        return "unknown function"
    }
    return split[len(split)-1]
}

具体来说,3 if语句及其返回值当前未经测试,因为运行时函数似乎不易被操作以返回我们想要的值。在这些情况下,我们的标准响应是模拟有问题的项,但这些调用是在运行时包本身内的包级函数(而不是接口的方法)。

我的第一个想法是通过使用具有Caller()和FuncForPC()方法的结构来模拟运行时令牌本身,该方法被分配给测试文件中名为“runtime”的变量(因此它不会影响生产代码flow,因为在正常构建期间省略了测试文件)。但是,这会触发在(全局)块中重新声明的“运行时”的构建错误。

我知道如果“运行时”变量在非全局范围(example masking fmt)中声明,这是可能的,但是我找不到一种优雅的方法来这样做,以便它被掩盖在测试,但不在生产代码本身内。我想到的唯一方法是通过改变生产代码的来源来声明这样的变量并在测试中替换它的值,但这远非理想,因为它纯粹为了测试目的而使生产代码复杂化。

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

一种解决方案是声明要模拟的那些函数的变量。

var runtimeCaller = runtime.Caller
var runtimeFuncForPC = runtime.FuncForPC

func functionName(n int) string {
    pc, _, _, ok := runtimeCaller(n + 1)
    if !ok {
        return "unknown function"
    }
    me := runtimeFuncForPC(pc)
    if me == nil {
        return "unknown function"
    }

    split := strings.Split(me.Name(), ".")
    if len(split) == 0 {
        return "unknown function"
    }
    return split[len(split)-1]
}

或者如果您更喜欢点符号......

var _runtime = struct{
    Caller    func(skip int) (pc uintptr, file string, line int, ok bool)
    FuncForPC func(pc uintptr) *runtime.Func
}{runtime.Caller, runtime.FuncForPC}

func functionName(n int) string {
    pc, _, _, ok := _runtime.Caller(n + 1)
    if !ok {
        return "unknown function"
    }
    me := _runtime.FuncForPC(pc)
    if me == nil {
        return "unknown function"
    }

    split := strings.Split(me.Name(), ".")
    if len(split) == 0 {
        return "unknown function"
    }
    return split[len(split)-1]
}

在测试中,在运行functionName之前,您可以将变量/字段设置为模拟实现。如果其他测试可能会导致functionName被称为并发访问...我认为如果不显着更改现有代码,您还可以做很多事情。

答案 1 :(得分:0)

  

On the reliability of programs. Edsger W. Dijkstra

     

故事的第一个道理是程序测试可以非常用   有效地显示错误的存在,但从来没有显示他们的   不存在。

让我们阅读您的代码。 Go类型int是32位或64位有符号整数。因此,请考虑,

funcname.go

package main

import (
    "fmt"
    "runtime"
    "strings"
)

// functionName returns a string representing the function name of the function n stack frames above the caller.
// if n = 0, the name of the function calling functionName() will be returned.
func functionName(n int) string {
    pc, _, _, ok := runtime.Caller(n + 1)
    if !ok {
        return "unknown function"
    }
    me := runtime.FuncForPC(pc)
    if me == nil {
        return "unknown function"
    }

    split := strings.Split(me.Name(), ".")
    if len(split) == 0 {
        return "unknown function"
    }
    return split[len(split)-1]
}

func main() {
    for skip := -4; skip <= 4; skip++ {
        fn := functionName(skip)
        fmt.Println(functionName(0), skip, fn)
    }
    const (
        sizeInt = 32 << (^uint(0) >> 63)
        maxInt  = 1<<(sizeInt-1) - 1
        minInt  = -1 << (sizeInt - 1)
    )
    for _, skip := range []int{minInt, maxInt} {
        fn := functionName(skip)
        fmt.Println(functionName(0), skip, fn)
    }
}

输出:

$ go run funcname.go
main -4 skipPleaseUseCallersFrames
main -3 skipPleaseUseCallersFrames
main -2 skipPleaseUseCallersFrames
main -1 functionName
main 0 main
main 1 main
main 2 goexit
main 3 unknown function
main 4 unknown function
main -9223372036854775808 skipPleaseUseCallersFrames
main 9223372036854775807 skipPleaseUseCallersFrames
$  

对我来说,这似乎是functionName中的错误。您的覆盖测试对此有何评价?

读取代码似乎没有可靠的方法来检测错误值的返回。一种方法是返回一个空字符串。如果要使用"unknown function"之类的特殊值,请提供要检查的值。例如,

const functionUnknown = "unknown function"

func functionName(n int) string {
    pc, file, line, ok := runtime.Caller(n + 1)
    if !ok {
        return functionUnknown
    }
    // . . .
}

func main() {
    fn := functionName(0)
    if fn == functionUnknown {
        // handle error
    }
}

您的承保测试对此有何评价?