Go语言精进之路读书笔记第30条——使用接口提高代码的可测试性
Go语言有一个惯例是让单元测试代码时刻伴随着你编写的Go代码。
单元测试是自包含和自运行的,运行时一般不会依赖外部资源(如外部数据库、外部邮件服务器等),并具备跨环境的可重复性(既可在开发人员的本地运行,也可以在持续集成的环境中运行)。
30.1 实现一个附加免责声明的电子邮件发送函数
v1版本实现中,由于"github.com/jordan-wright/email"中Email实例的Send方法会真实地连接外部的邮件服务器,因此该测试每执行一次就会向目标电子邮箱发送一封电邮。
如果用例中的参数有误或执行用例的环境无法联网又或无法访问邮件服务器,那么这个测试将会以失败告终,因此这种测试代码并不具备跨环境的可重复性。
归根结底,是v1版本的实现和"github.com/jordan-wright/email"有紧密的依赖,耦合较深。
30.2 使用接口来降低耦合
v2版本的测试用例不再对外部有任何依赖,是具备跨环境可重复性的。v2版本可以验证content字段中是否包含免责声明。
type FakeEmailSender struct {
subject string
from string
to []string
content string
}
func (s *FakeEmailSender) Send(subject, from string,
to []string, content string, mailserver string, a smtp.Auth) error {
s.subject = subject
s.from = from
s.to = to
s.content = content
return nil
}
func TestSendMailWithDisclaimer(t *testing.T) {
s := &FakeEmailSender{}
err := mail.SendMailWithDisclaimer(s, "gopher mail test v2",
"YOUR_MAILBOX",
[]string{"DEST_MAILBOX"},
"hello, gopher",
"smtp.163.com:25",
smtp.PlainAuth("", "YOUR_EMAIL_ACCOUNT", "YOUR_EMAIL_PASSWD!", "smtp.163.com"))
if err != nil {
t.Fatalf("want: nil, actual: %s\n", err)
return
}
want := "hello, gopher" + "\n\n" + mail.DISCLAIMER
if s.content != want {
t.Fatalf("want: %s, actual: %s\n", want, s.content)
}
}
如果依然要使用"github.com/jordan-wright/email"中Email实例作为邮件发送者,可以使用一个适配器对该Email实例进行包装,使其成为接口MailSender的实现者。
type EmailSenderAdapter struct {
e *email.Email
}
func (adapter *EmailSenderAdapter) Send(subject, from string,
to []string, content string, mailserver string, a smtp.Auth) error {
adapter.e.Subject = subject
adapter.e.From = from
adapter.e.To = to
adapter.e.Text = []byte(content)
return adapter.e.Send(mailserver, a)
}
func ExampleSendMailWithDisclaimer() {
adapter := &EmailSenderAdapter{
e: email.NewEmail(),
}
err := mail.SendMailWithDisclaimer(adapter, "gopher mail test v2",
"YOUR_MAILBOX",
[]string{"DEST_MAILBOX"},
"hello, gopher",
"smtp.163.com:25",
smtp.PlainAuth("", "YOUR_EMAIL_ACCOUNT", "YOUR_EMAIL_PASSWD!", "smtp.163.com"))
if err != nil {
fmt.Printf("SendMail error: %s\n", err)
return
}
fmt.Println("SendMail ok")
// OutPut:
// SendMail ok
}