表驱动测试的子集

时间:2016-08-12 06:45:51

标签: unit-testing testing go tdd

对于测试功能,我可以选择哪个将由选项-run运行。

go test -run regex

如果我们将几十个测试用例放入数组中以便不为每个测试用例编写函数,那么很常见:

cases := []struct {
  arg, expected string
} {
    {"%a", "[%a]"},
    {"%-a", "[%-a]"},
    // and many others
}
for _, c := range cases {
  res := myfn(c.arg) 
  if  res != c.expected {
    t.Errorf("myfn(%q) should return %q, but it returns %q", c.arg, c.expected, res)
  }
}

这项工作很好,但问题在于维护。当我添加一个新的测试用例时,在调试时我想开始一个新的测试用例,但我不能说:

go test -run TestMyFn.onlyThirdCase

有没有任何优雅的方法,如何在阵列中有多个测试用例以及选择运行哪个测试用例的能力?

2 个答案:

答案 0 :(得分:3)

使用Go 1.6(及以下)

Go 1.6及以下版本的testing软件包不直接支持此功能。你必须自己实现它。

但它并不那么难。您可以使用flag包轻松访问命令行参数。

让我们看一个例子。我们定义一个"idx"命令行参数,如果存在,只会执行该索引的大小写,否则将执行所有测试用例。

定义标志:

var idx = flag.Int("idx", -1, "specify case index to run only")

解析命令行标志(实际上,这不是必需的,因为go test已经调用了这个,但只是为了确定/完成):

func init() {
    flag.Parse()
}

使用此参数:

for i, c := range cases {
    if *idx != -1 && *idx != i {
        println("Skipping idx", i)
        continue
    }
    if res := myfn(c.arg); res != c.expected {
        t.Errorf("myfn(%q) should return %q, but it returns %q", c.arg, c.expected, res)
    }
}

使用3个测试用例进行测试:

cases := []struct {
    arg, expected string
}{
    {"%a", "[%a]"},
    {"%-a", "[%-a]"},
    {"%+a", "[%+a]"},
}

没有idx参数:

go test

输出:

PASS
ok      play    0.172s

指定索引:

go test -idx=1

输出:

Skipping idx 0
Skipping idx 2
PASS
ok      play    0.203s

当然,您可以实现更复杂的过滤逻辑,例如您可以使用minidxmaxidx标志来运行范围内的案例:

var (
    minidx = flag.Int("minidx", 0, "min case idx to run")
    maxidx = flag.Int("maxidx", -1, "max case idx to run")
)

过滤:

if i < *minidx || *maxidx != -1 && i > *maxidx {
    println("Skipping idx", i)
    continue
}

使用它:

go test -maxidx=1

输出:

Skipping idx 2
PASS
ok      play    0.188s

从Go 1.7开始

Go 1.7(将于2016年8月18日发布)添加了subtests and sub-benchmarks的定义:

  

测试包现在支持使用子测试的子测试和基准测试的定义。这种支持使得编写表驱动基准并创建分层测试变得容易。它还提供了一种共享通用设置和拆卸代码的方法。有关详细信息,请参阅package documentation

有了这个,你可以做以下事情:

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

子测试的名称为"A=1""A=2""B=1"

  

-run和-bench命令行标志的参数是斜杠分隔的正则表达式列表,它们依次匹配每个名称元素。例如:

go test -run Foo     # Run top-level tests matching "Foo".
go test -run Foo/A=  # Run subtests of Foo matching "A=".
go test -run /A=1    # Run all subtests of a top-level test matching "A=1".

这对您的情况有何帮助?子测试的名称是string值,可以在运行中生成,例如:

for i, c := range cases {
    name := fmt.Sprintf("C=%d", i)
    t.Run(name, func(t *testing.T) {
        if res := myfn(c.arg); res != c.expected {
            t.Errorf("myfn(%q) should return %q, but it returns %q",
                c.arg, c.expected, res)
        }
    })
}

要在索引2处运行案例,您可以像

一样启动它
go test -run /C=2

go test -run TestName/C=2

答案 1 :(得分:0)

我编写了一个简单的代码,两者都可以正常工作,尽管命令行选项有点不同。 1.7的版本是:

// +build go1.7

package plist

import "testing"

func runTest(name string, fn func(t *testing.T), t *testing.T) {
    t.Run(name, fn)
}

和1.6岁及以上:

// +build !go1.7

package plist

import (
    "flag"
    "testing"
    "runtime"
    "strings"
    "fmt"
)

func init() {
    flag.Parse()
}

var pattern = flag.String("pattern", "", "specify which test(s) should be executed")
var verbose = flag.Bool("verbose", false, "write whether test was done")

// This is a hack, that a bit simulate t.Run available from go1.7
func runTest(name string, fn func(t *testing.T), t *testing.T) {
    // obtain name of caller
    var pc[10]uintptr
    runtime.Callers(2, pc[:])
    var fnName = ""

    f := runtime.FuncForPC(pc[0])
    if f != nil {
        fnName = f.Name()
    }
    names := strings.Split(fnName, ".")
    fnName = names[len(names)-1] + "/" + name
    if strings.Contains(fnName, *pattern) {
        if *verbose {
            fmt.Printf("%s is executed\n", fnName)
        }
        fn(t)
    } else {
        if *verbose {
            fmt.Printf("%s is skipped\n", fnName)
        }
    }
}