Go:我应该使用接口来进行模拟吗?

时间:2016-05-12 08:40:30

标签: unit-testing testing go

我正在Go中编写一个JSON验证器,我想测试另一个与我的Validator交互的对象。我已经使用方法将Validator实现为struct。为了允许我将模拟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)
}

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

// 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
}

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

有更好的方法吗?

2 个答案:

答案 0 :(得分:3)

更新:我撤回了之前的回答。不要跨包导出接口。使您的func返回具体类型,以便允许使用者创建自己的界面并在需要时覆盖。

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

我通常也会将我的测试编码在与我的包代码不同的包中。这样,我只能看到我导出的内容(如果导出太多,你有时会看到混乱的东西)。

遵循本指南,测试您的包裹,过程如下:

  • 使用funcs
  • 正常创建复杂对象
  • 根据需要在内部使用接口(例如,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) {
    ...
}

这样可以保持您的API包清洁,只导出ValidatorNew()

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

package main

import "foo.com/bar/validator"

func main() {
    v := validator.New()
    valid, err := v.Validate(...)
    ...
}

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

或者,消费者可以更少关心,只需编写上面简短而甜蜜的主要代码并完成工作。

答案 1 :(得分:2)

恕我直言,这是一个很好的解决方案。接口在测试和重新实现时为您提供更多自由。我经常使用接口而且我从不后悔(特别是在测试时),即使它是单个实现(在我的情况下,大部分时间都是这样)。

您可能对此感兴趣:http://relistan.com/writing-testable-apps-in-go/