How am I getting 'InternalError: table "dev~guestbook!!Entities" already exists' when I just created datastore?

How am I getting 'InternalError: table "dev~guestbook!!Entities" already exists' when I just created datastore?

我正在尝试弄清楚如何为 GAE 设置测试驱动开发。

我开始测试:

nosetests -v --with-gae  

我一直收到错误消息:

InternalError: table "dev~guestbook!!Entities" already exists

直到我在 setUp() 中创建数据存储才存在,但我仍然收到实体已存在的错误消息?

我正在使用 GAE tutorial 中的代码。

这是我在 functional_tests.py 中的测试代码:

import sys, os, subprocess, time, unittest, shlex   
sys.path.append("/usr/local/google_appengine")    
sys.path.append("/usr/local/google_appengine/lib/yaml/lib")    
sys.path.append("/usr/local/google_appengine/lib/webapp2-2.5.2")    
sys.path.append("/usr/local/google_appengine/lib/django-1.5")    
sys.path.append("/usr/local/google_appengine/lib/cherrypy")    
sys.path.append("/usr/local/google_appengine/lib/concurrent")    
sys.path.append("/usr/local/google_appengine/lib/docker")    
sys.path.append("/usr/local/google_appengine/lib/requests")    
sys.path.append("/usr/local/google_appengine/lib/websocket")    
sys.path.append("/usr/local/google_appengine/lib/fancy_urllib")    
sys.path.append("/usr/local/google_appengine/lib/antlr3")    

from selenium import webdriver    
from google.appengine.api import memcache, apiproxy_stub, apiproxy_stub_map   
from google.appengine.ext import db    
from google.appengine.ext import testbed    
from google.appengine.datastore import datastore_stub_util     
from google.appengine.tools.devappserver2 import devappserver2    


class NewVisitorTest(unittest.TestCase):    

    def setUp(self):    
        # Start the dev server  
        cmd = "/usr/local/bin/dev_appserver.py /Users/Bryan/work/GoogleAppEngine/guestbook/app.yaml --port 8080 --storage_path /tmp/datastore --clear_datastore --skip_sdk_update_check"  
        self.dev_appserver = subprocess.Popen(shlex.split(cmd),   
                                              stdout=subprocess.PIPE)  
        time.sleep(2) # Important, let dev_appserver start up  

        self.testbed = testbed.Testbed()
        self.testbed.setup_env(app_id='dermal')    
        self.testbed.activate()    

        self.testbed.init_user_stub()    
        # Create a consistency policy that will simulate the High Replication consistency model.
        # with a probability of 1, the datastore should be available.
        self.policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=1)
        # Initialize the datastore stub with this policy.
        self.testbed.init_datastore_v3_stub(datastore_file="/tmp/datastore/datastore.db", use_sqlite=True, consistency_policy=self.policy)    
        self.testbed.init_memcache_stub()    
        self.datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3')            
        # setup the dev_appserver    
        APP_CONFIGS = ['app.yaml']    

        # setup client to make sure   
        from guestbook import Author, Greeting  
        if not ( Author.query( Author.email == "bryan@mail.com").get()):  
            logging.info("create Admin")  
            client = Author(  
            email = "bryan@mail.com",   
            ).put()
            Assert( Author.query( Author.email == "bryan@mail.com").get() )
        self.browser = webdriver.Firefox()    
        self.browser.implicitly_wait(3)    

    def tearDown(self):    
        self.browser.quit()    
        self.testbed.deactivate()    
        self.dev_appserver.terminate()  

    def test_submit_anon_greeting(self):
          self.browser.get('http://localhost:8080')
          self.browser.find_element_by_name('content').send_keys('Anonymous test post')
          self.browser.find_element_by_name('submit').submit()
          Assert.assertEquals(driver.getPageSource().contains('Anonymous test post'))

这是回溯:

