Google App Engine 数据存储 - 测试查询失败
Google App Engine Datastore - Testing Queries fails
我目前正在尝试测试我的一段代码,该代码在放入新实体之前在数据存储上运行查询以确保不会创建重复项。我编写的代码在应用程序的上下文中运行良好,但我为该方法编写的测试失败了。似乎我无法通过测试包上下文中的查询访问放入数据存储区的数据。
一种可能性可能在于 goapp test
的输出:Applying all pending transactions and saving the datastore
。这行在 get 和 put 方法都被调用后打印出来(我用日志语句验证了这一点)。
我尝试关闭上下文并为不同的操作创建一个新上下文,但不幸的是,这也无济于事。下面是一个简单的测试用例,它放入一个对象然后对其运行查询。任何帮助将不胜感激。
type Entity struct {
Value string
}
func TestEntityQuery(t *testing.T) {
c, err := aetest.NewContext(nil)
if err != nil {
t.Fatal(err)
}
defer c.Close()
key := datastore.NewIncompleteKey(c, "Entity", nil)
key, err = datastore.Put(c, key, &Entity{Value: "test"})
if err != nil {
t.Fatal(err)
}
q := datastore.NewQuery("Entity").Filter("Value =", "test")
var entities []Entity
keys, err := q.GetAll(c, &entities)
if err != nil {
t.Fatal(err)
}
if len(keys) == 0 {
t.Error("No keys found in query")
}
if len(entities) == 0 {
t.Error("No entities found in query")
}
}
你的测试代码没有问题。问题在于数据存储本身。 HR Datastore 中的大多数查询不是 "immediately consistent",而是 eventually consistent. You can read more about this in the Datastore 文档。
所以基本上发生的事情是您将一个实体放入数据存储区,SDK 的数据存储区 "simulates" 您可以在生产中观察到的延迟,所以如果您 运行 之后立即进行查询(这不是祖先查询),查询结果将不包括您刚刚保存的新实体。
如果您在 datastore.Put()
和 q.GetAll()
之间暂停几秒钟,您将看到测试通过。尝试一下。在我的测试中,只睡 100 毫秒就足够了,而且测试总是通过。但是在为这种情况编写测试时,请使用 StronglyConsistentDatastore: true
选项,如 JonhGB 的答案所示。
如果您使用 Ancestor queries because they are strongly consistent,您也会看到测试通过而无需睡眠。
这样做的方法是通过像这样设置上下文来强制数据存储保持强一致性:
c, err := aetest.NewContext(&aetest.Options{StronglyConsistentDatastore: true})
if err != nil {
t.Fatal(err)
}
现在数据存储不需要任何睡眠就可以工作,这样速度更快,通常也更好。
更新: 这仅适用于通过 appengine/aetest
导入的旧 aetest 包。它不适用于使用 google.golang.org/appengine/aetest
导入的较新的 aetest 包。 App Engine 已从使用 appengine.Context
更改为使用 context.Context
,因此测试包现在的工作方式完全不同。
恭维@JohnGB 在最新版本的 aetest 中的回答,有更多的步骤来获得具有强一致性的上下文。首先创建一个实例,然后从该实例创建一个请求,您可以使用它来生成上下文。
inst, err := aetest.NewInstance(
&aetest.Options{StronglyConsistentDatastore: true})
if err != nil {
t.Fatal(err)
}
defer inst.Close()
req, err := inst.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
ctx := appengine.NewContext(req)
我目前正在尝试测试我的一段代码,该代码在放入新实体之前在数据存储上运行查询以确保不会创建重复项。我编写的代码在应用程序的上下文中运行良好,但我为该方法编写的测试失败了。似乎我无法通过测试包上下文中的查询访问放入数据存储区的数据。
一种可能性可能在于 goapp test
的输出:Applying all pending transactions and saving the datastore
。这行在 get 和 put 方法都被调用后打印出来(我用日志语句验证了这一点)。
我尝试关闭上下文并为不同的操作创建一个新上下文,但不幸的是,这也无济于事。下面是一个简单的测试用例,它放入一个对象然后对其运行查询。任何帮助将不胜感激。
type Entity struct {
Value string
}
func TestEntityQuery(t *testing.T) {
c, err := aetest.NewContext(nil)
if err != nil {
t.Fatal(err)
}
defer c.Close()
key := datastore.NewIncompleteKey(c, "Entity", nil)
key, err = datastore.Put(c, key, &Entity{Value: "test"})
if err != nil {
t.Fatal(err)
}
q := datastore.NewQuery("Entity").Filter("Value =", "test")
var entities []Entity
keys, err := q.GetAll(c, &entities)
if err != nil {
t.Fatal(err)
}
if len(keys) == 0 {
t.Error("No keys found in query")
}
if len(entities) == 0 {
t.Error("No entities found in query")
}
}
你的测试代码没有问题。问题在于数据存储本身。 HR Datastore 中的大多数查询不是 "immediately consistent",而是 eventually consistent. You can read more about this in the Datastore 文档。
所以基本上发生的事情是您将一个实体放入数据存储区,SDK 的数据存储区 "simulates" 您可以在生产中观察到的延迟,所以如果您 运行 之后立即进行查询(这不是祖先查询),查询结果将不包括您刚刚保存的新实体。
如果您在 datastore.Put()
和 q.GetAll()
之间暂停几秒钟,您将看到测试通过。尝试一下。在我的测试中,只睡 100 毫秒就足够了,而且测试总是通过。但是在为这种情况编写测试时,请使用 StronglyConsistentDatastore: true
选项,如 JonhGB 的答案所示。
如果您使用 Ancestor queries because they are strongly consistent,您也会看到测试通过而无需睡眠。
这样做的方法是通过像这样设置上下文来强制数据存储保持强一致性:
c, err := aetest.NewContext(&aetest.Options{StronglyConsistentDatastore: true})
if err != nil {
t.Fatal(err)
}
现在数据存储不需要任何睡眠就可以工作,这样速度更快,通常也更好。
更新: 这仅适用于通过 appengine/aetest
导入的旧 aetest 包。它不适用于使用 google.golang.org/appengine/aetest
导入的较新的 aetest 包。 App Engine 已从使用 appengine.Context
更改为使用 context.Context
,因此测试包现在的工作方式完全不同。
恭维@JohnGB 在最新版本的 aetest 中的回答,有更多的步骤来获得具有强一致性的上下文。首先创建一个实例,然后从该实例创建一个请求,您可以使用它来生成上下文。
inst, err := aetest.NewInstance(
&aetest.Options{StronglyConsistentDatastore: true})
if err != nil {
t.Fatal(err)
}
defer inst.Close()
req, err := inst.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
ctx := appengine.NewContext(req)