前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go的代码规范指南-新人必看

Go的代码规范指南-新人必看

原创
作者头像
小诚信驿站
修改2019-10-05 16:56:14
1.8K0
修改2019-10-05 16:56:14
举报
想要坏坏的蓝猫,可以微信表情包搜索:墩墩猫
想要坏坏的蓝猫,可以微信表情包搜索:墩墩猫

项目约定:

环境设置:*nix环境或者Mac环境,安装go语言目录,默认是usr/local/go,如果想要让代码运行,需要将项目放到usr/local/go/src目录下,如果忘记了go的配置目录,可以通过go env查看go的环境变量设置。如果不希望每次使用sudo权限执行,可以通过设置用户目录下创建~/.bashrc文件,增加 用户目录的环境变量设置比如

代码语言:javascript
复制
export GOPATH=~/go的安装目录
source ~/.bashrc 激活配置文件,然后可以通过go env或者echo $GOPATH命令看到变量输出改变了。

如果不想上面那么搞,则可以直接设置多个GOPATH目录。
假设现在本地硬盘上有3个Go代码工程,分别为~/work/go-proj1、~/work2/goproj2和 ~/work3/work4/go-proj3,那么GOPATH可以设置为如下内容:
export GOPATH=~/work/go-proj1:~/work2/goproj2:~/work3/work4/go-proj3
经过这样的设置后,你可以在任意位置对以上的3个工程进行构建。

代码命名:以后缀.go作为开发文件,以xx_test.go作为测试文件。

打印日志:通过fmt包下的Pringtf函数或者log模块的日志功能

代码语言:javascript
复制
1. 命名 
命名规则涉及变量、常量、全局函数、结构、接口、方法等的命名。Go语言从语法层面进 行了以下限定:
任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写 字母开头。 
Go推行驼峰命名法排斥下划线命名法
2、排列
Go的代码左花括号严格要求不能换行
3、代码摆放乱
go fmt xx.go格式化为标准go文件
4、go的远程包导入
import可以导入本地包,也可以导入GitHub上的远程包,比如如下示例
package main
import ( "fmt"
    "github.com/myteam/exp/crc32"
)
然后,在执行go build或者go install之前,只需要加这么一句: go get github.com/myteam/exp/crc32

目录结构:

举个样例:

代码doc文件输出:

代码语言:javascript
复制
版权说明注释、包说明注释、函数说明注释和最后添 加的遗留问题说明,举个例子

$ go doc foo本地输出文档化
  godoc -http=:76 -path="." 浏览器访问http://localhost:76/pkg/foo/输出文档化

Go的单元测试:

Go的单元测试函数分为两类:功能测试函数和性能测试函数

代码语言:javascript
复制
执行功能单元测试非常简单,直接执行go test命令
go test simplematch(上面目录结构图)
性能测试的,增加-test.bench参数
go test–test.bench add.go

代码编程:

变量申明:

代码语言:javascript
复制
关键字var,而类型信息放在变量名之后,示例如下:
var v1 int
var v2 string 
var v3 [10]int // 数组
var v4 []int  // 数组切片
var v5 struct {
   f int 
}
var v6 *int // 指针
var v7 map[string]int // map,key为string类型,value为int类型
var v8 func(a int) int
变量声明语句不需要使用分号作为结束符。
省略多个重复var的申明:
var (
  v1 int
  v2 string
)

变量初始化:

代码语言:javascript
复制
var v1 int = 10 // 正确的使用方式1
var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型 
v3 := 10 // 正确的使用方式3,编译器可以自动推导出v3的类型

变量赋值:

代码语言:javascript
复制
在Go语法中,变量初始化和变量赋值是两个不同的概念
var v10 int 
v10 = 123

返回值的处理:

代码语言:go
复制
func GetName() (firstName, lastName, nickName string) { 
return "May", "Chan", "Chibi Maruko"
} 
若只想获得nickName,则函数调用语句可以用如下方式编写:
_, _, nickName := GetName()

常量:

在Go语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、

