英文:
Google App Engine Datastore - Testing Queries fails
问题
我目前正在尝试测试一段代码,该代码在向数据存储中添加新实体之前运行一个查询,以确保不会创建重复项。我编写的代码在应用程序的上下文中运行良好,但我为该方法编写的测试失败了。似乎在测试包的上下文中无法通过查询访问放入数据存储的数据。
可能的原因之一可能在于goapp test
的输出,其中显示:“应用所有待处理的事务并保存数据存储”。这一行在调用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")
}
}
英文:
I am currently trying to test a piece of my code that runs a query on the datastore before putting in a new entity to ensure that duplicates are not created. The code I wrote works fine in the context of the app, but the tests I wrote for that methods are failing. It seems that I cannot access data put into the datastore through queries in the context of the testing package.
One possibility might lie in the output from goapp test
which reads: Applying all pending transactions and saving the datastore
. This line prints out after both the get and put methods are called (I verified this with log statements).
I tried closing the context and creating a new one for the different operations, but unfortunately that didn't help either. Below is a simple test case that Puts in an object and then runs a query on it. Any help would be appreciated.
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")
}
}
答案1
得分: 10
你的测试代码没有问题。问题出在Datastore本身上。HR Datastore中的大多数查询都不是“立即一致”的,而是“最终一致”的。你可以在Datastore文档中了解更多相关信息。
基本上,当你将一个实体放入Datastore时,SDK的Datastore会“模拟”你在生产环境中观察到的延迟。因此,如果你在此之后立即运行一个查询(非祖先查询),查询结果将不包括你刚保存的新实体。
如果在datastore.Put()
和q.GetAll()
之间加入几秒的延迟,你会看到测试通过。你可以试一试。在我的测试中,只需要延迟100毫秒,测试总是通过的。但是在编写这种情况下的测试时,使用StronglyConsistentDatastore: true
选项,就像JonhGB的答案中所示。
如果你使用祖先查询,你也会看到测试通过,因为它们是“强一致”的。
英文:
There is nothing wrong with your test code. The issue lies in the Datastore itself. Most queries in the HR Datastore are not "immediately consistent" but eventually consistent. You can read more about this in the Datastore documentation.
So basically what happens is that you put an entity into the Datastore, and the SDK's Datastore "simulates" the latency that you can observe in production, so if you run a query right after that (which is not an ancestor query), the query result will not include the new entity you just saved.
If you put a few seconds sleep between the datastore.Put()
and q.GetAll()
, you will see the test passes. Try it. In my test it was enough to sleep just 100ms, and the test always passed. But when writing tests for such cases, use the StronglyConsistentDatastore: true
option as can be seen in JonhGB's answer.
You would also see the test pass without sleep if you'd use [Ancestor queries] 3 because they are strongly consistent.
答案2
得分: 5
这样做的方法是通过设置上下文来强制数据存储保持强一致性:
c, err := aetest.NewContext(&aetest.Options{StronglyConsistentDatastore: true})
if err != nil {
t.Fatal(err)
}
现在数据存储不需要任何延迟就可以工作,这样更快,并且在一般情况下是更好的做法。
更新:这仅适用于旧的aetest
包,该包通过appengine/aetest
导入。对于较新的aetest
包(通过google.golang.org/appengine/aetest
导入),这种方法不起作用。App Engine已从使用appengine.Context
更改为使用context.Context
,因此测试包的工作方式也有很大不同。
英文:
The way to do this is to force the datastore to be strongly consistent by setting up the context like this:
c, err := aetest.NewContext(&aetest.Options{StronglyConsistentDatastore: true})
if err != nil {
t.Fatal(err)
}
Now the datastore won't need any sleep to work, which is faster, and better practice in general.
Update: This only works with the old aetest package which was imported via appengine/aetest
. It does not work with the newer aetest package which is imported with google.golang.org/appengine/aetest
. App Engine has changed from using an appengine.Context
to using a context.Context
, and consequently the way that the test package now works is quite different.
答案3
得分: 3
为了补充@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)
英文:
To compliment @JohnGB's answer in the latest version of aetest, there are more steps to get a context with strong consistency. First create an instance, then create a request from that instance, which you can use to produce a context.
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论