如何对Google App Engine中的Go HTTP处理程序进行单元测试?

huangapple go评论97阅读模式
英文:

How can I unit test Google App Engine Go HTTP handlers?

问题

从Google App Engine Go SDK的1.8.6版本开始,支持本地单元测试。appengine/aetest包允许我创建一个Context来进行单元测试。

我该如何与net/http/httptest一起使用,来测试我的HTTP处理程序?

英文:

Local unit testing is supported from version 1.8.6 of the Google App Engine Go SDK. The appengine/aetest package allows me to create a Context to unit test with.

How can I use this with net/http/httptest to test my HTTP handlers?

答案1

得分: 16

请参考以下翻译:

请查看goroot/src/pkg/appengine/aetest/context.go文件的顶部(更新的源代码尚未发布在https://code.google.com/p/appengine-go上)。乍一看,新的测试应用程序似乎是appenginetesting的一个稍微强大/不同版本,因此您可以执行相同类型的测试,有关如何使用sampleHandler(w http.ResponseWriter, r *http.Request)进行测试的方法,请参见此处

或者,您可以将您的http.Handler的ContextHandler定义如下:

type ContextHandler struct {
    Real func(*appengine.Context, http.ResponseWriter, *http.Request)
}

func (f ContextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    f.Real(c, w, r)
}

func myNewHandler(c appengine.Context, w http.ResponseWriter, r *http.Request) {
// 做一些操作
}

然后,您可以在init()函数中执行以下操作以支持生产环境:

http.Handle("/myNewHandler", ContextHandler{myNewHandler})

这样可以轻松测试该函数:

func TestMyNewHandler(t *testing.T) {
    c := aetest.NewContext()
    r, _ := http.NewRequest("GET", "/tasks/findOverdueSchedules", nil)
    w := httptest.NewRecorder()
    myNewHandler(c, w, r)
    if 200 != w.Code {
        t.Fail()
    }
}

以下是appengine/aetest中context.go的内容:
>/*
Package aetest提供了用于测试的appengine.Context。

>一个示例测试文件:
package foo_test

import (
    "testing"

    "appengine/memcache"
    "appengine/aetest"
)

func TestFoo(t *testing.T) {
    c, err := aetest.NewContext(nil)
    if err != nil {
        t.Fatal(err)
    }
    defer c.Close()

    it := &memcache.Item{
        Key:   "some-key",
        Value: []byte("some-value"),
    }
    err = memcache.Set(c, it)
    if err != nil {
        t.Fatalf("Set err: %v", err)
    }
    it, err = memcache.Get(c, "some-key")
    if err != nil {
        t.Fatalf("Get err: %v; want no error", err)
    }
    if g, w := string(it.Value), "some-value" ; g != w {
        t.Errorf("retrieved Item.Value = %q, want %q", g, w)
    }
}

>环境变量APPENGINE_API_SERVER指定要使用的api_server.py可执行文件的位置。如果未设置,则会查找系统PATH。
*/

英文:

See the top of goroot/src/pkg/appengine/aetest/context.go (updated source is not yet posted at https://code.google.com/p/appengine-go). At first glance, the new testing app looks to be a slightly beefier/different version of appenginetesting so you can do the same sorts of tests, see here for one way to do it with how sampleHandler(w http.ResponseWriter, r *http.Request) is called.

Alternatively, you can make your http.Handler's ContextHandler like as below:

type ContextHandler struct {
	Real func(*appengine.Context, http.ResponseWriter, *http.Request)
}

func (f ContextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	f.Real(c, w, r)
}

func myNewHandler(c appengine.Context, w http.ResponseWriter, r *http.Request) {
// do something
}

Then you can do this in init() to support production:

http.Handle("/myNewHandler", ContextHandler{myNewHandler})

This makes testing the function easy:

func TestMyNewHandler(t *testing.T) {
    c := aetest.NewContext()
    r, _ := http.NewRequest("GET", "/tasks/findOverdueSchedules", nil)
    w := httptest.NewRecorder()
    myNewHandler(c, w, r)
    if 200 != w.Code {
        t.Fail()
    }
}

Here's what's from context.go inside appengine/aetest:
>/*
Package aetest provides an appengine.Context for use in tests.

>An example test file:
package foo_test

import (
	"testing"

	"appengine/memcache"
	"appengine/aetest"
)

func TestFoo(t *testing.T) {
	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	it := &memcache.Item{
		Key:   "some-key",
		Value: []byte("some-value"),
	}
	err = memcache.Set(c, it)
	if err != nil {
		t.Fatalf("Set err: %v", err)
	}
	it, err = memcache.Get(c, "some-key")
	if err != nil {
		t.Fatalf("Get err: %v; want no error", err)
	}
	if g, w := string(it.Value), "some-value" ; g != w {
		t.Errorf("retrieved Item.Value = %q, want %q", g, w)
	}
}

>The environment variable APPENGINE_API_SERVER specifies the location of the
api_server.py executable to use. If unset, the system PATH is consulted.
*/

答案2

得分: 1

如果你不反对使用Martini,依赖注入是解决这个问题的一种不错的方式。以下是如何设置你的测试(使用Ginkgo):

var _ = Describe("Items", func() {
    var (
        m *martini.ClassicMartini
    )

    BeforeEach(func() {
        m = martini.Classic()

        // 将应用引擎上下文注入到请求中
        m.Use(func(c martini.Context, req *http.Request) {
            con, _ := aetest.NewContext(nil)
            c.MapTo(con, (*appengine.Context)(nil))
        })

        m.Get("/items", func(c martini.Context){
            // 你想要测试的代码
        })
    })

    It("should get items", func() {
        recorder := httptest.NewRecorder()
        r, _ := http.NewRequest("GET", "/items", nil)
        m.ServeHTTP(recorder, r) // 使用 martini 服务器
        Expect(recorder.Code).To(Equal(200))
    })
})

在生产环境中,实际的上下文将被注入:

m.Use(func(c martini.Context, req *http.Request) {
    c.MapTo(appengine.NewContext(req), (*appengine.Context)(nil))
})
英文:

If you are not opposed to using Martini, dependency injection is a nice way to solve the problem. Here is how you can set up your test (with Ginkgo):

var _ = Describe("Items", func() {

    var (
	    m *martini.ClassicMartini
    )

    BeforeEach(func() {
	    m = martini.Classic()

	    // injects app engine context into requests
	    m.Use(func(c martini.Context, req *http.Request) {
		     con, _ := aetest.NewContext(nil)
		     c.MapTo(con, (*appengine.Context)(nil))
	    })

	    m.Get("/items", func(c martini.Context){
             // code you want to test
        })
    })

	It("should get items", func() {
		recorder := httptest.NewRecorder()
		r, _ := http.NewRequest("GET", "/items", nil)
		m.ServeHTTP(recorder, r) // martini server used
		Expect(recorder.Code).To(Equal(200))
	})
})

In the production the actual context would be injected:

m.Use(func(c martini.Context, req *http.Request) {
	c.MapTo(appengine.NewContext(req), (*appengine.Context)(nil))
})

huangapple
  • 本文由 发表于 2013年10月16日 23:21:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/19407343.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定