zoukankan      html  css  js  c++  java
  • Golang单元测试(go test )

    前言

    TDD(Test Driven Development),那么我们如何做到可反复、无遗漏、高效地测试我们自己写的的代码?实现敏捷开发呢?

    这就需要我们自己给自己写的代码写测试用例!

    参考

    本文主要介绍下在Go语言中如何做单元测试、基准测试、非功能测试。

    go test介绍

    想要测试Go代码需要依赖go test命令,go test命令更像1个框架:

    在包目录内所有测试文件必须以_test.go结尾go build不会把这些测试文件编译到最终的可执行文件中

    ps:

    我们执行go test命令时,它会遍历该go包中所有以_test.go结尾的测试文件, 然后调用并执行测试文件中符合go test 规则的函数帮助我们实现自动化测试。

    其过程为生成1个临时的main包用于调用相应的测试函数,然后构建并运行测试文件中的函数、报告测试结果,最后清理测试中生成的临时文件。

    根据测试维度可以把包内以_test.go结尾的测试文件中的函数划分为以下3种:

    类型格式作用
    测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
    基准函数 函数名前缀为Benchmark 测试函数的性能(执行时间、内存申请情况)
    示例函数 函数名前缀为Example 为文档提供示例文档

    单元测试函数(unit testing):是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。

    基准测试函数:测试程序执行时间复杂度、空间复杂度

    示例函数:为调用该功能代码的人提供演示

    以上了解到为了可以使用golang内置的 go test命令实现自动化测试 需要做以下步骤:

    1.在需要测试代码的同一目录下,准备一些以x_test.go结尾的测试文件

    2.执行go test自动发现x_test.go结尾的测试文件并执行其内部符合go test 格式的函数

     

    有go test这个测试工具之后,对于1个golang程序员来说,主要学习如何准备测试文件、测试文件中的测试函数需要遵循哪些格式规范,go test才会帮我去自动调用、执行我写的测试用例!

    单元测试函数

    单元测试函数就是1个针对源码中1个单元(func/class)进行功能测试的函数。

    单元测试函数格式

    1.每个函数必须导入testing包

    import (
    	"reflect"
    	"testing"
    )
    

    2.测试函数格式

    单元测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头

    每个单元测试函数的参数必须为*testing.T,参数t用于报告测试是否失败以及日志信息。

    func TestAdd(t *testing.T){ ... }
    func TestSum(t *testing.T){ ... }
    func TestLog(t *testing.T){ ... }
    

      

    testing.T参数的拥有的方法如下:

    func (c *T) Error(args ...interface{})
    func (c *T) Errorf(format string, args ...interface{})
    func (c *T) Fail()
    func (c *T) FailNow()
    func (c *T) Failed() bool
    func (c *T) Fatal(args ...interface{})
    func (c *T) Fatalf(format string, args ...interface{})
    func (c *T) Log(args ...interface{})
    func (c *T) Logf(format string, args ...interface{})
    func (c *T) Name() string
    func (t *T) Parallel()
    func (t *T) Run(name string, f func(t *T)) bool
    func (c *T) Skip(args ...interface{})
    func (c *T) SkipNow()
    func (c *T) Skipf(format string, args ...interface{})
    func (c *T) Skipped() bool
    

    3.验证测试驱动开发理念

    假设现在开发了1个单元(函数), 该单元的功能是对string类型的变量进行split。

    split.go  (源代码)

    package splitString
    import "strings"
    
    //Newsplit 切割字符串
    //example:
    //abc,b=>[ac]
    func Newsplit(str, sep string) (des []string) {
    	index := strings.Index(str, sep)
    	for index > -1 {
    		sectionBefor := str[:index]
    		des = append(des, sectionBefor)
    		str = str[index+1:]
    		index = strings.Index(str, sep)
    	}
    	//最后1
    	des = append(des, str)
    	return
    }
    

    split_test.go(单元测试代码)

    package splitString
    
    import (
    	"reflect"
    	"testing"
    )
    
    //测试用例1:以字符分割
    func TestSplit(t *testing.T) {
    	got := Newsplit("123N456", "N")
    	want := []string{"123", "456"}
    	//DeepEqual比较底层数组
    	if !reflect.DeepEqual(got, want) {
    		//如果got和want不一致说明你写得代码有问题
    		t.Errorf("The values of %v is not %v
    ", got, want)
    	}
    
    }
    
    //测试用例2:以标点符号分割
    func TestPunctuationSplit(t *testing.T) {
    	got := Newsplit("a:b:c", ":")
    	want := []string{"a", "b", "c"}
    	if !reflect.DeepEqual(got, want) {
    		t.FailNow()//出错就stop别往下测了!
    	}
    
    }
    

      

    It's the truth that the test driven the developmen.

    我在原来测试用例的基础上增加了1个测试用例3

    package splitString
    
    import (
    	"reflect"
    	"testing"
    )
    
    //测试用例1:以字符分割
    func TestSplit(t *testing.T) {
    	got := Newsplit("123N456", "N")
    	want := []string{"123", "456"}
    	//DeepEqual比较底层数组
    	if !reflect.DeepEqual(got, want) {
    		//如果got和want不一致说明你写得代码有问题
    		t.Errorf("The values of %v is not %v
    ", got, want)
    	}
    
    }
    
    //测试用例2:以标点符号分割
    func TestPunctuationSplit(t *testing.T) {
    	got := Newsplit("a:b:c", ":")
    	want := []string{"a", "b", "c"}
    	if !reflect.DeepEqual(got, want) {
    		t.FailNow() //出错就stop别往下测了!
    	}
    
    }
    
    //测试用例3:增加分隔符的长度
    func TestMultipleChartSplit(t *testing.T) {
    	got := Newsplit("hellowbsdjshdworld", "bsdjshd")
    	want := []string{"hellow", "world"}
    	if !reflect.DeepEqual(got, want) {
    		t.Fatalf("无法通过多字符分隔符的测试!got: %v want:%v
    ", got, want) //出错就stop别往下测了!
    	}
    
    }
    

      

    执行go test测试出bug无法 使用多个字符分割字符串

    D:goprojectsrcLearningTestsplitString>go test -v
    === RUN   TestSplit
    --- PASS: TestSplit (0.00s)
    === RUN   TestPunctuationSplit
    --- PASS: TestPunctuationSplit (0.00s)
    === RUN   TestMultipleChartSplit
    --- FAIL: TestMultipleChartSplit (0.00s)
        split_test.go:35: 无法通过多字符分隔符的测试!got: [hellow sdjshdworld] want:[hellow world]
    FAIL
    exit status 1
    FAIL    LearningTest/splitString        0.037s
    

    驱动我继续开发源码

    package splitString
    import "strings"
    
    //Newsplit 切割字符串
    //example:
    //abc,b=>[ac]
    func Newsplit(str, sep string) (des []string) {
    	index := strings.Index(str, sep)
    	for index > -1 {
    		sectionBefor := str[:index]
    		des = append(des, sectionBefor)
    		str = str[index+len(sep):]
    		index = strings.Index(str, sep)
    	}
    	//最后1
    	des = append(des, str)
    	return
    }
    

      

    测试组

    以上的测试模式中我们每写个测试用例就需要再写1个函数,可以继续优化测试代码!

    利用结构体组织测试数据把多个测试用例合到一起,在1个函数内对1组测试用例进行统一测试。

    测试代码

    package splitString
    
    import (
    	"reflect"
    	"testing"
    )
    
    //测试组:在1个函数中写多个测试用例,切支持灵活扩展!
    
    type testCase struct {
    	str      string
    	separate string
    	want     []string
    }
    
    var testGroup = []testCase{
    	//测试用例1:单个英文字母
    	testCase{
    		str:      "123N456",
    		separate: "N",
    		want:     []string{"123", "456"},
    	},
    	//测试用例2:符号
    	testCase{
    		str:      "a:b:c",
    		separate: ":",
    		want:     []string{"a", "b", "c"},
    	},
    	//测试用例3:多个英文字母
    	testCase{
    		str:      "hellowbsdjshdworld",
    		separate: "bsdjshd",
    		want:     []string{"hellow", "world"},
    	},
    	//测试用例4:单个汉字
    	testCase{
    		str:      "山西运煤车煤运西山",
    		separate: "山",
    		want:     []string{"西运煤车煤运西"},
    	},
    
    	//测试用例4:多个汉字
    	testCase{
    		str:      "京北北京之北",
    		separate: "北京",
    		want:     []string{"京北", "之北"},
    	},
    }
    
    func TestSplit(t *testing.T) {
    	for _, test := range testGroup {
    		got := Newsplit(test.str, test.separate)
    		if !reflect.DeepEqual(got, test.want) {
    			t.Fatalf("失败!got:%#v want:%#v
    ", got, test.want)
    		}
    	}
    
    }
    

      

    源码

    测试驱动开发!源码又发现了新的bug!

    package splitString
    
    import "strings"
    
    //Newsplit 切割字符串
    //example:
    //abc,b=>[ac]
    func Newsplit(str, sep string) (des []string) {
    	index := strings.Index(str, sep)
    	for index > -1 {
    		sectionBefor := str[:index]
    		if len(sectionBefor) >= 1 {
    			des = append(des, sectionBefor)
    		}
    		str = str[index+len(sep):]
    		index = strings.Index(str, sep)
    	}
    	//最后1
    	if len(str) >= 1 {
    		des = append(des, str)
    	}
    
    	return
    }
    

      

    测试结果

    D:goprojectsrcLearningTestsplitString>go test -v
    === RUN   TestSplit
    --- PASS: TestSplit (0.00s)
    PASS
    ok      LearningTest/splitString        0.022s
    
    D:goprojectsrcLearningTestsplitString>
    

      

    子测试

    基于测试组对测试代码再次进行优化,利用使用t *testing.T参数的run方法去执行测试用例

    这种方法可以针对测试组里的1个测试用例进行单独测试,所以也叫子测试。

    测试代码

    package splitString
    
    import (
    	"reflect"
    	"testing"
    )
    
    //子测试
    type testCase struct {
    	str      string
    	separate string
    	want     []string
    }
    
    var testGroup = map[string]testCase{
    	"punctuation": testCase{
    		str:      "a:b:c",
    		separate: ":",
    		want:     []string{"a", "b", "c"},
    	},
    	"sigalLetter": testCase{
    		str:      "123N456",
    		separate: "N",
    		want:     []string{"123", "456"},
    	},
    
    	"MultipleLetter": testCase{
    		str:      "hellowbsdjshdworld",
    		separate: "bsdjshd",
    		want:     []string{"hellow", "world"},
    	},
    	"singalRune": testCase{
    		str:      "山西运煤车煤运西山",
    		separate: "山",
    		want:     []string{"西运煤车煤运西"},
    	},
    	"multiplRune": testCase{
    		str:      "京北北京之北",
    		separate: "北京",
    		want:     []string{"京北", "之北"},
    	},
    }
    
    //测试用例函数
    func TestSplit(t *testing.T) {
    	for name, test := range testGroup {
    		//使用t参数的run方法
    		t.Run(name, func(t *testing.T) {
    			got := Newsplit(test.str, test.separate)
    			if !reflect.DeepEqual(got, test.want) {
    				t.Fatalf("失败!got:%#v want:%#v
    ", got, test.want)
    			}
    		})
    	}
    }
    

      

    测试结果

    D:goprojectsrcLearningTestsplitString>go test -v
    === RUN   TestSplit
    === RUN   TestSplit/punctuation
    === RUN   TestSplit/sigalLetter
    === RUN   TestSplit/MultipleLetter
    === RUN   TestSplit/singalRune
    === RUN   TestSplit/multiplRune
    --- PASS: TestSplit (0.00s)
        --- PASS: TestSplit/punctuation (0.00s)
        --- PASS: TestSplit/sigalLetter (0.00s)
        --- PASS: TestSplit/MultipleLetter (0.00s)
        --- PASS: TestSplit/singalRune (0.00s)
        --- PASS: TestSplit/multiplRune (0.00s)
    PASS
    ok      LearningTest/splitString        0.037s
    
    针对某1个测试用例进行单独测试 D:goprojectsrcLearningTestsplitString>go test -run=TestSplit/punctuation PASS ok LearningTest/splitString 0.042s D:goprojectsrcLearningTestsplitString>

    测试覆盖率

    测试覆盖率是你的代码被测试套件覆盖的百分比。

    通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。

    Go提供内置功能来检查你的代码覆盖率。我们可以使用

    go test -cover

    查看测试覆盖率。

    D:goprojectsrcLearningTestsplitString> go test -cover
    PASS
    coverage: 100.0% of statements
    ok      LearningTest/splitString        0.042s
    
    D:goprojectsrcLearningTestsplitString>

    go test -cover -coverprofile=测试报告文件

    把测试覆盖率的详细信息输出到文件

    D:goprojectsrcLearningTestsplitString>go test -cover -coverprofile=test_report.out
    PASS
    coverage: 100.0% of statements
    ok      LearningTest/splitString        0.040s
    

      

    go tool cover -html=测试报告文件

    把测试报告输出到文件,就是为了分析测试结果,go内置的工具支持以HTML的方式打开测试报告文件!

    D:goprojectsrcLearningTestsplitString>go tool cover -html=test_report.out

    上图中每个用绿色标记的语句块表示被覆盖了,而红色的表示没有被覆盖。

    参考

  • 相关阅读:
    动态列 Excel 导出
    Smart Thread Pool (智能线程池)
    Nuget Server 搭建
    hadoop 分布式集群安装
    DRF 基本功能梳理 demo
    docker 相关梳理
    Python 开发面试梳理
    结合 element-ui 对 Vue 相关知识点整理 (router,axios,Vuex )
    VUE 相关工具 vue-cli/webpack/vue-router
    Vue 基础语法相关特性
  • 原文地址:https://www.cnblogs.com/sss4/p/12859027.html
Copyright © 2011-2022 走看看