func NewPerson() Person {
return Person{}.
WithName("No Name")
}
接口 (Interface)
到现在为止,我们使用的还是公有的结构体。任由这些结构体方法摆布之下,加上创建 mock 可能会引发非预期的副作用,测试起来会很痛苦。
我们可以创建一个同名的接口,并把相应的结构体重命名为 person 使之私有化:
type Person interface {
WithName(name string) Person
Name() string
WithFavoriteColors(favoriteColors []string) Person
NumFavoriteColors() int
FavoriteColorAt(i int) string
WithFavoriteColorAt(i int, favoriteColor string) Person
}
type person struct {
name string
favoriteColors []string
}
我们现在就可以只重写想要替换的逻辑来创建测试 mock:
type personMock struct {
Person
receivedNewColor string
}
func (m personMock) WithFavoriteColorAt(i int, favoriteColor string) Person {
m.receivedNewColor = favoriteColor
return m
}
测试代码样例如下:
mock := personMock{}
result := updateFavoriteColors(mock)
result.(personMock).receivedNewColor // "red"
记录变化
如我早前所言,完整的状态转换非常有益于调试,而且我们可以 wither 来挂入钩子的方式捕捉到所有或部分变换过程:
func (p person) nextState() Person {
fmt.Printf("nextState: %#+vn", p)
return p
}
func (p person) WithName(name string) Person {
p.name = name
return p.nextState() // <- Use "nextState" whenever you return.
}
对于更加复杂的逻辑或个人偏好,你也可以采用 defer 的方式:
func (p person) WithFavoriteColors(favoriteColors []string) Person {
defer func() {
p.nextState()
}()
p.favoriteColors = favoriteColors
return p
}
这样变换就可看到了:
nextState: main.person{name:"No Name", favoriteColors:[]string(nil)}
nextState: main.person{name:"Elliot", favoriteColors:[]string(nil)}
nextState: main.person{name:"Elliot", favoriteColors:[]string{"black", "blue"}}
你可以添加更多诸如此类的信息。例如,时间戳、栈追踪记录和其他自定义的上下文信息来使得调试更加容易。
历史及回滚
除了打印变化之外,我们还可以收集这些状态作为历史:
type Person interface {
// ...
AtVersion(version int) Person
}
type person struct {
// ...
history []person
}
func (p *person) nextState() Person {
p.history = append(p.history, *p)
return *p
}
func (p person) AtVersion(version int) Person {
return p.history[version]
}
func main() {
me := NewPerson().
WithName("Elliot").
WithFavoriteColors([]string{"black", "blue"})
// We discard the result, but it will be put into the history.
updateFavoriteColors(me)
fmt.Printf("%sn", me.AtVersion(0).Name())
fmt.Printf("%sn", me.AtVersion(1).Name())
}
// No Name
// Elliot