test_submit_anon_greeting (functional_tests.NewVisitorTest) ... INFO     2015-05-11 14:41:40,516 devappserver2.py:745] Skipping SDK update check.  
INFO     2015-05-11 14:41:40,594 api_server.py:190] Starting API server at: http://localhost:59656  
INFO     2015-05-11 14:41:40,598 dispatcher.py:192] Starting module "default" running at: http://localhost:8080  
INFO     2015-05-11 14:41:40,600 admin_server.py:118] Starting admin server at: http://localhost:8000  
WARNING  2015-05-11 14:41:45,008 tasklets.py:409] suspended generator _run_to_list(query.py:964) raised InternalError(table "dev~guestbook!!Entities" already exists)  
ERROR    2015-05-11 14:41:45,009 webapp2.py:1552] table "dev~guestbook!!Entities" already exists  
Traceback (most recent call last):  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1535, in __call__  
    rv = self.handle_exception(request, response, e)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1529, in __call__  
    rv = self.router.dispatch(request, response)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher  
    return route.handler_adapter(request, response)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1102, in __call__  
    return handler.dispatch()  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 572, in dispatch  
    return self.handle_exception(e, self.app.debug)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 570, in dispatch  
    return method(*args, **kwargs)  
  File "/Users/Bryan/work/GoogleAppEngine/guestbook/guestbook.py", line 50, in get  
    greetings = greetings_query.fetch(10)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/utils.py", line 142, in positional_wrapper  
    return wrapped(*args, **kwds)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/query.py", line 1187, in fetch  
    return self.fetch_async(limit, **q_options).get_result()  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/tasklets.py", line 325, in get_result  
    self.check_success()  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/tasklets.py", line 368, in _help_tasklet_along  
    value = gen.throw(exc.__class__, exc, tb)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/query.py", line 964, in _run_to_list  
    batch = yield rpc  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/tasklets.py", line 454, in _on_rpc_completion  
    result = rpc.get_result()  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result  
    return self.__get_result_hook(self)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/datastore/datastore_query.py", line 2870, in __query_result_hook  
    self._batch_shared.conn.check_rpc_success(rpc)  
  File "/Users/Bryan/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/datastore/datastore_rpc.py", line 1342, in check_rpc_success  
    raise _ToDatastoreError(err)  
InternalError: table "dev~guestbook!!Entities" already exists  

我猜这行可能是错误的:

self.testbed.init_datastore_v3_stub(datastore_file="/tmp/datastore/datastore.db", use_sqlite=True, consistency_policy=self.policy)

设置 datastore_file="/tmp/datastore/datastore.db" 表示您想在测试中重用这个现有的数据存储

python 代码文档说:

The 'datastore_file' argument can be the path to an existing datastore file, or None (default) to use an in-memory datastore that is initially empty.

我个人在测试中使用这些:

 def setUp(self):
    self.testbed = testbed.Testbed()
    self.testbed.activate()
    self.testbed.init_datastore_v3_stub(
        consistency_policy=datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=0)
    )
    self.testbed.init_memcache_stub()

def tearDown(self):
    self.testbed.deactivate()

看来这里发生了几件事。

首先,您似乎在使用 NoseGAE --with-gae。该插件负责设置和拆除您的测试台,因此您不必这样做。这意味着您不需要任何 self.testbed 代码,实际上它可能会导致内部冲突。要么切换到 NoseGAE 方式,要么不使用 --with-gae 标志。如果您坚持使用 NoseGAE,它有一个选项 --gae-datastore,可让您设置将用于测试的数据存储的路径。然后在你的测试 class 中,设置 属性 nosegae_datastore_v3 = True 来为你设置它:

class NewVisitorTest(unittest.TestCase):
    # enable the datastore stub
    nosegae_datastore_v3 = True

其次,dev_appserver / sqlite 一起工作的方式,appserver 将 sqlite db 文件加载到内存中并在那里使用它。当应用程序服务器退出时,它会将数据库内容刷新回磁盘。由于您为测试使用的数据存储与您为 selenium 打开的 dev_appserver.py 进程相同,因此它们可能会也可能不会看到您在测试中设置的夹具数据。

这是来自 https://github.com/Trii/NoseGAE/blob/master/nosegae.py#L124-L140

的示例
class MyTest(unittest.TestCase):
    nosegae_datastore_v3 = True
    nosegae_datastore_v3_kwargs = {
        'datastore_file': '/tmp/nosegae.sqlite3',
        'use_sqlite': True
    }
    def test_something(self):
        entity = MyModel(name='NoseGAE')
        entity.put()
        self.assertNotNone(entity.key.id())