测试的分类
手动测试:手动打开页面进行一个页面一个页面的测试。
自动化测试:
- 单元测试 :测试的最小单元,测试底层功能函数,例如有一个数据库连接的文件,文件里的每一个方法都可用单元测试来保证其可用性;
- 集成测试 :又称为功能测试,在 Web 开发中测试整个 Web 请求或 HTTP API 请求,会模拟 HTTP 请求到自己提供的服务器上,创建或更改数据,然后检查数据库里这些数据是否变更。
- 黑盒测试 :完全模拟用户测试,把应用连带服务器看做是一个整体,模拟 HTTP 请求,对返回结果进行断言。
数据污染
一般来讲,测试环境需要与其他环境区分开,一方面是为了保证测试的准确性,另一方面也是避免污染其他环境。举例说明:假如我们测试文章的创建功能,会模拟用户提交表单,创建一条文章数据,然后访问链接,断言标题是否与提交的表单数据对应上。此时数据库里会多出来一条我们的测试数据,我们称之为 污染。
安装测试第三方包
安装 stretchr/testify
,运用此包的断言功能。
Assertion:断言,指在代码层面上,用以判断获取到的结果是否符合预期,以此来判断测试是否通过。
go get 安装:go get github.com/stretchr/testify
使用第三方测试包
测试文件名称以 _test.go
结尾,会告知 Go 编译器和工具链这是一个测试文件。Go 编译器在编译时会跳过这些文件,而工具 go test
在执行时默认会运行当前目录下所有拥有 _test
后缀名的文件。
例:
tests/xxx_test.go
package tests
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHomePage(t *testing.T) {
baseURL := "http://localhost:3001"
// 1. 请求,模拟用户访问浏览器
var (
resp *http.Response
err error
)
resp, err = http.Get(baseURL + "/")
// 2. 检测,是否无错误且返回状态码 200
assert.NoError(t, err, "有错误发生,err 不为空")
assert.Equal(t, http.StatusFound, resp.StatusCode, "应返回状态码 200")
}
使用 http.Get()
返回的是 *http.Response
和 error ,error 为 nil 表示没有出现错误。
type Response struct {
Status string // 响应状态,字符串,"200 OK"
StatusCode int // 响应状态码,200、304、404等
Proto string // 协议类型,字符串,"HTTP/1.1"
ProtoMajor int // 协议的主版本号, 1
ProtoMinor int // 协议的副版本号,0
Header Header // 响应头
Body io.ReadCloser // 响应的body信息
ContentLength int64 // 响应数据包长度
TransferEncoding []string // 传输编码
Request *Request // 响应的请求信息
}
assert.NoError(t TestingT, err error, msgAndArgs ...interface{}) bool
断言没有错误发生。
t
:为 testing 标准库里的 testing.T 对象
err
:为错误对象 err
msgAndArgs
:interface 类型的可变长参数,出错时显示的信息
assert.Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
断言两个值相等。
t
:为 testing 标准库里的 testing.T 对象
expected
:是期待的状态码。
actual
:interface 类型的参数是请求返回的状态码。
msgAndArgs
:interface 类型的可变长参数,出错时显示的信息
testify 常用断言函数
// 相等
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
// 是否为 nil
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
// 是否为空
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
// 是否存在错误
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool
// 是否为 0 值
func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool
func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool
// 是否为布尔值
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool
// 断言长度一致
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool
// 断言包含、子集、非子集
func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool)
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool)
// 断言文件和目录存在
func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool
func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool
testing.T
// 获取测试名称
method (*T) Name() string
// 打印日志
method (*T) Log(args ...interface{})
// 打印日志,支持 Printf 格式化打印
method (*T) Logf(format string, args ...interface{})
// 反馈测试失败,但不退出测试,继续执行
method (*T) Fail()
// 反馈测试失败,立刻退出测试
method (*T) FailNow()
// 反馈测试失败,打印错误
method (*T) Error(args ...interface{})
// 反馈测试失败,打印错误,支持 Printf 的格式化规则
method (*T) Errorf(format string, args ...interface{})
// 检测是否已经发生过错误
method (*T) Failed() bool
// 相当于 Error + FailNow,表示这是非常严重的错误,打印信息结束需立刻退出。
method (*T) Fatal(args ...interface{})
// 相当于 Errorf + FailNow,与 Fatal 类似,区别在于支持 Printf 格式化打印信息;
method (*T) Fatalf(format string, args ...interface{})
// 跳出测试,从调用 SkipNow 退出,如果之前有错误依然提示测试报错
method (*T) SkipNow()
// 相当于 Log 和 SkipNow 的组合
method (*T) Skip(args ...interface{})
// 与Skip,相当于 Logf 和 SkipNow 的组合,区别在于支持 Printf 格式化打印
method (*T) Skipf(format string, args ...interface{})
// 用于标记调用函数为 helper 函数,打印文件信息或日志,不会追溯该函数。
method (*T) Helper()
// 标记测试函数可并行执行,这个并行执行仅仅指的是与其他测试函数并行,相同测试不会并行。
method (*T) Parallel()
// 可用于执行子测试
method (*T) Run(name string, f func(t *T)) bool
打印测试信息
单单执行 go test
时,出错才会打印出来信息,可以设置 -v
参数,测试信息就会输出。
测试与重构
第一重要点:先让代码工作起来 - 如果代码不能工作,就不能产生价值。
第二重要点:然后再视图将它编号 - 通过对代码进行重构,让写代码的人和阅读代码的人更好地理解代码,并能按照需求不断地修改代码。
第三重要点:最后试着让它运行更快 - 按照性能提升的需求来重构代码。
缓存测试
Go 中会存在缓存测试的结果:即修改源代码后,未修改测试代码的情况下,结果为上一次测试所产生的结果。
解决办法:为 go test
命令添加 -count=1
的参数,这也是官方推荐的清除缓存的方式,数字代表运行的测试次数。