据我所知,在Go中模拟结构依赖的唯一方法是使用接口。所以我的问题是:在我的struct有执行某些操作的方法的情况下(当结构不仅仅是存储数据的模型时),我是否应该始终将属性定义为接口以便正确地模拟&测试一下吗?
简单示例:
type UserService struct {
userRepository UserRepository
}
func (us *UserService) MaleUsers() []User {
all := us.userRepository.FindAll()
maleUsers := []User{}
for _, u := range all {
if u.gender == "male" {
maleUsers = append(maleUsers, u)
}
}
return maleUsers
}
想象一下,我们有一个具有依赖关系的用户服务:存储库。 服务具有获取所有用户然后按某些标准过滤它们的方法。 顺便说一下,过滤逻辑可以进入单独的依赖关系,以避免在服务方法(SRP)中进行过滤。
顺便说一下。我来自Java世界。如果这种结构化应用程序的方法在Go中没有意义,请告诉我。
答案 0 :(得分:1)
为了模仿userRepository
的{{1}}依赖关系,您认为最佳方法是使用界面是正确的。
首先,创建你的界面:
UserService
然后建立一个模拟:
type UserRepository interface {
FindAll() []Users
}
最后,在测试用例中使用此模拟作为依赖项:
type MockUserRepository struct{}
func (mock MockUserRepository) FindAll() []Users {
// here you would manually build a slice of users and return it
return []Users
}
通过这种方式,您创建了一个可以在测试中使用的接口模拟,而无需在存储库上执行任何数据库调用。
答案 1 :(得分:0)
我想,如果你想要正确执行单元测试,使用接口是合理的,正如你在问题中所说的那样。更重要的是,在Java或C#等其他语言中,这是一种自然的方式。但是,如果没有任何更改,您也可以轻松测试包的代码,如果它没有外部依赖关系。
此外,我强烈建议您尽可能多地使用接口,这样可以获得更高的内聚力和更少的耦合,这将使您的代码更易于维护和测试。
在Go中,一个实体可以隐式地满足一个接口,所以你需要有能力在将来模拟你的结构就是添加一个接口声明。看看一些例子:
type MaleIterable interface {
MaleUsers() []User
}
type UserIterable interface {
FindAll() []User
}
以下是一段代码片段,对您也很有用。
package main
import "fmt"
type User struct {
Name string
Gender string
Age int
}
type UserIterable interface {
FindAll() []User
}
type FakeUserRepository struct {}
func (ur FakeUserRepository) FindAll() []User {
return []User{
User{Name:"Alex", Gender:"male", Age:19},
User{Name:"Max", Gender:"male", Age:34},
User{Name:"Maria", Gender:"female", Age:12},
}
}
type UserService struct {
userRepository UserIterable
}
func (us *UserService) MaleUsers() []User {
all := us.userRepository.FindAll()
maleUsers := []User{}
for _, u := range all {
if u.Gender == "male" {
maleUsers = append(maleUsers, u)
}
}
return maleUsers
}
func main() {
us := UserService{
userRepository: FakeUserRepository{},
}
males := us.MaleUsers()
fmt.Println(males)
}