浮点型和复数类型)、布尔类型、字符串类型等

常量定义:

代码语言:javascript
复制

const a, b, c = 3, 4, "foo" //a=3,b=4,c="foo", 无类型整型和字符串常量
const zero = 0.0 
const (
size int64 = 1024 
eof = -1
// 无类型浮点常量 // 无类型整型常量
)
const Pi float64 = 3.14159265358979323846
const u, v float32 = 0, 3

预定义常量:

Go语言预定义了这些常量:true、false和iota。

枚举:

代码语言:javascript
复制
const (
        Sunday = iota
        Monday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
        numberOfDays// 这个常量没有导出,因为大写字母外包可见,小写字母属于私有
        )

数组切片:

代码语言:javascript
复制
创建一个初始元素个数为5的数组切片,元素初始值为0:
mySlice1 := make([]int, 5)
创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:
mySlice2 := make([]int, 5, 10)
直接创建并初始化包含5个元素的数组切片:
mySlice3 := []int{1, 2, 3, 4, 5}

元素遍历:

代码语言:javascript
复制
传统的元素遍历方法如下:
for i := 0; i <len(mySlice); i++ { fmt.Println("mySlice[", i, "] =", mySlice[i])
}
使用range关键字可以让遍历代码显得更整洁。range表达式有两个返回值,第一个是索引,
第二个是元素的值:
for i, v := range mySlice { fmt.Println("mySlice[", i, "] =", v)
}

数组切片的内容复制:

代码语言:javascript
复制
slice1 := []int{1, 2, 3, 4, 5} 
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 copy(slice1, slice2) 
// 只会复制slice2的3个元素到slice1的前3个位置

map的使用:

代码语言:javascript
复制
申明:var myMap map[string] PersonInfo
创建:myMap = make(map[string] PersonInfo)
性能优化-指定分配存储空间:myMap = make(map[string] PersonInfo, 100)
元素的赋值:
myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."}
元素的删除:
delete(myMap, "1234")
元素的查找:
value, ok := myMap["1234"] 
if ok {// 找到了
// 处理找到的value 
}

流程控制的使用:

