我有一项仅对influxDB进行查询(读/写)的服务。
我想对此进行单元测试,但是我不确定该怎么做,我读过很多关于模拟的tuto。很多处理诸如go-sqlmock之类的组件。但是当我使用influxDB时,我无法使用它。
我还发现我尝试使用的其他组件(例如goMock或testify)过于复杂。
我想做的是创建一个存储库层,该接口应该实现我需要运行/测试的所有方法,并通过依赖注入传递具体的类。
我认为它可以工作,但这是最简单的方法吗?
我想到处都有存储库,即使对于小型服务而言,只是为了使其可测试,似乎都过度设计了。
如果需要,我可以给您代码,但是我认为我的问题在理论上比实际更多。这是模拟用于单元测试的自定义数据库的最简单方法。
答案 0 :(得分:2)
按照其定义,如果您使用外部资源测试集成,那么我们所说的是集成测试,而不是单元测试。因此,我们有两个问题要解决。
您通常要做的是拥有一个接受接口的数据访问层,而接口又易于模拟并且可以对应用程序逻辑进行单元测试。
package main
import (
"errors"
"fmt"
)
var (
values = map[string]string{"foo": "bar", "bar": "baz"}
Expected = errors.New("Expected error")
)
type Getter interface {
Get(name string) (string, error)
}
// ErrorGetter implements Getter and always returns an error to test the error handling code of the caller.
// ofc, you could (and prolly should) use some mocking here in order to be able to test various other cases
type ErrorGetter struct{}
func (e ErrorGetter) Get(name string) (string, error) {
return "", Expected
}
// MapGetter implements Getter and uses a map as its datasource.
// Here you can see that you actually get an advantage: you decouple your logic from the data source,
// making refactoring (and debugging) **much** easier WTSHTF.
type MapGetter struct {
data map[string]string
}
func (m MapGetter) Get(name string) (string, error) {
if v, ok := m.data[name]; ok {
return v, nil
}
return "", fmt.Errorf("No value found for %s", name)
}
type retriever struct {
g Getter
}
func (r retriever) retrieve(name string) (string, error) {
return r.g.Get(name)
}
func main() {
// Assume this is test code. No tests possible on playground ;)
bad := retriever{g: ErrorGetter{}}
s, err := bad.retrieve("baz")
if s != "" || err == nil {
panic("Something went seriously wrong")
}
// Needs to fail as well, as "baz" is not in values
good := retriever{g: MapGetter{values}}
s, err = good.retrieve("baz")
if s != "" || err == nil {
panic("Something went seriously wrong")
}
s, err = good.retrieve("foo")
if s != "bar" || err != nil {
panic("Something went seriously wrong")
}
}
在上面的示例中,实际上我必须实现两个Getter来覆盖所有测试用例,因为我无法使用模拟库,但是您可以理解。
至于过度工程:简单明了,不,那不是过度工程。这就是我个人所说的适当的工艺。从长远来看,它将习惯它。也许不是在这个项目中,而是一个未来。
躲闪。我倾向于做的是确保在我提交查询之前 是正确的;)
在极少数情况下,例如,我真的想在CI中验证我的查询,我通常会创建一个Makefile,该文件反过来会旋转一个docker(-compose),后者提供要集成的内容,然后运行测试
答案 1 :(得分:2)
要扩展@Markus W Mahlberg答案:
如果目标是验证查询是否有效并针对涌入实际执行,则没有捷径可以针对涌入实际执行查询。这些通常被认为是“集成”测试。我发现使用docker-compose可以使这些测试与单元测试一样可靠,并且速度足够快,可以集成到CI中。通过在CI中执行测试,本地工程师可以轻松地运行这些测试以验证其查询更改。
我想到处都有存储库,即使对于小型服务而言,只是为了使其可测试,似乎都经过了过度设计。
我发现这是个两极分化的讨论。 test implementation IS a concrete implementation并为reliable, repeatable tests铺平了道路,它们支持轻松隔离和执行代码的特定组件。
我想对此进行单元测试,但是我不确定该怎么做
我认为这很细微,IMO单元测试查询提供了负值。价值来自使用存储库接口,使您的单元测试可以显式配置从入站接收的响应,以充分利用您的应用程序代码。这没有提供有关流入的反馈,这就是为什么必须进行集成测试才能验证您的应用程序可以有效地配置,连接和查询流入的原因。部署应用程序时会隐式地进行此验证,这时在反馈方面要比在本地和通过集成测试在CI中进行验证要昂贵得多。
我创建了一个图表来尝试说明这些差异:
带有存储库的单元测试专注于您的应用程序代码,几乎没有提供任何与涌入有关的反馈/价值。集成测试对于验证客户端很有用(可能会根据测试的执行位置扩展到您的应用程序,但是我更喜欢将其绑定到客户端,因为您已经从接口和调用中获得了静态反馈)。最后,正如@Markus指出的那样,从集成测试开始进行e2e测试的步骤非常小,您可以使用test your full service。