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

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

    参考

  • 相关阅读:
    团队项目-第一阶段冲刺7
    团队项目-第一阶段冲刺6
    Spring Boot 揭秘与实战(七) 实用技术篇
    Spring Boot 揭秘与实战(七) 实用技术篇
    Spring Boot 揭秘与实战(六) 消息队列篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(四) 配置文件篇
  • 原文地址:https://www.cnblogs.com/sss4/p/12859027.html
Copyright © 2011-2022 走看看