Go函数

1. 第五章 Go函数定义

Go函数是指:一段具有独立功能的代码,然后可以在程序中其他地方多次调用。

Go分为自定义函数,系统函数。

函数可以将一个大的工作拆解成小的任务。

函数对用户隐藏了细节。

Golang函数特点:

支持不定长参数
支持多返回值
支持命名返回参数
支持匿名函数、闭包
函数也是类型,可以赋值给变量

一个package下不得有两个同名函数,不支持函数重载

函数参数可以没有,或者多个参数
注意类型在变量名后面
多个连续的函数命名参数是同一类型,除了最后一个类型,其余可以省略
函数可以返回任意数量的返回值
函数体中,形参作为局部变量
函数返回值可以用 _标识符进行忽略

main()函数由编译器调用,其他函数手动调用

Go函数基本语法:

1)形参:函数的输入参数

2)执行代码:实现函数功能的代码块

3)函数的返回值可有可无

func 函数名(形参列表)(返回值列表){
    执行代码
    return 返回值列表
}

func test(x, y int, z string) (int, string) {
    //类型相同的相邻参数x,y参数类型可以合并
    //多返回值得用括号括起来
    n := x + y
    return n, z
}

1.1. 函数实战

package main

import "fmt"

//最普通的函数,无参数,无返回值
func sayHello() {
    fmt.Printf("hello world\n")
}

//求和函数add
func add(a, b, c int) int {
    //sum := a + b + c
    //return sum
    return a + b
}

//接收2个参数a 和b都是int类型
//返回2个参数,sum和sub作为返回值,也叫做对返回值命名
func calc(a, b int) (sum int, sub int) {
    sum = a + b
    sub = a - b
    return
}

//接收不定长参数个数,
//参数名是b,类型是不固定个数的int类型
//变量b此时是一个slice切片,数据类型:[]int,可以通过索引取值
func calc_v1(b ...int) int {
    sum := 0
    for i := 0; i < len(b); i++ {
        sum = sum + b[i]
    }
    return sum
}

func main() {
    //调用函数
    sayHello()
    //打印返回值求和结果
    fmt.Println(add(5, 5, 5))

    //多个返回值
    sum1, sub1 := calc(5, 10)
    fmt.Printf("calc计算和是%d\n", sum1)
    fmt.Printf("calc计算差是%d\n", sub1)

    //传入不固定长度的参数个数
    sum := calc_v1(10, 20, 30, 40)
    fmt.Println(sum)

}

1.2. Go函数注意事项

1)基本数据类型和数组默认值传递,有一个值拷贝过程,不会修改原本变量的值

package main

import "fmt"

func modify(n int) {
    n = n + 100
    fmt.Println("modify函数修改后n=", n)
}

func main() {
    num := 10
    modify(num)
    fmt.Println("此时main主程中的nun值=", num)
}

2)如果希望函数可以修改函数外的变量,需要以指针传递,传入变量的地址,函数内以指针方式操作变量。

package main

import "fmt"

//指针变量,接收一个地址
func modify2(n *int) {
    *n = *n + 100
    fmt.Println("modify2修改后n的值=", *n)
}

func main() {
    num2 := 10
    modify2(&num2)
    fmt.Println("此时main主程中的num2值=", num2)
}

1.3. init函数

每个源文件都会包含一个inti函数,该函数在main函数之前被执行。

package main

import "fmt"

func init() {
    fmt.Println("init函数一般完成初始化工作,如数据库驱动连接等")
}

func main() {
    fmt.Println("我是主程序")
}

1.4. init函数细节

go程序加载流程:

全局变量
↓
init函数
↓
main函数
package main

import "fmt"

//全局变量
var num = test()

func test() int {
    fmt.Println("执行了test()函数")
    return 999
}

func init() {
    fmt.Println("执行了init函数")
}

func main() {
    fmt.Println("我是主程序")
    fmt.Println(num)
}

面试题:

如果再包导入中,main.go和utils.go都有变量加载,init函数,执行流程是?

main.go是程序入口
↓
自上而下,进入import导入
↓
优先进入utils.go 加载全局变量  这是第一步
↓
执行utils.go的init函数        第二步
↓
完毕后,回到main.go的全局变量    第三步
↓
执行main.go的init函数        第四步
↓
执行main.go的主程main()函数    第五步






