英文:
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))
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论