代码语言:javascript
复制
if a < 5 { 
  return 0
} else { 
  return 1
}
 条件语句不需要使用括号将条件包含起来();
 无论语句体内有几条语句,花括号{}都是必须存在的;
 左花括号{必须与if或者else处于同一行;
 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
 在有返回值的函数中,不允许将“最终的”return语句包含在if...else...结构中,
否则会编译失败:
switch i { 
  case 0:
    fmt.Printf("0") 
  case 1:
    fmt.Printf("1") 
  case 2:
    fallthrough 
  case 3:
    fmt.Printf("3") 
  case 4, 5, 6:
    fmt.Printf("4, 5, 6") 
  default:
    fmt.Printf("Default")
}
在使用switch结构时,我们需要注意以下几点:
 左花括号{必须与switch处于同一行;
 条件表达式不限制为常量或者整数;
 单个case中,可以出现多个结果选项;
 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case; 
可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个
if...else...的逻辑作用等同
sum := 0
for i := 0; i < 10; i++ {
 sum += i 
}
在条件表达式中也支持多重赋值,如下所示:
a := []int{1, 2, 3, 4, 5, 6}
for i, j := 0, len(a) – 1; i < j; i, j = i + 1, j – 1 {
    a[i], a[j] = a[j], a[i]
}
使用循环语句时,需要注意的有以下几点。
 左花括号{必须与for处于同一行。
 Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别
是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多
个变量。
 Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更高级的
 break,可以选择中断哪一个循环
goto语句的语义非常简单,就是跳转到本函数内的某个标签,如:
func myfunc() { 
    i := 0
    HERE:
    fmt.Println(i)
    i++
    if i < 10 {
      goto HERE 
     }
}

函数的使用:

代码语言:javascript
复制
1. 不定参数类型
不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受 不定参数类型:
  func myfunc(args ...int) {
for _, arg := range args {
        fmt.Println(arg)
    }
}
如下方式调用:
    myfunc(2, 3, 4)
    myfunc(1, 3, 7, 13)
2. 不定参数的传递
假设有另一个变参函数叫做myfunc3(args ...int),下面的例子演示了如何向其传递变参:
func myfunc(args ...int) { // 按原样传递
        myfunc3(args...)
// 传递片段,实际上任意的int slice都可以传进去
        myfunc3(args[1:]...)
    }
3. 任意类型的不定参数
之前的例子中将不定参数类型约束为int,如果你希望传任意类型,可以指定类型为 interface{}。下面是Go语言标准库中fmt.Printf()的函数原型:
func Printf(format string, args ...interface{}) { // ...
}

并发的使用:

在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用 的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个 返回值会被丢弃

“不要通过共享内存来通信,而应该通过通信来共享内存。”

代码语言:javascript
复制
一般channel的声明形式为:
var chanName chan ElementType
与一般的变量声明不同的地方仅仅是在类型之前加了chan关键字。ElementType指定这个 channel所能传递的元素类型。举个例子,我们声明一个传递类型为int的channel:
 var ch chan int
 或者,我们声明一个map,元素是bool型的channel:
var m map[string] chan bool
定义一个channel也很简单,直接使用内置的函数make()即可: ch := make(chan int)
在channel的用法中,最常见的包括写入和读出。将一个数据写入(发送)至channel的语法
很直观,如下:
ch <- value
 向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。
 从 channel中读取数据的语法是
value := <-ch
 如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel 中被写入数据为止。
 我们之后还会提到如何控制channel只接受写或者只允许读取,即单向 channel。
代码语言:javascript
复制
Go语言直接在语言级别支持select关键字,用于处理异步IO 问题。
select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由 case语句来描述。
与switch语句可以选择任何可使用相等比较的条件相比,select有比较多的 限制,
其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句 
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句 d
efault:
// 如果上面都没有成功,则进入default处理流程 
}
比如如下的死循环例子:
ch := make(chan int, 1) for {
select {
case ch <- 0:
case ch <- 1: }
i := <-ch
        fmt.Println("Value received:", i)
    }

增加buffer的channel

代码语言:javascript
复制
 c := make(chan int, 1024) 
在调用make()时将缓冲区大小作为第二个参数传入即可 
for i := range c { fmt.Println("Received:", i)
}
利用超时机制,防止死锁问题
// 首先,我们实现并执行一个匿名的超时等待函数 
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // 等待1秒钟
timeout <- true }()
// 然后我们把timeout这个channel利用起来 select {
case <-ch:
// 从ch中读取到数据
case <-timeout:
// 一直没有从ch中读取到数据,但从timeout中读取到了数据
   }
代码语言:javascript
复制
单向channel变量的声明非常简单,如下:
var ch1 chan int // ch1是一个正常的channel,不是单向的 
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int // ch3是单向channel,只用于读取int数据
ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一个单向的读取
ch6 := chan<- int(ch4) // ch6 是一个单向的写入channel
代码语言:javascript
复制
关闭channel非常简单,直接使用Go语言内置的close()函数即可:
 close(ch)
 判断是否关闭
 x, ok := <-ch

多核并行化计算任务

