我应该使用接口来允许模拟吗?

Ros*_*ane 5 testing unit-testing go

我正在用 Go 编写一个 JSON 验证器,我想测试另一个与我的验证器交互的对象。我已经将 Validator 实现为带有方法的结构。为了允许我将模拟 Validator 注入另一个对象,我添加了一个接口,该接口由 Validator 实现。然后我交换了参数类型以期待接口。

// Validator validates JSON documents.
type Validator interface {
    // Validate validates a decoded JSON document.
    Validate(doc interface{}) (valid bool, err error)

    // ValidateString validates a JSON string.
    ValidateString(doc string) (valid bool, err error)
}

// SchemaValidator is a JSON validator fixed with a given schema.
// This effectively allows us to partially apply the gojsonschema.Validate()
// function with the schema.
type SchemaValidator struct {
    // This loader defines the schema to be used.
    schemaLoader    gojsonschema.JSONLoader
    validationError error
}

// Validate validates the given document against the schema.
func (val *SchemaValidator) Validate(doc interface{}) (valid bool, err error) {
    documentLoader := gojsonschema.NewGoLoader(doc)
    return val.validate(documentLoader)
}

// ValidateString validates the given string document against the schema.
func (val *SchemaValidator) ValidateString(doc string) (valid bool, err error) {
    documentLoader := gojsonschema.NewStringLoader(doc)
    return val.validate(documentLoader)
}
Run Code Online (Sandbox Code Playgroud)

我的一个模拟看起来像这样:

// PassingValidator passes for everything.
type PassingValidator bool

// Validate passes. Always
func (val *PassingValidator) Validate(doc interface{}) (valid bool, err error) {
    return true, nil
}

// ValidateString passes. Always
func (val *PassingValidator) ValidateString(doc string) (valid bool, err error) {
    return true, nil
}
Run Code Online (Sandbox Code Playgroud)

这有效,但感觉不太对。协作者不会在生产代码中看到我的具体类型以外的任何东西;我只介绍了适合测试的界面。如果我在任何地方都这样做,我觉得我将通过为只有一个真正实现的方法编写接口来重复自己。

有一个更好的方法吗?

edu*_*911 5

更新:我收回之前的回答。不要跨包导出接口。让您的函数返回具体类型,以便允许消费者创建自己的接口并根据需要进行覆盖。

请参阅: https: //github.com/golang/go/wiki/CodeReviewComments#interfaces HatTip:@rocketspacer

我通常还会在与包代码不同的包中编写测试代码。这样,我只能看到我导出的内容(如果导出太多,有时您会看到您弄乱的内容)。

遵循此指南,测试您的包的过程将是:

  • 使用 func 正常创建复杂对象
  • 根据需要在内部使用接口(例如,Car <- Object -> House)
  • 只导出具体内容,不导出接口
  • 在测试期间,指定一个采用具体方法的 Test 接口的测试方法,并根据需要更改接口。您在测试包中创建此测试接口。

以下为后代的原始答案


仅导出您的接口,而不导出您的具体类型。并添加一个New()构造函数,以便人们可以从您的包中实例化一个符合接口的默认实例。

package validator

type Validator interface {
    Validate(doc interface{}) (valid bool, err error)
    ValidateString(doc string) (valid bool, err error)
}

func New() Validator {
    return &validator{}
}

type validator struct {
    schemaLoader    gojsonschema.JSONLoader
    validationError error
}


func (v *validator) Validate(doc interface{}) (valid bool, err error) {
    ...
}

func (v *validator) ValidateString(doc string) (valid bool, err error) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

这使您的 API 包保持干净,仅包含ValidatorNew()导出。

您的消费者只需要了解界面即可。

package main

import "foo.com/bar/validator"

func main() {
    v := validator.New()
    valid, err := v.Validate(...)
    ...
}
Run Code Online (Sandbox Code Playgroud)

这让您的消费者可以遵循依赖注入模式并New()在其使用之外实例化(调用 ),并在他们使用实例的地方注入实例。这将允许他们在测试中模拟接口并注入模拟。

或者,消费者可以不那么关心,只需编写上面的主要代码即可完成工作,该代码简短而有趣。