我有一个生产golang代码和功能测试,它不是用golang编写的。功能测试运行编译二进制。我的生产代码的非常简化版本在这里:main.go
:
package main
import (
"fmt"
"math/rand"
"os"
"time"
)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
os.Exit(0)
}
if i%2 == 0 {
os.Exit(1)
}
time.Sleep(time.Second)
}
}
我想为我的功能测试构建覆盖率配置文件。为此,我添加了main_test.go
文件内容:
package main
import (
"os"
"testing"
)
var exitCode int
func Test_main(t *testing.T) {
go main()
exitCode = <-exitCh
}
func TestMain(m *testing.M) {
m.Run()
// can exit because cover profile is already written
os.Exit(exitCode)
}
修改main.go
:
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"time"
)
var exitCh chan int = make(chan int)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
exit(0)
}
if i%2 == 0 {
fmt.Println("status 1")
exit(1)
}
time.Sleep(time.Second)
}
}
func exit(code int) {
if flag.Lookup("test.coverprofile") != nil {
exitCh <- code
runtime.Goexit()
} else {
os.Exit(code)
}
}
然后我构建覆盖二进制文件:
go test -c -coverpkg=. -o myProgram
然后我的功能测试运行这个覆盖二进制文件,如下所示:
./myProgram -test.coverprofile=/tmp/profile
6507374435908599516
PASS
coverage: 64.3% of statements in .
我构建了HTML输出,显示了覆盖范围:
$ go tool cover -html /tmp/profile -o /tmp/profile.html
$ open /tmp/profile.html
由于条件exit
,方法if flag.Lookup("test.coverprofile") != nil
永远不会显示100%的覆盖率。因此,os.Exit(code)
行对我的覆盖结果来说是一个盲点,但事实上,功能测试会在此行上进行,此行应显示为绿色。
另一方面,如果我删除条件if flag.Lookup("test.coverprofile") != nil
,则行os.Exit(code)
将终止我的二进制文件而不构建覆盖率配置文件。
如何重写exit()
和main_test.go
以显示覆盖没有盲点?
首先想到的解决方案是time.Sleep()
:
func exit(code int) {
exitCh <- code
time.Sleep(time.Second) // wait some time to let coverprofile be written
os.Exit(code)
}
}
但它不是很好,因为在退出之前会导致生产代码变慢。
答案 0 :(得分:3)
根据我们在评论中的对话,我们的覆盖率配置文件将永远不会包含该行代码,因为它永远不会被执行。
如果没有看到完整的代码,很难找到合适的解决方案,但是你可以采取一些措施来增加覆盖率而不会牺牲太多。
GOLANG
的标准做法是避免测试主应用程序入口点,以便大多数专业人员将其他功能提取到其他类中,以便轻松测试。
GOLANG
测试框架允许您使用main函数测试应用程序,但在其中您可以使用TestMain函数,该函数可用于测试代码需要在主线程上运行的位置。以下是来自GOLANG Testing的小动作。
测试程序有时需要在测试之前或之后进行额外的设置或拆卸。有时还需要测试来控制在
main
线程上运行哪些代码。要支持这些和其他情况,如果测试文件包含函数:func TestMain(m *testing.M)
查看GOLANG Testing了解详情。
以下是测试代码所有功能的示例(覆盖范围为93.3%,我们将100%覆盖)。我对你的设计做了一些改动,因为它不适合测试,但功能仍然相同。
package main
dofunc.go
import (
"fmt"
"math/rand"
"time"
)
var seed int64 = time.Now().UTC().UnixNano()
func doFunc() int {
rand.Seed(seed)
var code int
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
code = 0
break
}
if i%2 == 0 {
fmt.Println("status 1")
code = 1
break
}
time.Sleep(time.Second)
}
return code
}
dofunc_test.go
package main
import (
"testing"
"flag"
"os"
)
var exitCode int
func TestMain(m *testing.M) {
flag.Parse()
code := m.Run()
os.Exit(code)
}
func TestDoFuncErrorCodeZero(t *testing.T) {
seed = 2
if code:= doFunc(); code != 0 {
t.Fail()
}
}
func TestDoFuncErrorCodeOne(t *testing.T) {
seed = 3
if code:= doFunc(); code != 1 {
t.Fail()
}
}
main.go
package main
import "os"
func main() {
os.Exit(doFunc());
}
如果我们使用封面配置文件构建我们的应用程序。
$ go test -c -coverpkg=. -o example
然后运行它。
$ ./example -test.coverprofile=/tmp/profile
运行测试
1543039099823358511
2444694468985893231
6640668014774057861
6019456696934794384
status 1
PASS
coverage: 93.3% of statements in .
所以我们看到我们得到93%的覆盖率,我们知道这是因为我们没有main
的任何测试覆盖来解决这个问题我们可以为它编写一些测试(不是一个好主意)因为代码有os.Exit
或者我们可以重构它所以它非常简单,功能很少,我们可以将它从测试中排除。
要从覆盖率报告中排除main.go
文件,我们可以通过在tags
文件的第一行放置标记注释来使用版本main.go
。
//+build !test
有关构建标志的更多信息,请访问以下链接:http://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool
这将告诉GOLANG
该文件应该包含在构建过程中,其中存在标记构建,但NOT
存在标记测试。
查看完整代码。
//+build !test
package main
import "os"
func main() {
os.Exit(doFunc());
}
我们需要建立略有不同的覆盖应用程序。
$ go test -c -coverpkg=. -o example -tags test
运行它会是一样的。
$ ./example -test.coverprofile=/tmp/profile
我们收到以下报告。
1543039099823358511
2444694468985893231
6640668014774057861
6019456696934794384
status 1
PASS
coverage: 100.0% of statements in .
我们现在可以构建覆盖html了。
$ go tool cover -html /tmp/profile -o /tmp/profile.html
答案 1 :(得分:0)
在我的pkglint project中,我声明了一个包可见的变量:
var exit = os.Exit
在设置测试的代码中,我用特定于测试的功能将其覆盖,并且在拆除测试时,将其重置回os.Exit。
这是一个简单实用的解决方案,对我来说至少需要一年的广泛测试。我得到了100%的分支覆盖率,因为根本不涉及分支。