doujujian0052 2016-05-12 08:40
浏览 45
已采纳

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

I'm writing a JSON validator in Go, and I want to test another object that interacts with my Validator. I've implemented the Validator as a struct with methods. To allow me to inject a mock Validator into another object, I've added an interface, which the Validator implements. I've then swapped argument types to expect the interface.

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

One of my mocks looks like this:

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

This works, but it doesn't feel quite right. Collaborators won't see anything other than my concrete type in production code; I've only introduced the interface to suit the test. If I do this everywhere, I feel like I'll be repeating myself by writing interfaces for methods that will only ever have one real implementation.

Is there a better way to do this?

  • 写回答

2条回答 默认 最新

  • douxiuyu2028 2016-05-12 13:34
    关注

    Update: I retract my previous answer. Do not export interfaces across packages. Make your funcs return the concrete type, so to allow the consumer to create their own interface and overrides if they wish.

    See: https://github.com/golang/go/wiki/CodeReviewComments#interfaces HatTip: @rocketspacer

    I also typically code my Tests in a different package than my package code. That way, I can only see what I export (and you sometimes see what you are cluttering up if exporting too much).

    Following this guideline, for testing your package, the process would be:

    • Create your complex object as normal with funcs
    • Use interfaces internally as needed (for example, Car <- Object -> House)
    • Only export your concretes, not interfaces
    • During testing, specify a test method that takes a Test interface of your concrete methods, and change up your interface as needed. you create this test interface in your test package.

    Original Answer Below for Posterity


    Export only your interface, not your concrete type. And add a New() constructor so people can instantiate a default instance from your package, that conforms to the interface.

    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) {
        ...
    }
    

    This keeps your API package clean, with only Validator and New() exported.

    Your consumers only need to know about the interface.

    package main
    
    import "foo.com/bar/validator"
    
    func main() {
        v := validator.New()
        valid, err := v.Validate(...)
        ...
    }
    

    This leaves it up to your consumers to follow dependency injection patterns and instantiate (call the New()) outside of its usage, and inject the instance where ever they use it. This would allow them to mock the interface in their tests and inject the mock.

    Or, the consumer could care less and just write the main code above which is short and sweet and gets the job done.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 乘性高斯噪声在深度学习网络中的应用
  • ¥15 运筹学排序问题中的在线排序
  • ¥15 关于docker部署flink集成hadoop的yarn,请教个问题 flink启动yarn-session.sh连不上hadoop,这个整了好几天一直不行,求帮忙看一下怎么解决
  • ¥30 求一段fortran代码用IVF编译运行的结果
  • ¥15 深度学习根据CNN网络模型,搭建BP模型并训练MNIST数据集
  • ¥15 C++ 头文件/宏冲突问题解决
  • ¥15 用comsol模拟大气湍流通过底部加热(温度不同)的腔体
  • ¥50 安卓adb backup备份子用户应用数据失败
  • ¥20 有人能用聚类分析帮我分析一下文本内容嘛
  • ¥30 python代码,帮调试,帮帮忙吧