代码语言:javascript
复制
type Vector []float64
// 分配给每个CPU的计算任务
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
 for ; i < n; i++ {
v[i] += u.Op(v[i])
}
c <- 1
// 发信号告诉任务管理者我已经计算完成了
}
const NCPU = 16
func (v Vector) DoAll(u Vector) {
c := make(chan int, NCPU) // 用于接收每个CPU的任务完成信号
for i := 0; i < NCPU; i++ {
go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c)
}
// 等待所有CPU的任务完成
for i := 0; i < NCPU; i++ {
<-c // 获取到一个数据,表示一个CPU计算完成了 }
// 到这里表示所有计算已经结束 }

Go语言如果不支持多核默认的话,可以手动设置
代码中启动goroutine之前先调用以下这个语句以设置使用16个CPU 核心:
runtime.GOMAXPROCS(16)
代码语言:javascript
复制
对于从全局的角度只需要运行一次的代码,比如全局初始化操作,Go语言提供了一个Once
类型来保证全局的唯一性操作,具体代码如下:
var a string
var once sync.Once
func setup() {
a = "hello, world"
}
func doprint() { once.Do(setup)
print(a) }
func twoprint() { go doprint() go doprint()
}

网络编程

代码语言:javascript
复制
socket编程:
(1) 建立Socket:使用socket()函数。
(2) 绑定Socket:使用bind()函数。
(3) 监听:使用listen()函数。或者连接:使用connect()函数。
(4) 接受连接:使用accept()函数。
(5) 接收:使用receive()函数。或者发送:使用send()函数
Go的网络编程:
Go语言标准库对此过程进行了抽象和封装。无论我们期望使用什么协议建立什么形式的连
接,都只需要调用net.Dial()即可
TCP链接:
    conn, err := net.Dial("tcp", "192.168.0.10:2100")
UDP链接:
conn, err := net.Dial("udp", "192.168.0.12:975")
ICMP链接(使用协议名称):
conn, err := net.Dial("ip4:icmp", "www.baidu.com")
ICMP链接(使用协议编号):
conn, err := net.Dial("ip4:1", "10.0.0.3")
目前,Dial()函数支持如下几种网络协议:"tcp"、"tcp4"(仅限IPv4)、"tcp6"(仅限 IPv6)、"udp"、"udp4"(仅限IPv4)、"udp6"(仅限IPv6)、"ip"、"ip4"(仅限IPv4)和"ip6"
(仅限IPv6)


HTTP编程
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络 协议,
定义了客户端和服务端之间请求与响应的传输标准
基本方法
net/http包的Client类型提供了如下几个方法,让我们可以用最简洁的方式实现 HTTP 请求:
func (c *Client) Get(url string) (r *Response, err error)
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err
error)
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) func (c *Client) Head(url string) (r *Response, err error)
func (c *Client) Do(req *Request) (resp *Response, err error)


RPC编程
RPC 采用客户端—服务器(Client/Server)的工作模式。请求程序就是一个客户端(Client), 
而服务提供程序就是一个服务器(Server)。当执行一个远程过程调用时,客户端程序首先发送一 
个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到 
客户端的调用信息到达为止。当一个调用信息到达时,服务端获得进程参数,计算出结果,
并向 客户端发送应答信息,然后等待下一个调用。最后,客户端接收来自服务端的应答信息,
获得进 程结果,然后调用执行并继续进行
一个对象中只有满足如下这些条件的方法,才能被 RPC 服务端设置为可供远程访问:
 必须是在对象外部可公开调用的方法(首字母大写);
 必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go内建支持的类
型;
 第二个参数必须是一个指针;
 方法必须返回一个error类型的值。
以上4个条件,可以简单地用如下一行代码表示:
func (t *T) MethodName(argType T1, replyType *T2) error

JSON处理
使用json.Marshal()函数可以对一组数据进行JSON格式的编码。json.Marshal()函数 的声明如下:
func Marshal(v interface{}) ([]byte, error)
假如有如下一个Book类型的结构体:
type Book struct { 
Title string
Authors []string 
Publisher string 
IsPublished bool 
Price float
}
并且有如下一个 Book 类型的实例对象:
gobook := Book{ "Go语言编程",
["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"],
"ituring.com.cn", true,
9.99
}
然后,我们可以使用 json.Marshal() 函数将gobook实例生成一段JSON格式的文本:
    b, err := json.Marshal(gobook)
如果编码成功,err 将赋于零值 nil,变量b 将会是一个进行JSON格式化之后的[]byte 类型:
b == []byte(`{
"Title": "Go语言编程",
"Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
             "XuDaoli"],
         "Publisher": "ituring.com.cn",
         "IsPublished": true,
         "Price": 9.99
}`)

在Go中,JSON转化前后的数据类型映射如下。
 布尔值转化为JSON后还是布尔类型。
 浮点数和整型会被转化为JSON里边的常规数字。
 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<将会被转义为
 \u003c。
 数组和切片会转化为JSON里边的数组,但[]byte类型的值将会被转化为 Base64 编码后
