Dawn's Blogs

分享技术 记录成长

0%

Go Web编程学习笔记 (7) 错误处理和测试

错误处理

在 Go 语言中,定义了 error 类型,来显示的表达错误,错误作为函数的返回值进行返回。

Error 类型

error 类型是一个接口类型:

1
2
3
type error interface {
Error() string
}

可以通过 errors.New 把一个字符串转化为 errorString,以得到一个 error 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
// errorString is a trivial implementation of error.
type errorString struct {
s string
}

func (e *errorString) Error() string {
return e.s
}

// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}

自定义 Error

error 因为是一个接口,所以可以通过实现 error 接口,自定义错误类型。如 json.SyntaxError 类型,除了错误描述外,还定义了错误发生的位置:

1
2
3
4
5
6
type SyntaxError struct {
msg string // 错误描述
Offset int64 // 错误发生的位置
}

func (e *SyntaxError) Error() string { return e.msg }

再如 net.Error 类型,定义了更复杂的错误处理,判断是否超时、或者临时性错误:

1
2
3
4
5
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}

可以通过类型断言,获取自定义的错误类型:

1
2
3
serr, ok := err.(*json.SyntaxError)

nerr, ok := err.(net.Error)

异常

Go 语言中,将异常与错误区分开来:

  • 错误指可以预期的错误,这时可以将错误当作函数返回值返回。
  • 异常指无法预期、无法继续执行的严重程序错误,如数组地址越界。

异常恢复机制

Go 语言中可以使用 recover 机制来恢复异常:

1
2
3
4
5
6
defer func(){
if x := recover(); x != nil {
// 如果发生了异常
// ....
}
}()

测试

编写测试用例

测试文件必须遵循如下原则:

  • 文件名必须是 _test.go 结尾的。
  • 必须 import testing
  • 所有的测试函数的格式为 func TestXxx (t *testing.T)
  • 通过调用 testing.TError, Errorf, FailNow, Fatal, FatalIf 方法,说明测试不通过,调用 Log 方法用来记录测试的信息。

如编写 go 文件 gotest.go

1
2
3
4
5
6
7
8
9
10
package gotest

import "errors"

func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("被除数不为0")
}
return a / b, nil
}

编写相应的测试文件 gotest_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package gotest

import "testing"

func TestDivide_1(t *testing.T) {
if i, e := Divide(6, 2); i != 3 || e != nil {
t.Error("除法函数测试没通过")
}
t.Log("第一个测试通过")
}

func TestDivide_2(t *testing.T) {
if _, e := Divide(6, 0); e == nil {
t.Error("除数为0测试未通过")
}
t.Log("第二个测试通过")
}

执行测试文件可以得到以下结果:

1
2
3
4
5
6
7
=== RUN   TestDivide_1
gotest_test.go:9: 第一个测试通过
--- PASS: TestDivide_1 (0.00s)
=== RUN TestDivide_2
gotest_test.go:16: 第二个测试通过
--- PASS: TestDivide_2 (0.00s)
PASS

压力测试

压力测试用来测试函数的性能,需要注意:

  • 压力测试函数的格式为 func BenchmarkXXX(b *testing.B)
  • go test 不会默认执行压力测试的函数,如果要执行压力测试需要带上参数 -test.bench,语法: -test.bench="test_name_regex"

对上述编写的除法函数进行压力测试:

1
2
3
4
5
6
7
8
9
10
11
12
func BenchmarkDivide(b *testing.B) {
b.StopTimer() // 调用该函数停止压力测试的时间计数

// 做一些初始化的工作,例如读取文件数据,数据库连接之类的,
// 这样这些时间不影响我们测试函数本身的性能

b.StartTimer() // 重新开始时间

for i := 0; i < b.N; i++ {
Divide(10, 3)
}
}

可以通过调用 b.StopTimer()b.StartTimer() 暂停和开始时间计数。执行结果如下:

1
2
3
4
5
6
7
goos: windows
goarch: amd64
pkg: go-web-demo/ch11/gotest
cpu: Intel(R) Pentium(R) CPU G4560 @ 3.50GHz
BenchmarkDivide
BenchmarkDivide-4 1000000000 0.4220 ns/op
PASS