1. 5.2 Go 包与函数

在多个包中相互调用函数,需要用到Go包的知识。

代码组织如下:

思路:

1.定义功能函数calc放入到utils.go,将utils.go放在utils文件夹/包中,当其他文件需要引入utils.go时,只需要导入该utils包,即可使用(包名.函数名)

代码

main.go

package main

import (
    "fmt"
    "gostudy/gobook/funcDemo/utils"
)

//两种方式二选一
//相对路径导入
//import "../utils"

//绝对路径导入,从src目录下开始

func main() {
    //通过utils包访问公开函数Calc
    res := utils.Calc(10, 20)
    fmt.Println(res)
}

utils.go

package utils

//写一个可导出的函数,需要首字母大写
//给返回值命名n3
func Calc(n1, n2 int) (n3 int) {
    res := n1 + n2
    return res
}

包的import方式,详见章节2.4

1.1. 编译可执行程序

对上述代码编译,需要包声明为main,也就是package main,这是语法规范。

go build main.go








1. 5.3 Go 匿名函数

Go支持匿名函数,顾名思义就是没名字的函数。

匿名函数一般用在,函数只运行一次,也可以多次调用。

匿名函数可以像普通变量一样被调用。

匿名函数由不带函数名字的函数声明函数体组成。

package main

import "fmt"

func main() {
    //定义匿名函数,接收2个参数n1,n2,返回值int
    res := func(n1, n2 int) int {
        return n1 * n2
    }(10, 20) //匿名函数在此处调用,传参
    fmt.Println("res=", res)
}

匿名函数赋值给变量

局部变量

package main

import "fmt"

func main() {
//局部变量n1
    n1 := func(a, b int) int {
        return a * b
    }
    fmt.Printf("n1的类型:%T\n", n1)
    res := n1(10, 10)
    fmt.Println("res调用结果:", res)
}

全局变量

package main

import "fmt"
//f1就是全局匿名函数
var (
    f1 = func(n1, n2 int) int {
        return n1 * n2
    }
)

func test() int {
    return f1(10, 10)
}
func main() {
    res := f1(20, 20)
    fmt.Printf("res结果:%d\n", res)

    res2 := test()
    fmt.Printf("res2结果:%d\n", res2)
}
 

1. 5.4 Go 闭包

闭包(closure):是由一个函数和其相关的引用环境组合的一个整体。(闭包=函数+引用环境)

package main

import (
    "fmt"
)

//是由一个函数和其相关的引用环境组合的一个整体。(闭包=函数+引用环境)
//函数addUpper返回值是个函数
//返回值是匿名函数 func(int)int
func test() func(int) int {
    //定义一个局部变量
    n := 10
    //返回一个匿名函数
    return func(x int) int {
        n += x
        return n
    }

}

/*
addUpper函数返回了一个匿名函数,这个匿名函数又引用了函数外的变量n,因此匿名函数+n组成了一个整体,形成闭包
当调用f函数时,n仅仅被初始化一次,因此每次调用形成累计

*/

func main() {
    //调用addUpper函数,获取返回值
    f := test()
    //此时f是匿名函数,对其传参调用
    fmt.Println(f(50)) //10+50=60
    fmt.Println(f(20)) //60+20=80
    fmt.Println(f(20)) //80+20=100 同一个f对象,保留了n的值

    f1 := test()
    fmt.Println(f1(10))
}

闭包代码修改

package main

import (
    "fmt"
)

//是由一个函数和其相关的引用环境组合的一个整体。(闭包=函数+引用环境)
//函数addUpper返回值是个函数
//返回值是匿名函数 func(int)int
func test() func(int) int {
    //定义一个局部变量
    n := 10
    var str = "oldboy"
    //返回一个匿名函数
    return func(x int) int {
        n += x
        str += string(36) //36对应的
        fmt.Println("此时str值:", str)
        return n
    }

}

/*
addUpper函数返回了一个匿名函数,这个匿名函数又引用了函数外的变量n,因此匿名函数+n组成了一个整体,形成闭包
当调用f函数时,n仅仅被初始化一次,因此每次调用形成累计

*/

func main() {
    //调用addUpper函数,获取返回值
    f := test()
    //此时f是匿名函数,对其传参调用
    fmt.Println(f(50)) //10+50=60
    fmt.Println(f(20)) //60+20=80
    fmt.Println(f(20)) //80+20=100 同一个f对象,保留了n的值

    //新的初始化
    f1 := test()
    fmt.Println(f1(10))
}

