golang单元测试

816 篇文章 0 订阅
754 篇文章 0 订阅

2024软件测试面试刷题,这个小程序(永久刷题),靠它快速找到工作了!(刷题APP的天花板)_软件测试刷题小程序-CSDN博客文章浏览阅读3.4k次,点赞86次,收藏15次。你知不知道有这么一个软件测试面试的刷题小程序。里面包含了面试常问的软件测试基础题,web自动化测试、app自动化测试、接口测试、性能测试、自动化测试、安全测试及一些常问到的人力资源题目。最主要的是他还收集了像阿里、华为这样的大厂面试真题,还有互动交流板块……_软件测试刷题小程序​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502​编辑https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502icon-default.png?t=N7T8https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5502

单元测试有什么用?

单元测试是检测你写的一个函数是否具备安全性的一次检测。比如,当你写了一个函数,传入你期望的值,该函数是能正常执行的。但是当你传入一个非合理的值后,你写的函数直接宕机,而不是返回err。那就说明你写的函数存在逻辑漏洞。一个通过单元测试的函数,无论传入什么类型的值,返回的要么是结果,要么是error,绝对不会停止运行。

go test工具

在golang语言中存在测试依赖go test命令,go test命令会对包路径下的所有_test.go为后缀的源代码文件进行测试,不会被go build编译到可执行文件中。

在*_test.go文件中,包含三种类型的函数:单元测试函数、基准测试函数、示例函数

类型文件名作用
测试函数TestXxxx测试程序的一些逻辑行为是否正确
基准函数BenchchmarkXxxx测试函数的性能
示例函数ExampleXxxx为文档提供示例文档

go test 命令会对遍历*_test.go文件中的符合上述规范的函数,生成一个临时的main包用于调用相应的测试函数,重构、运行、报告、测试,最后清除测试中的生成的临时文件。

测试函数

每个测试函数都必须导入testing包。

// 用于对str字符串的子串截取
func subString(str string, start, end int) string {
   //将str转换为[]rune,支持Unicode编码
   rs := []rune(str)
   length := len(rs)
   if start < 0 || start > length {
      panic("start is wrong")
   }
   if end < start || end > length {
      panic("end is wrong")
   }
   return string(rs[start:end])
}

上述函数传入str、start、end参数,分别表示原始字符串,起始下标,结束下标。目的是为了获取str的子串。

这里有个知识点,是关于go语言的编码的:

在golang中,其采纳的编码方式是UTF8的支持中文编码方式,而非Unicode。在UTF8中,一个汉字是用三个字节来表示的。举个列子,str := "hang行",其len(str)==7,如果你从start ==5 ,end == 6切割就会出现乱码,相当于你截取了汉字的三分之一。当str转换为[]rune类型后,len([]rune(str)) == 5,这个时候汉字就是一个整体,就能完整的截取"行"这个汉字。

func TestCode(t *testing.T) {
   // []rune与Unicode
   str := "i am your 爸爸"
   fmt.Printf("str的原始长度:%d\n", len(str))
   fmt.Printf("str的[]rune类型长度:%d\n", len([]rune(str)))
}


打印信息:
API server listening at: 127.0.0.1:64049
WARNING: undefined behavior - version of Delve is too old for Go version 1.19.0 (maximum supported version 1.18)
=== RUN   TestCode
str的原始长度:16
str的[]rune类型长度:12
--- PASS: TestCode (0.00s)
PASS

对[]rune的测试完毕,回到测试函数中:

func TestSubString(t *testing.T) {
   got := subString("hang", 0, 1)
   want := "h"
   if !reflect.DeepEqual(got, want) {
      t.Error("subString test is failed")
   }
}

打印信息:
API server listening at: 127.0.0.1:57824
WARNING: undefined behavior - version of Delve is too old for Go version 1.19.0 (maximum supported version 1.18)
=== RUN   TestSubString
--- PASS: TestSubString (0.00s)
PASS

在该测试函数中,got为函数返回信息;want为期望值。经过reflect.DeepEqual(got,want)函数进行匹配比较。

reflect.DeepEqual(got,want)函数:用于比较两个值的深度相等性。它可以用来在测试中判断期望的值和实际得到的值是否相等。reflect.DeepEqual对比的是值的具体内容,而不仅仅是内存地址或类型。用于确保值的类型和结构是一致的。