的字符串,slice类型的零值会被转化为 null。
 结构体会转化为JSON对象,并且只有结构体里边以大写字母开头的可被导出的字段才会
被转化输出,而这些可导出的字段会作为JSON对象的字符串索引。 3 转化一个map类型的数据结构时,该数据的类型必须是 map[string]T(T可以是
encoding/json 包支持的任意数据类型)。

可以使用json.Unmarshal()函数将JSON格式的文本解码为Go里边预期的数据结构。 json.Unmarshal()函数的原型如下:
   func Unmarshal(data []byte, v interface{}) error
   
在解码JSON 数据的过程中,JSON数据里边的元素类型将做如下转换:
 JSON中的布尔值将会转换为Go中的bool类型;  数值会被转换为Go中的float64类型;
 字符串转换后还是string类型;
 JSON数组会转换为[]interface{}类型;
 JSON对象会转换为map[string]interface{}类型;  null值会转换为nil。
在Go的标准库encoding/json包中,允许使用map[string]interface{}和[]interface{} 类型的值来分别存放未知结构的JSON对象或数组

安全编程:

代码语言:javascript
复制
常见的单密钥加密算法有DES、AES、 RC4等。
私钥和公钥都可以被用作加密或者解密,但是用私 钥加密的明文,必须要用对应的公钥解密,用公钥加密的明文,必须用对应的私钥解密。
常见的 双密钥加密算法有RSA等。
常见的哈希算法包括MD5、SHA-1等。

证书加密HTTPS
(1) 在浏览器中输入HTTPS协议的网址。
(2) 服务器向浏览器返回证书,浏览器检查该证书的合法性
(3) 验证合法性
(4) 浏览器使用证书中的公钥加密一个随机对称密钥,并将加密后的密钥和使用密钥加密后 的请求URL一起发送到服务器
(5) 服务器用私钥解密随机对称密钥,并用获取的密钥解密加密的请求URL
(6) 服务器把用户请求的网页用密钥加密,并返回给用户
(7) 用户浏览器用密钥解密服务器发来的网页数据,并将其显示出来
SSL协议由两层组成,上层协议包括SSL握手协议、更改密码规格协议、警报协议,下层协 议包括SSL记录协议。
SSL握手协议建立在SSL记录协议之上,在实际的数据传输开始前,用于在客户与服务器之 间进行“握手”。“握手”是一个协商过程。这个协议使得客户和服务器能够互相鉴别身份,协商 加密算法。在任何数据传输之前,必须先进行“握手”。
在“握手”完成之后,才能进行SSL记录协议,它的主要功能是为高层协议提供数据封装、 压缩、添加MAC、加密等支持

错误处理策略:

代码语言:javascript
复制
//1、将错误返回给调用者
resp, err := http.Get(url) 
if err != nil{
return nill, err 
}
//2、将构造新的信息返回给调用者
doc, err := html.Parse(resp.Body) 
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
 }
 //3、如果错误发生后,程序无法继续运行,我们就可以采用第三种策略:输出错误信息并结束程序。
 //需 要注意的是,这种策略只应在main中执行。对库函数而言,应仅向上传播错误,除非该错误意味着 
 //程序内部包含不一致性,即遇到了bug,才能在库函数中结束程序。
if err := WaitForServer(url); err != nil { 
 fmt.Fprintf(os.Stderr, "Site is down: %v\n", err) 
 os.Exit(1)
}
//4、第四种策略:有时,我们只需要输出错误信息就足够了,不需要中断程序的运行。我们可以通过 log包提供函数
if err := Ping(); err != nil {
log.Printf("ping failed: %v; networking disabled",err)
}
//或者标准错误流输出错误信息。
if err := Ping(); err != nil {
fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabled\n", err)
}
//5、第五种,也是最后一种策略:我们可以直接忽略掉错误。
dir, err := ioutil.TempDir("", "scratch") if err != nil {
return fmt.Errorf("failed to create temp dir: %v",err) 
}
// ...use temp dir...
os.RemoveAll(dir)
//尽管os.RemoveAll会失败,但上面的例子并没有做错误处理。
//这是因为操作系统会定期的清理临时 目录。正因如此,虽然程序没有处理错误,
//但程序的逻辑不会因此受到影响。我们应该在每次函数 调用后,都养成考虑错误处理的习惯,
//当你决定忽略某个错误时,你应该在清晰的记录下你的意 图。 