2. 闭包实战

package main

import (
    "fmt"
    "strings"
)

/*
1.makeSuffixFunc函数接收一个文件名后缀,如.png,且返回闭包
2.调用闭包,传入文件名前缀,如果没有后缀就添加后缀,返回 文件名.png
3.strings.HasSuffix可以判断指定字符串后缀
*/

func makeSuffixFunc(suffix string) func(string) string {
    //返回值闭包函数
    return func(filename string) string {
        //如果没有xx后缀,执行代码
        if !strings.HasSuffix(filename, suffix) {
            //则字符串拼接
            return filename + suffix
        }
        //否则有后缀名,则直接返回新文件名
        return filename
    }
}

func main() {
    //f1返回的是闭包函数,对此闭包函数进行功能性使用
    f1 := makeSuffixFunc(".png")

    fmt.Println(f1("苍老师"))  //没有后缀
    fmt.Println("小泽老师.png") //有后缀
}

总结:

1.makeSuffixFunc函数中的变量suffix和返回值匿名函数,组合成了一个闭包
2.由于闭包函数保留了上次引用的值suffix,只需要传入一次,即可反复使用

2.1. 函数式编程

支持将函数作为另一个函数的参数,叫回调函数。

支持将函数作为另一个函数的返回值。

package main

import "fmt"

//闭包函数
//函数体内有局部变量

func adder() func(int) int {
    sum := 0
    //return 的是一个闭包
    return func(v int) int {
        //引用自由变量,sum
        sum += v
        return sum
    }
}

//递归定义
type iAdder func(int) (int, iAdder)

//函数式编程写法,函数+常量
func adder2(base int) iAdder {
    return func(v int) (int, iAdder) {
        return base + v, adder2(base + v)
    }
}

func main() {
    // a := adder() is trivial and also works.
    a := adder2(0)
    for i := 0; i < 10; i++ {
        var s int
        s, a = a(i)
        fmt.Printf("0 + 1 + ... + %d = %d\n",
            i, s)
    }
}

go闭包-斐波那契数列

package main

import "fmt"

// 1,1,2,3,5,8,13,21,34,55...
//
func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }

}

func main() {
    f := fibonacci()
    //斐波那契数列
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())

}

2.2. python中闭包

def adder():
    sum = 0
    def f(value):
        nonlocal sum
        sum +=value 
        return sum 
    return f




1. 5.5 Go defer

程序开发中经常要创建资源(数据库初始化连接,文件句柄,锁等),在程序执行完毕都必须得释放资源,Go提供了defer(延时机制)更方便、更及时的释放资源。

1.内置关键字defer 用于延迟调用
2.defer在return前执行,常用于资源释放
3.多个defer按    先进后出    的机制执行
4.defer语句的变量,在defer声明时定义

实例

package main

import (
    "fmt"
)

func testDefer1() {
    //defer机制 先入后出,如同羽毛球筒
    defer fmt.Println("hello v1") //顺序5
    defer fmt.Println("hello v2") //顺序4
    defer fmt.Println("hello v3") //顺序3
    fmt.Println("aaaaa")          // 顺序1
    fmt.Println("bbbb")           //顺序2
}

func testDefer2() {

    for i := 0; i < 5; i++ {
        //每次循环,defer将后入的语句压到defer栈
        //依旧先入后出
        defer fmt.Printf("i=%d\n", i)
    }

    fmt.Printf("running\n") //顺序1
    fmt.Printf("return\n")  //顺序2
}

func testDefer3() {
    var i int = 0
    //defer是声明时定义好的,之后再修改无效,因此i=0
    defer fmt.Printf("defer i=%d\n", i)
    i = 1000
    fmt.Printf("i=%d\n", i)
}

func main() {
    //testDefer1()
    //testDefer2()
    testDefer3()
}
 
 

1. 5.6 Go 常用函数

最正确的学习模块姿势:

https://golang.org/pkg/        //golang官网

程序开发常用函数

strings处理字符串相关

