测试驱动开发是保持高代码质量的好方法,同时保护自己免于回归,并向自己和其他人证明自己的代码完成了预期的工作。 这里有五个技巧和窍门可以改善你的测试。
把你的测试放在一个不同的包里
Go坚持同一个文件夹中的文件属于同一个包,除了_test.go
文件。将测试代码移出软件包,可以让您编写测试,就好像您是软件包的真正用户。你不能摆弄内部,而是专注于暴露的界面,并总是想着你可能会添加到你的API的任何噪音。
这样可以让你自由地更改内部,而无需调整测试代码。
在不同的文件中进行内部测试
如果你确实需要对一些内部进行单元测试,用_internal_test.go作为后缀创建另一个文件。内部测试必然比接口测试更脆弱, 但是它们是确保内部组件行为的一个很好的方法,而且如果你使用测试驱动开发的话,它是特别有用的。
运行所有测试保存
去建立和运行速度非常快,所以没有任何理由可以说为什么不每次保存时都运行整个测试case。
当你在这个时候,为什么不去同时运行go vet,golint和goimports呢?
在Sublime Text中,这可以通过安装GoSublime并在添加以下配置项之前按下 Cmd +.,5来实现:
“on_save”: [{
“cmd”: “gs9o_run_many”, “args”: {
“commands”:[
[“clear”],
[“sh”, “if [ -f onsave.sh ]; then ./onsave.sh; else gofmt -s -w ./ && go build . errors && go test -i && go test && go vet && golint ; fi”]
],
“focus_view”: false
}
}],
“fmt_cmd”: [“goimports”]
上面的脚本首先检查项目是否有一个onsave.sh
文件,它会运行。这使您可以轻松关闭不适用的软件包的自动测试功能。
写表驱动的测试
匿名结构和复合文字允许我们写出非常清晰和简单的表格测试,而不依赖任何外部包。 下面的代码允许我们为一个尚未写成的“Fib”函数设置一系列的测试:
var fibTests = [] struct {
n int // input
expected int //预期结果
} {
{1,1},
{2,1},
{3,2},
{4,3},
{5,5},
{6,8},
{7,13},
}
然后,我们的测试函数只是覆盖在切片上,在声明结果正确之前为每个“n”调用“Fib”方法:
func TestFib(t *testing.T) {
for _, tt := range fibTests {
actual := Fib(tt.n)
if actual != tt.expected {
t.Errorf("Fib(%d): expected %d, actual %d", tt.n, tt.expected, actual)
}
}
}
看看你是否可以自己写“Fib”函数来让测试通过,或者你可以从Dave Chaney那里找到解决方法。
使用Go代码模拟接口
如果你需要模拟你的代码依赖的东西来正确地测试它,那么很可能是一个接口的好选择。即使你依赖于一个你不能改变的外部包,你的代码仍然可以使用一个外部类型将会满足的接口。
经过几年的写mocks接口,我终于找到了模拟接口的完美方式,而不需要添加任何依赖项到我们的项目中: [check out Moq](https://medium.com/@matryer/meet-moq-easily-mock-interfaces-in-go-476444187d10)。
假设我们正在导入这个外部包:
package mailman
import “net/mail”
type MailMan struct{}
func (m *MailMan) Send(subject, body string, to ...*mail.Address) {
// some code
}
func New() *MailMan {
return &MailMan{}
}
如果我们正在测试的代码需要一个MailMan
对象,我们的测试代码可以调用它的唯一方法是提供一个实际的MailMan
实例。
func SendWelcomeEmail(m * MailMan,to ... * mail.Address){...}
这意味着每当我们运行我们的测试,一个真正的电子邮件可以发送。想象一下,如果我们已经实现了上面的保存功能。我们很快就会惹恼我们的测试用户,或者拿出一大笔服务费用。 另一种方法是将这个简单的接口添加到您的代码中:
type EmailSender interface{
Send(subject, body string, to ...*mail.Address)
}
当然,MailMan已经满足了这个接口,因为我们首先从他的Send
方法签名 - 所以我们仍然可以像以前一样传入MailMan
对象。 但现在我们可以写一个测试邮件发件人:
type testEmailSender struct{
lastSubject string
lastBody string
lastTo []*mail.Address
}
// make sure it satisfies the interface
var _ package.EmailSender = (*testEmailSender)(nil)
func (t *testEmailSender) Send(subject, body string, to ...*mail.Address) {
t.lastSubject = subject
t.lastBody = body
t.lastTo = to
}
现在我们可以更新我们的SendWelcomeEmail
函数来取接口,而不是具体的类型:
func SendWelcomeEmail(m EmailSender,to ... * mail.Address){...}
在我们的测试代码中,我们可以发送我们的假发送者,并在调用目标函数之后在这些字段上进行断言:
func TestSendWelcomeEmail(t *testing.T) {
sender := &testEmailSender{}
SendWelcomeEmail(sender, to1, to2)
if sender.lastSubject != "Welcome" {
t.Error("Subject line was wrong")
}
if sender.To[0] != to1 && sender.To[1] != to2 {
t.Error("Wrong recipients")
}
}
如果你想进一步探索mock特性,那么一定要看看check out Moq, 它不仅描述了一个写模拟的好方法,而且还为你写。