作者:非妃是公主
专栏:《Golang》
博客主页:https://blog.csdn.net/myf_666
个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩
文章目录
- 序
- 一、单元测试
- 1. 测试文件命名
- 2. 测试函数
- 3. 测试覆盖率
- 4. Tips
- 二、Mock测试
- 三、基准测试
- 1. 性能劣化
- 2. 原因分析
- 3. 性能优化
- 4. 一个小疑问?
- the end……
序
软件测试:软件测试(英语:Software Testing),描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。换句话说,软件测试是一种实际输出与预期输出之间的审核或者比较过程。软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
软件测试的概念大家都很熟悉,他是为了发现程序中的错误,但永远也无法证明软件没有错误。
同时,软件测试按照测试策略可以分为单元测试、集成测试、回归测试等。按照测试用例的编写方法,又可以分成黑盒测试、白盒测试等。
单元测试,是测试中成本最低,也最容易发现bug的一个缓解。对于不同的编程语言,一般有着不同的单元测试框架!就Go语言而言,有3个方面的测试——单元测试、Mock测试、基准测试。
一、单元测试
1. 测试文件命名
所有测试文件以_test
结尾,如下图:
2. 测试函数
测试函数名字,为TestXxx,其中为函数名前加上Test,print.go
示例如下:
package testfunc HelloTom() string {return "非妃是公主"
}
print_test.go
示例如下:
package testimport ("github.com/stretchr/testify/assert""testing"
)func TestHelloTom(t *testing.T) {output := HelloTom()expectOutput := "Tom"assert.Equal(t, expectOutput, output)
}
运行测试程序,测试输出如下:
修改函数返回为Tom
后输出正常,如下:
值得注意的是,由于一些复杂的测试用例,需要在测试前进行初始化,这可以放在TestMain函数中进行定义,完成前置
及释放
操作。
3. 测试覆盖率
如下运行后,可以在控制台看到测试覆盖率,具体操作如下:
从这里可以看到测试覆盖率情况,如下:
4. Tips
- 一般测试覆盖率应该在50%~60%,较高覆盖率80%+。
- 测试分支相互独立,全面覆盖。
- 测试粒度足够小,函数单一职责。
二、Mock测试
Mock也叫做打桩,它的作用是可以降低程序不同模块之间的耦合度。比如,正常我们需要从一个文件中读取数据,然后再进行数据处理模块的测试,但是由于读取数据这部分也是存在代码的,也可能出现异常、错误。
如果最终测试没有通过,就存在两种可能:
- 文件读取存在问题;
- 数据处理存在问题。
为了使得出现错误的可能性更为单一,便于问题的定位。
我们就可以通过mock,将这部分的代码替换掉,生成虚拟数据,这时候,输入到数据处理函数中,这样就可以实现对数据处理模块的Mock测试。
下面为一个读取文件的函数:
func ReadFirstLine() string {open, err := os.Open("log")defer open.Close()if err != nil {return ""}scanner := bufio.NewScanner(open)for scanner.Scan() {return scanner.Text()}return ""
}
下面为数据处理函数:
func ProcessFirstLine() string {line := ReadFirstLine()destLine := strings.ReplaceAll(line, "11", "00")return destLine
}
我们要对处理函数进行测试,但是处理函数中是依赖一个ReadFirstLine
这个函数的,常规的单元测试如下:
func TestProcessFirstLine(t *testing.T) {firstLine := ProcessFirstLine()assert.Equal(t, "line00", firstLine)
}
从上面可以看出,常规的单元测试就是正常调用ProcessFirstLine
函数,因此需要调用ReadFirstLine
里面的函数,会造成错误定位不精确的问题。
因此就需要将其返回值Mock,如下:
func TestProcessFirstLineWithMock(t *testing.T) {monkey.Patch(ReadFirstLine, func() string {return "line110"})defer monkey.Unpatch(ReadFirstLine)line := ProcessFirstLine()assert.Equal(t, "line000", line)
}
需要用到的库,如下:
import ("bou.ke/monkey""github.com/stretchr/testify/assert""testing"
)
这里的monkey是一个mock相关的库,通过它的patch函数,即可实现打桩,进而不再依赖本地文件。
三、基准测试
首先来看一下什么是基准测试,百度百科定义如下:
基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。例如,对计算机CPU进行浮点运算、数据访问的带宽和延迟等指标的基准测试,可以使用户清楚地了解每一款CPU的运算性能及作业吞吐能力是否满足应用程序的要求。1
下面来看一个负载均衡的例子,首先有10个服务器,每次选择一个服务器进行执行,代码如下:
import ("github.com/bytedance/gopkg/lang/fastrand""math/rand"
)var ServerIndex [10]intfunc InitServerIndex() {for i := 0; i < 10; i++ {ServerIndex[i] = i+100}
}func Select() int {return ServerIndex[rand.Intn(10)]
}
其中,initServerIndex
是初始化服务器函数,Select即为随机选择一个服务器实现负载均衡。
基准测试函数命名以Benchmark开头,输入参数是testing.B,用b中的N值(即:b.N)反复递增循环测试。(如果Select的运行时间小于1s,那么N值将按照1、2、5、10、20、50……递增,直到递增到运行时间超过1s为止,然后去计算平均时间;如果超过1s,那么N就为1。)
这样处理的好处就是,可以使得求解得到的时间更加准确,不会收到机器运行状态的影响。
代码如下:
func BenchmarkSelect(b *testing.B) {InitServerIndex()b.ResetTimer()for i := 0; i < b.N; i++ {Select()}
}
其中,ResetTimer()
为重置计时器,再Select操作前进行充值,可以使得时间检测更加准确。
以Parallel结尾标识多协程并发测试,测试代码如下:
func BenchmarkSelectParallel(b *testing.B) {InitServerIndex()b.ResetTimer()b.RunParallel(func(pb *testing.PB) {for pb.Next() {Select()}})
}
1. 性能劣化
运行后发现,在并发情况下,代码性能存在一定的劣化。
2. 原因分析
分析原因,是由于rand为了保证全局的随机性和并发安全,持有了一把全局锁,进而影响了性能。
3. 性能优化
由于字节跳动公司后端主要采用Go语言,因此为了解决这一问题,字节跳动公司开源了一个高性能随机数方法 fastrand,开源地址为:https://github.com/bytedance/gopkg.
fastrand优化后的负载均衡代码如下:
import ("github.com/bytedance/gopkg/lang/fastrand"
)func FastSelect() int {return ServerIndex[fastrand.Intn(10)]
}
从上图可以看出,无论是并发还是串行运行,FastSelect效率都比Select高。
4. 一个小疑问?
这里有一个疑问,知道的读者可以评论区回答一下。为什么串行的FastSelect运行时间是3.467ns,而并行的是0.5309ns呢?
这里我提出一种猜测,产生这种原因,可能和Timer
的计时方法有关!比如利用如下公示:
E f f i c i e n c y = R u n T i m e N u m O p e r a t i o n s Efficiency=\frac{RunTime}{NumOperations} Efficiency=NumOperationsRunTime
其中,Efficiency
用的就是ns/op
作为单位,数值越小,表示消耗的时间越小,效率也就越高!RunTime
单位为ns
,NumOperations
指在这段时间内执行的操作数。
这样我们就可以解释问什么并行操作的效率更高了,因为我们在RunTime这段时间内有多个操作在并行执行,也就是分母会很大,这样效率也就会更高了。
总结一下,不难发现,基准测试主要是对程序的某一性能指标进行可对比的测试,然后点对点的进行性能的优化,因此更具有针对性。
the end……
Go语言测试——单元测试、Mock测试、基准测试三部分的内容到这里就要结束啦~~
到此既是缘分,欢迎您的点赞、评论、收藏!关注我,不迷路,我们下期再见!!
😘😘😘 我是Cherries,一位计算机科班在校大学生,写博客用来记录自己平时的所思所想!
💞💞💞 内容繁杂,又才疏学浅,难免存在错误,欢迎各位大佬的批评指正!
👋👋👋 我们相互交流,共同进步!
注:本文由
非妃是公主
发布于https://blog.csdn.net/myf_666,转载请务必标明原文链接:https://blog.csdn.net/myf_666/article/details/128938363
百度百科——基准测试 ↩︎