我在模型设计方面遇到一些问题,特别是处理模型特定操作 vs 数据库操作。一个很好的例子就是我的用户模型。
在我的数据库中创建用户时,我想:
测试时,我显然希望对所有4个进行一组单元测试,但是#4调用其他3个,我不想重新测试,或者如果其中任何一个有#4测试失败的风险3做。
我已经为ModelActions和StoreActions创建了一个单独的接口,并在需要时将UserAction接口发送到商店操作,但是当我写出来时,我已经感觉到了一些严重的代码味道。
User
是否有更好的方法来模拟这些单独的操作,以便在测试DB / Store功能时可以模拟UserActions?我尝试将type UserAction {
SetTimestamps()
CreateDigest() (string, error)
VerifyPassword() error
ComparePassword(password string) error
User() *User
}
结构存储为接口的一部分,例如:
{{1}}
然而,这会在创建模拟时导致周期性导入,并且还会打开所有字段,因为模型的字段是可导出的,所以这些字段已经可以使用
答案 0 :(得分:1)
我认为您的用户应该是具体类型,您应该使用界面来模拟您的商店。
例如,像这样的项目结构对我有意义:
cmd/
server/
user.go
user_test.go
main.go
store.go
mysql/
mysql.go
user.go
user_test.go
user.go
user_test.go
您的用户模型位于user.go
的根目录中。此文件将包含您的User
结构,以及将在其上运行的函数,如CreateDigest
。应在user_test.go
。
值得一提的是,在您的根目录中,您的软件包不应该是main
,您的软件包名称应该是项目的名称,我们将其称为myapp
。
您的mysql
,postgres
等也应该是具体实现。您可以在该包中使用以下功能:
func (m *MySQL) InsertUser(u *myapp.User) error
应在mysql/user_test.go
。
最后,我们可以在server
中将它们放在一起。这是您实际部署或运行的二进制文件。
在cmd/server/store.go
中,您应该创建一个由mysql
实现的界面。
在cmd/server/user_test.go
中,很容易模拟这个,这样你就不必点击真正的数据库了。我相信你的界面应该存在于你的客户端。在这种情况下,server
是mysql
的客户。
在cmd/server/user.go
中,您的功能可能如下所示:
func CreateUser(w http.ResponseWriter, r *http.Request) {
var u myapp.user
err := json.NewDecoder(r.Body).Decode(&u)
if err != nil {
panic(err) // don't do this for real
}
d := myapp.CreateDigest(u.Password)
u.Digest = d
// s is the interface, defined in `cmd/server/store.go`, but is implemented by mysql
err = s.InsertUser(&u)
if err != nil {
panic(err)
}
// Since we pass a pointer, you can have your store set the ID of the user
fmt.Println(u.ID)
}
现在您可以更好地分离关注点,一切都应该易于测试,并且对现有代码进行更改很容易。