单元测试

简介

单元测试是为了对函数或者模块的功能进行测试。以往,我们在调试程序的时候,是有main函数的,在main函数中对函数进行调用,然后观察运行结果是否正确。

但是实际项目开发中,这样的操作手段存在很多隐患:

  • 多个测试用例在main中进行调用时,为了不调用某个测试用例就需要先注释掉然后再运行main
  • 由于在main中修改,会影响到程序主体架构,这样不利于版本的构建

所以Go引入了Testing测试框架来进行单元测试。

基本规则

  1. 为了使用Testing,需要引入Testing包
  2. 文件名以_test.go结尾是测试文件。
  3. 在 _test.go 中编写测试用例时,函数以Test开头,且Test之后第一个字母不能是小写字母,因为go识别Test是根据驼峰命名法识别的。
  4. 运行_test.go文件的方法是在测试文件所在目录下,输入命令go test
    • go test和go test -v的区别是前者仅在有错误的情况下才会输出详细信息(正确仅输出PASS),后者是任何情况都会输出详细信息

例子

例子是查找数组中的值,常规方法是O(N),而排序后使用二分法是O(NlogN)(排序算法是O(NlogN)+二分是O(logN))。

在工程下新建Search文件夹,下面新建Search.go和Search_test.go文件

Search.go

package Search

import "sort"

func BinarySearch1(arr []int,val int) bool{
	for i:=0;i<len(arr);i++{
		if arr[i]==val{
			return true
		}
	}
	return false
}
func BinarySearch2(arr []int,val int) bool{
	sort.Slice(arr, func(i, j int) bool {
		return arr[i]<arr[j]
	})
	ind:=sort.Search(len(arr), func(i int) bool {
		return arr[i]<val
	})
	if ind<len(arr) && arr[ind]==val{
		return true
	}
	return false
}

Search_test.go

package Search

import (
	"log"
	"testing"
)

func TestBinarySearch1(t *testing.T) {
	arr:=[]int{1,2,3,4,5}
	val:=5
	if !BinarySearch1(arr,val){
		log.Fatalln("期望返回true,实际上返回false",val)
	}
	val=6
	if BinarySearch1(arr,val){
		log.Fatalln("期望返回false,实际上返回true",val)
	}
}
func TestBinarySearch2(t *testing.T) {
	arr:=[]int{1,2,3,4,5}
	val:=5
	if !BinarySearch2(arr,val){
		log.Fatalln("期望返回true,实际上返回false")
	}
	val=6
	if BinarySearch2(arr,val){
		log.Fatalln("期望返回false,实际上返回true")
	}
}

上面Search_Test.go中的log.Fatalln就可以在有错误的情况下报错,从而告知Testing框架是否PASS

命令

go test -v

输出

=== RUN   TestBinarySearch1
--- PASS: TestBinarySearch1 (0.00s)
=== RUN   TestBinarySearch2
2021/08/14 16:48:09 期望返回true,实际上返回false
exit status 1
FAIL    awesomeProject/Search   0.305s

上述可以看见第二个用例没有通过,说明代码出现问题

那么我们在修改部分错误代码修改后(修改可以通过调试,goland是支持调试测试函数的

func BinarySearch2(arr []int,val int) bool{
	sort.Slice(arr, func(i, j int) bool {
		return arr[i]<arr[j]
	})
	ind:=sort.Search(len(arr), func(i int) bool {
		return arr[i]>=val
	})
	if ind<len(arr) && arr[ind]==val{
		return true
	}
	return false
}

如果我们只想测试第二个测试用例可以使用如下命令

命令

go test -v -test.run TestBinarySearch2

输出

=== RUN   TestBinarySearch2
--- PASS: TestBinarySearch2 (0.00s)
PASS
ok      awesomeProject/Search   0.323s

实际上,如果在目录下存在多个测试用例文件,我们还可以指定测试文件,注意一定要带上测试的源文件

命令

go test -v Search_test.go Search.go

输出

=== RUN   TestBinarySearch1
--- PASS: TestBinarySearch1 (0.00s)
=== RUN   TestBinarySearch2
--- PASS: TestBinarySearch2 (0.00s)
PASS
ok      command-line-arguments  0.258s

上面的输出标出了几个时间,虽然看起来两个函数运行时间都是0.00s(太快以至于可以忽略不计)但是最后总时间是0.258s,这是因为Testing测试框架运行还需要时间。

如果我将测试用例改为如下:

func TestBinarySearch1(t *testing.T) {
	arr:=[]int{1,2,3,4,5}
	for i:=0;i<100000;i++{
		arr=append(arr,i)
	}
	val:=99999
	if !BinarySearch1(arr,val){
		log.Fatalln("期望返回true,实际上返回false")
	}
	val=100000
	if BinarySearch1(arr,val){
		log.Fatalln("期望返回false,实际上返回true")
	}
}
func TestBinarySearch2(t *testing.T) {
	arr:=[]int{1,2,3,4,5}
	for i:=0;i<100000;i++{
		arr=append(arr,i)
	}
	val:=99999
	if !BinarySearch2(arr,val){
		log.Fatalln("期望返回true,实际上返回false")
	}
	val=100000
	if BinarySearch2(arr,val){
		log.Fatalln("期望返回false,实际上返回true")
	}
}

命令

go test -v Search_test.go Search.go

输出

=== RUN   TestBinarySearch1
--- PASS: TestBinarySearch1 (0.01s)
=== RUN   TestBinarySearch2
--- PASS: TestBinarySearch2 (0.06s)
PASS
ok      command-line-arguments  0.389s

可以看出时间的差别。