统计字符串长度,按字节                   len(str)
字符串遍历,处理中文                    r:=[]rune(str)
字符串转整数                           n, err := strconv.Atoi("12")
整数转字符串                           str = strconv.Itoa(12345)
字符串 转 []byte                   var bytes = []byte("hello go")
[]byte 转 字符串                   str = string([]byte{97, 98, 99})
10 进制转 2, 8, 16 进制:              str = strconv.FormatInt(123, 2) // 2-> 8 , 16
查找子串是否在指定的字符串中             strings.Contains("seafood", "foo") //true
统计一个字符串有几个指定的子串         strings.Count("ceheese", "e") //4
不区分大小写的字符串比较(==是区分字母大小写的)  fmt.Println(strings.EqualFold("abc", "Abc")) // true
返回子串在字符串第一次出现的 index 值,如果没有返回-1      strings.Index("NLT_abc", "abc") // 4
返回子串在字符串最后一次出现的 index,如没有返回-1         strings.LastIndex("go golang", "go")
将指定的子串替换成 另外一个子串 strings.Replace("go go hello", "go", "go 语言", n) ,n 可以指 定你希望替换几个,如果 n=-1 表示全部替换
按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组  strings.Split("hello,wrold,ok", ",")
将字符串的字母进行大小写的转换: strings.ToLower("Go") // go strings.ToUpper("Go") // GO
将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")
将字符串左右两边指定的字符去掉 : strings.Trim("! hello! ", " !") 
将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! ", " !")
将字符串右边指定的字符去掉 :strings.TrimRight("! hello! ", " !")
判断字符串是否以指定的字符串开头: strings.HasPrefix("ftp://192.168.10.1", "ftp") 
判断字符串是否以指定的字符串结束: strings.HasSuffix("NLT_abc.jpg", "abc") //false
package main

func main() {
    //1.统计字符串长度
    //str:="sdffsfdsf";
    //fmt.Println(len(str));
    //2.字符串变量同时出现中文的
    //name:="你好水电费第三方是dsfs";
    //slice:=[]rune(name);//使用rune切片转换为字节切片    //3.字符串转化为整数
    //num:="12321";
    //nums,_:=strconv.Atoi(num);
    //fmt.Println(nums);
    //4.整数转化为字符串返回值一个数字
    //num:=12312;
    //info:=strconv.Itoa(num);
    //fmt.Println(info);
    //5.字符串转化为字节切片因为是字节所以字节切片里面是整数
    //str:="sdfdsfsd";
    //str_slice := []byte(str);
    //for _,val:=range str_slice{
    //    fmt.Println(val);
    //}
    //6.字节切片转换为字符串
    //nums:=string([]byte{97,98,99});
    //fmt.Println(nums);
    //7.查找子传是否在父字符串里面返回true|false
    //str_Children :="aa";
    //str_Parent:="aabbcc";
    //bol:=strings.Contains(str_Parent,str_Children);
    //8.统计一个子字符串在父字符串里面出现的次数
    //str:="e";
    //strparent:="adfdsesdfsee";
    //nums:=strings.Count(strparent,str);
    //fmt.Println(nums);
    //9.比较字符串不区分大小写
    //str1:="ABC";
    //str:="abc";
    //bol:= strings.EqualFold(str1,str);
    //fmt.Println(bol);
    //10.返回子字符串第一次出现的位置index
    //str:="abcefefd";
    //target:="fd";
    //num:=strings.Index(str,target);
    //fmt.Println(num);
    //11.字符串替换replace -1标识全部替换
    //str:="abcedfdsfssfdooo";
    //target:="ooo";
    //replace:="<<<<";
    //new_str:=strings.Replace(str,target,replace,-1);
    //fmt.Println(new_str);
    //12.字符串切割成字节切片
    //str:="sdfjslkdfjdslkfjdslkfds";
    //arr:=strings.Split(str,"");
    //fmt.Println(arr);
    //13.大小写转换
    //str:="sdfsdfdss";
    //target:=strings.ToUpper(str);//转换为大写
    //fmt.Println(target);
    //str:="DJFLKSJFLKDS";
    //target:=strings.ToLower(str);//转为小写
    //fmt.Println(target);
    //14.去除左右两边空格
    //str:="  ssdfdsfssf ";
    //target_str:=strings.TrimSpace(str);
    //fmt.Println(target_str);
    //new_str:=" sdjfkls jfslkjsd fdsf ds !!";//去除制定空格
    //info:=strings.Trim(new_str,"!");
    //fmt.Println(info);
}

 