testing.T类型的方法作用
Error输出一个错误消息,并标记该测试为失败。
Errorf格式化输出一个错误消息,并标记该测试为失败。
Fail标记该测试为失败。
FailNow标记该测试为失败,并立即停止当前测试函数的执行。
Fatal输出一个致命错误消息,并终止测试执行
Fatalf格式化输出一个致命错误消息,并终止测试执行。
Name返回当前测试的名称。
Run运行一个子测试函数,并指定其名称。

目前常用的testing.T类型的打印信息函数是上诉这几个。如果对其他函数感兴趣,可以查阅资料。

测试函数是能够执行了,但是只能针对一个测试用例。如果有多个测试用例呢?这就引入了子测试函数的使用:

func TestSubString(t *testing.T) {

   type test struct {
      str   string
      start int
      end   int
      want  interface{}
   }
   es := map[string]test{
      "basic":   {str: "basic", start: 0, end: 4, want: "basi"}, //basi
      "basicCN": {str: "你好world", start: 0, end: 1, want: "你"},  //"你"
   }
   for name, testValue := range es {
      t.Run(name, func(t *testing.T) {
         got := subString(testValue.str, testValue.start, testValue.end)
         if !reflect.DeepEqual(got, testValue.want) {
            t.Errorf("the name== %s got %v, want %v", t.Name(), got, testValue.want)
         }
      })
   }
}


API server listening at: 127.0.0.1:64621
WARNING: undefined behavior - version of Delve is too old for Go version 1.19.0 (maximum supported version 1.18)
=== RUN   TestSubString
=== RUN   TestSubString/basic
=== RUN   TestSubString/basicCN
--- PASS: TestSubString (0.00s)
    --- PASS: TestSubString/basic (0.00s)
    --- PASS: TestSubString/basicCN (0.00s)
PASS

说明:在我写的函数SubString中,有 panic("start is wrong") 语句,这个结果在测试单元中是无法want的,即这种结果是无法测试。但是这个函数对我的项目是很重要的,未达到start和end就必须要panic。无需去深究,你可以将panic换成error.new("XXXX")。测试函数的部分知识就讲解完毕了,文章后续有案例实战。

基准测试

基准测试是在一定的工作负载下检测程序性能的一种方法。函数一定是先通过测试函数之后,才开始对该函数性能的检测。很多程序员在开发了一套函数时,就会有一种心态:能跑就行。其实这样想也没问题,工作嘛。基准测试也是检测自身编写代码的性能。很简单一个道理,同样是写一样功能的代码,为什么就用其他的,而不用你的,因为其他的在性能测试上超越了你。

基准测试是以Benchmark为开头的,需要以*testing.B类型的参数。

func BenchmarkSubstring(b *testing.B) {
   for i := 0; i < b.N; i++ {
      subString("Benchmark", 0, 5)
   }
}

goos: windows
goarch: amd64
pkg: sspaas.io/gpu-manage/utils/data
cpu: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz
BenchmarkSubstring
BenchmarkSubstring-8    14777178                81.77 ns/op
PASS

相较于单元测试,基准测试使用得不是很多。

BenchmarkSubstring-8    14777178                81.77 ns/op
表示Substring函数在迭代14777178次下,平均每次消耗81.77 纳秒。b.N默认是很大的一个数,你也可以自定义迭代次数。一般是不要去动b.N

基准测试还有很多知识点,包括内存消耗的检测、测试代码的覆盖率等。一般来说,作为开发人员仅仅测试一下迭代时间即可。若想了解更多,自行查阅资料。

示例函数

在单元测试中,示例函数(Example Functions)是一种特殊的函数,用于提供对被测试函数的示例使用方法、参数和期望输出的演示。

func ExampleFunction() {
	result := YourFunction(input)
	fmt.Println(result)
	// Output: expected_output
}

闲的没事的人,才去写示例函数。有这时间,还不如去泡杯茶····

实战

对aes加密解密的aes.go文件进行单元测试


import (
   "bytes"
   "crypto/aes"
   "crypto/cipher"
   "encoding/base64"
)