数据库编程:

代码语言:javascript
复制
import "database/sql"
func listTracks(db sql.DB, artist string, minYear, maxYear int) { 
result, err := db.Exec(
"SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",
artist, minYear, maxYear)
// ...
}
Exec方法使用SQL字面量替换在查询字符串中的每个'?';SQL字面量表示相应参数的值,
它有可能 是一个布尔值,一个数字,一个字符串,或者nil空值。用这种方式构造查询可以帮助避免SQL注入 攻击;

如果希望通过类型开关来控制入参可以使用如下方式:
func sqlQuote(x interface{}) string { 
switch x := x.(type) {
case nil:
  return "NULL" 
case int, uint:
  return fmt.Sprintf("%d", x) // x has type interface{} here. case bool:
  if x {
  return "TRUE"
  }
  return "FALSE" 
case string:
  return sqlQuoteString(x) // (not shown) default:
panic(fmt.Sprintf("unexpected type %T: %v", x, x)) }
}

测试编程:

代码语言:javascript
复制

在 *_test.go 文件中,有三种类型的函数:测试函数、基准测试(benchmark)函数、示例函数。
一 个测试函数是以Test为函数名前缀的函数,用于测试程序的一些逻辑行为是否正确;
go test命令会 调用这些测试函数并报告测试结果是PASS或FAIL。
基准测试函数是以Benchmark为函数名前缀的 函数,它们用于衡量一些函数的性能;
go test命令会多次运行基准函数以计算一个平均的执行时 间。
示例函数是以Example为函数名前缀的函数,提供一个由编译器保证正确性的示例文档。

go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,生成一个临时的main包用于 
调用相应的测试函数,接着构建并运行、报告测试结果,最后清理测试中生成的临时文件。

测试函数:

每个测试函数必须导入testing包。测试函数有如下的签名:

代码语言:javascript
复制
func TestName(t *testing.T) { // ...
}
测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头:
func TestLog(t *testing.T) { /* ... */ }
其中t参数用于报告测试失败和附加的日志信息。
比如如下例子:
// Package word provides utilities for word games.
package word
// IsPalindrome reports whether s reads the same forward and backward. // (Our first attempt.)
func IsPalindrome(s string) bool {
for i := range s {
if s[i] != s[len(s)‐1‐i] {
return false }
}
return true }

在相同的目录下,创建word_test.go测试文件
package word
import "testing"
func TestPalindrome(t *testing.T) { 
if !IsPalindrome("detartrated") {
t.Error(`IsPalindrome("detartrated") = false`) }
if !IsPalindrome("kayak") { 
t.Error(`IsPalindrome("kayak") = false`)
} }
func TestNonPalindrome(t *testing.T) { 
if IsPalindrome("palindrome") {
t.Error(`IsPalindrome("palindrome") = true`) }
}
参数 ‐v 可用于打印每个测试函数的名字和运行时间:
go test ‐v
=== RUN TestPalindrome
‐‐‐ PASS: TestPalindrome (0.00s)
=== RUN TestNonPalindrome
‐‐‐ PASS: TestNonPalindrome (0.00s) === RUN TestFrenchPalindrome
‐‐‐ FAIL: TestFrenchPalindrome (0.00s)
word_test.go:28: IsPalindrome("été") = false === RUN TestCanalPalindrome
‐‐‐ FAIL: TestCanalPalindrome (0.00s)
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false FAIL
exit status 1
FAIL gopl.io/ch11/word1 0.017s