1.1. 时间日期函数

日期时间相关函数经常用到

package time
//time包提供了时间的显示和测量用的函数,日历计算用的是公历

time包用法

package main

import (
    "fmt"
    "time"
)

func main() {
    //获取当前时间
    now := time.Now()
    fmt.Printf("现在时间:%v\n", now)
    fmt.Printf("现在时间类型%T\n", now)
    //通过now获取年月日 时分秒
    fmt.Printf("现在时间 年=%v 月=%v 日=%v 时=%v 分=%v 秒=%v\n", now.Year(), int(now.Month()), now.Day(), now.Hour(), now.Minute(), now.Second())

    //时间格式化,这个时间固定2006-01-02 15:04:05 必须这么写
    fmt.Printf(now.Format("2006-01-02 15:04:05\n"))

    //Unix时间戳和UnixNano用法
    fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
}

计算程序执行时间

package main

import (
    "fmt"
    "strconv"
    "time"
)

//计算程序运行时间
func test() {
    str := ""
    for i := 0; i < 100000; i++ {
        //将int转为string
        str += "oldboy" + strconv.Itoa(i)
    }
}

func main() {
    //程序开始前的时间戳
    start := time.Now().Unix()
    test()
    //程序结束时的时间戳
    end := time.Now().Unix()
    fmt.Printf("执行test()函数,花费时间%v秒\n", end-start)
}
 
 

1. 5.7 Go 捕获异常

Go语言处理异常不同于其他语言处理异常的方式。

传统语言处理异常:
try 
catch
finally

go语言

引入了defer、panic、recover
1.Go程序抛出一个panic异常,在defer中通过recover捕获异常,然后处理

2. defer与recover捕获异常

package main

import "fmt"

func test() {
    //在函数退出前,执行defer
    //捕捉异常后,程序不会异常退出
    defer func() {
        err := recover() //内置函数,可以捕捉到函数异常
        if err != nil {
            //这里是打印错误,还可以进行报警处理,例如微信,邮箱通知
            fmt.Println("err错误信息:", err)
        }
    }()

    //如果没有异常捕获,直接报错panic,运行时出错
    num1 := 10
    num2 := 0
    res := num1 / num2
    fmt.Println("res结果:", res)

}

func main() {
    test()
    fmt.Println("如果程序没退出,就走我这里")
}

在goroutine中使用recover捕获异常

在goroutine中如果出现了panic,整个程序也会崩溃,因此在goroutine中进行异常捕获,保障程序正常运转。

示例代码

package main

import (
    "fmt"
    "time"
)

func test() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second)
        fmt.Println("你好,大妹子,我是你表哥")
    }
}

func test2() {
    //测试一个异常goroutine
    //使用defer+recover捕获异常
    defer func() {
        //匿名函数来捕获异常
        if err := recover(); err != nil {
            fmt.Println("test2函数出错,", err)
        }
    }()

    //主动模拟异常,对一个未初始化的map赋值,引出panic异常
    var myMap map[int]string
    myMap[0] = "你妹呀"
}

func main() {
    go test()
    go test2()

    time.Sleep(time.Second * 10)
}

2.1.1. Errors包

package main

import (
    "errors"
    "fmt"
)

func checkAge(age int) error {
    if age < 0 {
        err := fmt.Errorf("输入年龄不合法")
        return err
    }
    fmt.Println("年龄是:", age)
    //正常error类型返回nil,代表无错
    return nil
}

func main() {
    //New函数,返回一个结构体对象
    err1 := errors.New("自定义的错误信息,你看我好玩吗")
    fmt.Println(err1.Error()) //调用Error方法
    fmt.Printf("%T\n", err1)  //结构体指针对象

    //创建error另一个方法,其实也是通过erros.New()创建的
    err2 := fmt.Errorf("自定义错误状态码:%d", 500)
    fmt.Printf("%T\n", err2) //结构体指针对象
    fmt.Println(err2.Error())

    //测试error
    err3:=checkAge(-1)
    if err3!=nil{
        fmt.Println(err3.Error())
        return
    }
    fmt.Println("程序正常运行")
}
 

1. 5.8 Go 单元测试

如果你不想后半生的美好时光都在寻找BUG中度过,那么必须写些程序用来检测产品代码的结果和预期的一样。