// AesEncrypt AES加密
func AesEncrypt(orig string, key string) string {
   // 转换成字节数组
   origData := []byte(orig)
   k := []byte(key)
   // 分组秘钥
   block, _ := aes.NewCipher(k)
   // 获取秘钥块的长度
   blockSize := block.BlockSize()
   // 补全码
   origData = PKCS7Padding(origData, blockSize)
   // 加密模式
   blockMode := cipher.NewCBCEncrypter(block, k[:blockSize])
   // 创建数组
   cryted := make([]byte, len(origData))
   // 加密
   blockMode.CryptBlocks(cryted, origData)

   return base64.StdEncoding.EncodeToString(cryted)
}

// AesDecrypt AES解密
func AesDecrypt(cryted string, key string) string {
   // 转成字节数组
   crytedByte, _ := base64.StdEncoding.DecodeString(cryted)
   k := []byte(key)

   // 分组秘钥
   block, _ := aes.NewCipher(k)
   // 获取秘钥块的长度
   blockSize := block.BlockSize()
   // 加密模式
   blockMode := cipher.NewCBCDecrypter(block, k[:blockSize])
   // 创建数组
   orig := make([]byte, len(crytedByte))
   // 解密
   blockMode.CryptBlocks(orig, crytedByte)
   // 去补全码
   orig = PKCS7UnPadding(orig)
   return string(orig)
}

// PKCS7Padding 补码
func PKCS7Padding(ciphertext []byte, blocksize int) []byte {
   padding := blocksize - len(ciphertext)%blocksize
   padtext := bytes.Repeat([]byte{byte(padding)}, padding)
   return append(ciphertext, padtext...)
}

// PKCS7UnPadding 去码
func PKCS7UnPadding(origData []byte) []byte {
   length := len(origData)
   unpadding := int(origData[length-1])
   return origData[:(length - unpadding)]
}
import (
   "reflect"
   "testing"
)
/*----------------------单元测试----------------------------------------*/
const key = "q+#@,6%ej-QP^mbc"

func TestAesEncrypt(t *testing.T) {
   type test struct {
      org  string
      key  string
      want string
   }
   es := map[string]test{
      "passed": {org: "basic", key: key, want: "2+1s9cqk1NzIUnq+G3VgEg=="}, //passed
   }
   for _, value := range es {
      got := AesEncrypt(value.org, value.key)
      if !reflect.DeepEqual(got, value.want) {
         t.Errorf("the name== %s got %v, want %v", t.Name(), got, value.want)
      }
   }
}

/*
API server listening at: 127.0.0.1:54671
WARNING: undefined behavior - version of Delve is too old for Go version 1.19.0 (maximum supported version 1.18)
=== RUN   TestAesEncrypt
--- PASS: TestAesEncrypt (0.00s)
PASS
*/

func BenchmarkAesEncrypt(b *testing.B) {
   for i := 0; i < b.N; i++ {
      AesEncrypt("test", key)
   }
}
/*pkg: sspaas.io/gpu-manage/utils/aes
cpu: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz
BenchmarkAesEncrypt
BenchmarkAesEncrypt-8            1000000              1031 ns/op
PASS
*/

func TestAesDecrypt(t *testing.T) {
   type test struct {
      org  string
      key  string
      want string
   }
   es := map[string]test{
      "passed": {org: "2+1s9cqk1NzIUnq+G3VgEg==", key: key, want: "basic"}, //passed
   }
   for _, value := range es {
      got := AesDecrypt(value.org, value.key)
      if !reflect.DeepEqual(got, value.want) {
         t.Errorf("the name== %s got %v, want %v", t.Name(), got, value.want)
      }
   }
}
/*API server listening at: 127.0.0.1:54693
WARNING: undefined behavior - version of Delve is too old for Go version 1.19.0 (maximum supported version 1.18)
=== RUN   TestAesDecrypt
--- PASS: TestAesDecrypt (0.00s)
PASS
*/

func BenchmarkAesDecrypt(b *testing.B) {
   for i := 0; i < b.N; i++ {
      AesDecrypt("2+1s9cqk1NzIUnq+G3VgEg==", key)
   }
}
/*pkg: sspaas.io/gpu-manage/utils/aes
cpu: Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz
BenchmarkAesDecrypt
BenchmarkAesDecrypt-8            1334907               883.0 ns/op
PASS
*/

总结

golang基于单元测试的教程常用的技巧也就这些了。制作不容易,给个赞吧。

行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 759968159,里面有各种测试开发资料和技术可以一起交流哦。

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

​​​软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

在这里插入图片描述

在这里插入图片描述

  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值