【问题标题】:Golang with Martini: Mock testing exampleGolang with Martini:模拟测试示例
【发布时间】:2015-04-09 19:26:42
【问题描述】:

我已经整理了一段代码,它在我的路线上执行 GET。我想使用模拟来测试它。我是围棋和测试新手,因此非常感谢任何提示。

My Generate Routes.go 为当前 URL 生成路由。 片段:

func (h *StateRoute) GenerateRoutes (router *martini.Router) *martini.Router {
    r := *router

    /**
     * Get all states
     * 
     */
    r.Get("/state",  func( enc app.Encoder,
            db abstract.MongoDB,
            reqContext abstract.RequestContext,
            res http.ResponseWriter,
            req *http.Request) (int, string) {

        states := []models.State{}

        searchQuery := bson.M{}

        var q *mgo.Query = db.GetDB().C("states").Find(searchQuery)
        query, currentPage, limit, total := abstract.Paginate(req, q)
        query.All(&states)

        str, err := enc.EncodeWithPagination(currentPage, limit, total, states)

        return http.StatusOK, app.WrapResponse(str, err)
    })
}

这是在我的 server.go 中调用的:

var configuration = app.LoadConfiguration(os.Getenv("MYENV"))

// Our Martini API Instance
var apiInstance *martini.Martini

func init() {

    apiInstance = martini.New()
    // Setup middleware
    apiInstance.Use(martini.Recovery())
    apiInstance.Use(martini.Logger())

    // Add the request context middleware to support contexual data availability
    reqContext := &app.LRSContext{ }
    reqContext.SetConfiguration(configuration)

    producer := app.ConfigProducer(reqContext)
    reqContext.SetProducer(producer)

    apiInstance.MapTo(reqContext, (*abstract.RequestContext)(nil))

    // Hook in the OAuth2 Authorization object, to be processed before all requests
    apiInstance.Use(app.VerifyAuthorization)

    // Connect to the DB and Inject the DB connection into Martini
    apiInstance.Use(app.MongoDBConnect(reqContext))

    // Add the ResponseEncoder to allow JSON encoding of our responses
    apiInstance.Use(app.ResponseEncoder)

    // Add Route handlers
    r := martini.NewRouter()

    stateRouter := routes.StateRoute{}

    stateRouter.GenerateRoutes(&r)

    // Add the built router as the martini action
    apiInstance.Action(r.Handle)
}

我的疑惑:

  1. 考虑到我正在尝试注入依赖项,这里的模拟如何工作?

  2. 我应该从哪里开始测试,即我应该在 Generate Routes 中模拟 r.Get?现在,我已经这样做了,但由于我使用的是处理所有路由和请求的 Martini,如果我所做的是正确的,我会迷路吗?

state_test.go:

type mockedStateRoute struct {
    // How can I mock the stateRoute struct?
    mock.Mock
}
type mockedEncoder struct {
    mock.Mock
}
type mockedMongoDB struct {
    mock.Mock
}
type mockedReqContext struct{
    mock.Mock
}
type mockedRespWriter struct{
    mock.Mock
}
type mockedReq struct{
    mock.Mock
}

func (m *mockedStateRoute) testGetStatesRoute(m1 mockedEncoder,
                    m2 mockedMongoDB, m3 mockedReqContext,
                    m4 mockedReqContext, m5 mockedRespWriter,
                    m6 mockedReq) (string) {
                        args := m.Called(m1,m2,m3,m4,m5,m6)
                        fmt.Print("You just called /states/GET")
                        // 1 is just a test value I want to return
                    return 1, args.Error(1)
}

func TestSomething (t *testing.T) {
    testObj := new(mockedStateRoute)

    testObj.On("testGetStatesRoute", 123).Return(true,nil)

    // My target function that does something with mockedStateRoute
    // How can I call the GET function in GenerateRoutes(). Or should I, since martini is handling all my requests
}

我提到的链接:

  1. /stretchr/testify/mock doc
  2. examples of 1.

【问题讨论】:

    标签: testing go mocking martini


    【解决方案1】:

    为了进行依赖注入,要测试的东西需要有某种方式来接收它的依赖。在您的代码中,与 mongodb 的连接是在初始化事物以测试自身时完成的,不允许在模拟时注入看起来像 mongo 连接的东西。

    实现它的方法有很多,但是最简单和最直接的依赖注入方法之一是让要测试的东西在创建时接收依赖关系,这样它的上下文就是特定的地方配置了依赖项的实现。看看this example

    type DataStore interface {
        Get(k string) string
        Set(k, v string)
    }
    
    type MyInstance struct {
        *martini.Martini
    }
    
    func NewAppInstance(d DataStore) *MyInstance {
        ...
    }
    
    func main() {
       d := NewRedisDataStore("127.0.0.1", 6379)
       NewAppInstance(d).Run()
    }
    

    该实例需要一个Datastore 的实现才能工作,它不需要知道任何关于它的内部结构,唯一重要的是它实现了接口,同时具有Get 和@987654325 这两种方法@。事实上,作为单元测试的一般规则,您只想测试您的代码,而不是您的依赖项。在此示例中,它在“生产”中使用 Redis,但在测试中:

    type MockedDataStore struct {
        mock.Mock
    }
    
    func (m *MockedDataStore) Get(k string) string {
        args := m.Called(k)
        return args.String(0)
    }
    
    func (m *MockedDataStore) Set(k, v string) {
        m.Called(k, v)
    }
    

    除了让框架检查它是否被调用之外,它只是没有任何功能的东西。在测试本身中,您必须使用以下内容配置期望:

    d := new(MockedDataStore)
    ...
    d.On("Set", "foo", "42").Return().Once()
    ...
    d.On("Get", "foo").Return("42").Once()
    

    当然,用模拟的东西初始化实例,并测试它:

    d := new(MockedDataStore)
    instance := NewAppInstance(d)
    d.On("Get", "foo").Return("42").Once()
    request, _ = http.NewRequest("GET", "/get/foo", nil)
    response = httptest.NewRecorder()
    instance.ServeHTTP(response, request)
    d.AssertExpectations(t)
    

    因此,作为总结,更具体地回答您的问题:

    1. 您需要使您的实例能够使用其依赖项进行初始化,例如创建一个接收依赖项并返回实例的方法。然后模拟依赖项并从测试中使用模拟而不是“真实”的。

    2. 使用martini提供的方法ServeHTTP生成对HTTP请求的响应,并使用httptest.NewRecorder()模拟响应的接收。当然,如果您的应用程序有更复杂的功能,除了 HTTP 接口使用,您也可以将其作为普通方法进行测试。

    【讨论】:

    • 非常感谢!这就是我一直在寻找的。一旦我决定实施它,我会有更多的问题。如果我卡住了,我还能在这个线程中打扰你吗?
    • 当然,没问题,一点都不麻烦,我也是从Go开始的:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-29
    • 2020-01-02
    • 2016-08-09
    • 2015-10-23
    • 2022-11-22
    相关资源
    最近更新 更多