Go语言的测试依赖于go test测试命令和一组按约定方式编写的测试函数,测试命令可以运行这些测试函数。

Go单元测试对文件名和方法名有严格的要求。

1、文件名必须以xx_test.go命名
2、方法必须是Test[^a-z]开头
3、方法参数必须 t *testing.T
4、使用go test执行单元测试

go test是go自带的测试工具,其中包含单元测试和性能测试

通过go help test可以看到go test的使用说明:

格式形如:
go test [-c] [-i] [build flags] [packages] [flags for test binary]

参数解读:
-c : 编译go test成为可执行的二进制文件,但是不运行测试。

-i : 安装测试包依赖的package,但是不运行测试。

关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空

关于packages,调用go help packages,这些是关于包的管理,一般设置为空

关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数

-test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。

-test.run pattern: 只跑哪些单元测试用例

-test.bench patten: 只跑那些性能测试用例

-test.benchmem : 是否在性能测试的时候输出内存情况

-test.benchtime t : 性能测试运行的时间,默认是1s

-test.cpuprofile cpu.out : 是否输出cpu性能分析文件

-test.memprofile mem.out : 是否输出内存性能分析文件

-test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件

-test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,那就是不做打点了。

你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。

-test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-test.blockprofilerate=1,每一纳秒都打点记录一下

-test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。

-test.timeout t : 如果测试用例运行时间超过t,则抛出panic

-test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理

-test.short : 将那些运行时间较长的测试用例运行时间缩短

目录结构

test
      |
       —— calc.go
      |
       —— calc_test.go

calc.go

package main

func add(a, b int) int {
    return a + b
}

func sub(a, b int) int {
    return a - b
}

calc_test.go

package main

import (
    "testing"
)

func TestAdd(t *testing.T) {
    r := add(2, 4)
    if r != 6 {
        t.Fatalf("add(2, 4) error, expect:%d, actual:%d", 6, r)
    }
    t.Logf("test add succ")
}

输出结果:

cd test/
ls
calc.go        calc_test.go

//-v参数显示通过函数的信息
yugoMBP:test yuchao$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
        calc_test.go:11: test add succ...is ok
PASS
ok      gostudy/gobook/test     0.006s

单元测试文件代码规则:

1.文件名必须是_test.go结尾的,这样在执行go test的时候才会执行到相应的代码
2.你必须import testing这个包
3.所有的测试用例函数必须是Test开头
4.测试用例会按照源代码中写的顺序依次执行
5.测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态
6.测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。
7.函数中通过调用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。

1.1. go 代码覆盖率

获取测试结果文件 c.out

go test -coverprofile=c.out

使用go tool cover读取测试覆盖文件

Usage of 'go tool cover':
Given a coverage profile produced by 'go test':
        go test -coverprofile=c.out

Open a web browser displaying annotated source code:
        go tool cover -html=c.out

Write out an HTML file instead of launching a web browser:
        go tool cover -html=c.out -o coverage.html

Display coverage percentages to stdout for each function:
        go tool cover -func=c.out

Finally, to generate modified source code with coverage annotations
(what go test -cover does):
        go tool cover -mode=set -var=CoverageVariableName program.go

Flags:
  -V    print version and exit
  -func string
        output coverage profile information for each function
  -html string
        generate HTML representation of coverage profile
  -mode string
        coverage mode: set, count, atomic
  -o string
        file for output; default: stdout
  -var string
        name of coverage variable to generate (default "GoCover")

  Only one of -html, -func, or -mode may be set.

用法

go tool cover -html=c.out 
生成一个html页面,查看覆盖率

1.2. go 性能测试 benchmark

 go test -bench .
 go test -bench . -cpuprofile cpu.out

1.3. go pprof

go tool pprof cpu.out

Type: cpu
Time: Mar 21, 2019 at 9:29am (CST)
Duration: 1.94s, Total samples = 1.58s (81.30%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)  web

输入web可以直接在线查看性能分析图


OXS如果报错1:

Failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in $PATH

解决方法

brew install graphviz
官网:graphviz.org

报错2:

OXS默认用sublime或者vscode打开svg文件,我们想要chrome打开

修改svg格式文件,默认打开方式,用chrome即可
posted @ 2019-06-26 15:17  AnthonyWang  阅读(200)  评论(0)    收藏  举报