参数‐run对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被go test测试命令 运行:
 go test ‐v ‐run="French|Canal"
=== RUN TestFrenchPalindrome
‐‐‐ FAIL: TestFrenchPalindrome (0.00s)
word_test.go:28: IsPalindrome("été") = false === RUN TestCanalPalindrome
‐‐‐ FAIL: TestCanalPalindrome (0.00s)
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false FAIL
exit status 1
FAIL gopl.io/ch11/word1 0.014s

报告的输出:

代码语言:javascript
复制
$ 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
...

基准测试:

代码语言:javascript
复制
基准测试是测量一个程序在固定工作负载下的性能。在Go语言中,基准测试函数和普通测试函数 写法类似,
但是以Benchmark为前缀名,并且带有一个 *testing.B 类型的参数; 
*testing.B 参数除 了提供和 *testing.T 类似的方法,还有额外一些和性能测量相关的方法。
它还提供了一个整数N, 用于指定操作执行的循环次数。
下面是IsPalindrome函数的基准测试,其中循环将执行N次。

import "testing"
func BenchmarkIsPalindrome(b *testing.B) { 
for i := 0; i < b.N; i++ {
IsPalindrome("A man, a plan, a canal: Panama") }
}
$ go test ‐cpuprofile=cpu.out
$ go test ‐blockprofile=block.out 
$ go test ‐memprofile=mem.out


go test ‐run=NONE ‐bench=ClientServerParallelTLS64 \ ‐cpuprofile=cpu.log net/http
PASS
BenchmarkClientServerParallelTLS64‐8 1000
3141325 ns/op 143010 B/op 1747 allocs/op ok net/http 3.395s
$ go tool pprof ‐text ‐nodecount=10 ./http.test cpu.log 2570ms of 3590ms total (71.59%)
Dropped 129 nodes (cum <= 17.95ms)
Showing top 10 nodes out of 166 (cum >= 60ms)

示例函数:

代码语言:javascript
复制
第三种被go test特别对待的函数是示例函数,以Example为函数名开头。示例函数没有函数参数
和返回值。
func ExampleIsPalindrome() {
fmt.Println(IsPalindrome("A man, a plan, a canal: Panama")) fmt.Println(IsPalindrome("palindrome"))
// Output:
// true
// false
}
示例函数有三个用处。
最主要的一个是作为文档:
一个包的例子可以更简洁直观的方式来演示函数 的用法,比文字描述更直接易懂,特别是作为一个提醒或快速参考时。
一个示例函数也可以方便展 示属于同一个接口的几种类型或函数之间的关系,所有的文档都必须关联到一个地方,就像一个类 
型或函数声明都统一到包一样。同时,示例函数和注释并不一样,示例函数是真实的Go代码,需 要接受编译器的编译时检查,
这样可以保证源代码更新时,示例代码不会脱节。
示例文档的第二个用处是,在 执行测试的时候也会运行示例函数测试。如果示例函数内含 有类似上面例子中的 格式的注释,
那么测试工具会执行这个示例函数,然后检查示例函 数的标准输出与注释是否匹配。
示例函数的第三个目的提供一个真实的演练场。 http://golang.org 就是由godoc提供的文档服务

本文的内容来源于Go语言编程和Go程序设计语言,如果有需要可以购买书籍查看。

后续会持续更新Go语言的生态内容,如果有需要可以关注。或者百度搜索wolf_love666。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目约定:
  • 代码编程:
  • 网络编程
  • 错误处理策略:
  • 数据库编程:
  • 测试编程:
    • 测试函数:
      • 基准测试:
        • 示例函数:
        相关产品与服务
        文档服务
        文档服务(Document Service,DS)是腾讯云数据万象提供云上文档处理服务,支持多种类型的文件生成图片或 html 格式的预览,可以解决文档内容的页面展示问题,满足 PC、App 等多端的文档在线浏览需求。同时,本产品还提供文本隐私筛查能力,可以有效识别文本中的身份证号、银行卡号、手机号等敏感数据,满足数据可用性和隐私保护的各种要求。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档