等待最终一致性在 Selenium 测试中传播需要多长时间?

How long to wait for Eventual Consistency to propagate in a Selenium test?

我正在尝试创建测试以验证在使用 Selenium 时我的实体是否保存在数据库中。 当我手动输入表单数据时,代码工作正常,但自动测试失败。

当我在代码的post函数中放置断点时,我可以看到保存记录后客户数发生了变化。 我读 https://cloud.google.com/appengine/docs/python/tools/localunittesting#Python_Writing_High_Replication_Datastore_tests

我正在使用我认为具有强一致性的 Ancestor 查询,所以我完全不确定为什么我会遇到这些问题。

据我所知,测试失败是因为最终一致性,解决这个问题的方法是更改​​ PseudoRandomHRConsistencyPolicy 设置。

policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=1)  

当我再次 运行 测试时,我得到了同样的错误。

也有人提出,如果我等一段时间,Eventual Consistency就有时间传播。 这对测试也不起作用。

创建这些测试我做错了什么?

> /Users/Bryan/work/GoogleAppEngine/dermalfillersecrets/main.py(130)post()  
-> customer.put()  
(Pdb) l  
125             customer.phone = self.request.get('id_phone')  
126             customer.zipcode = int(self.request.get('id_zip'))  
127             # show original number of customer to show the code works  
128             starting_customer_count = Customer.query(ancestor=client.key).count()  
129             import pdb; pdb.set_trace()  
130  ->         customer.put()  
131             final_customer_count = Customer.query(ancestor=client.key).count()  
132             import pdb; pdb.set_trace()  
133             query_params = {'leadbook_name': leadbook_name}  
134             self.redirect('/?' + urllib.urlencode(query_params))  
135       
(Pdb) starting_customer_count  
19  
(Pdb) c  
> /Users/Bryan/work/GoogleAppEngine/dermalfillersecrets/main.py(133)post()  
-> query_params = {'leadbook_name': leadbook_name}  
(Pdb) l     
128             starting_customer_count = Customer.query(ancestor=client.key).count()  
129             import pdb; pdb.set_trace()  
130             customer.put()  
131             final_customer_count = Customer.query(ancestor=client.key).count()  
132             import pdb; pdb.set_trace()  
133  ->         query_params = {'leadbook_name': leadbook_name}  
134             self.redirect('/?' + urllib.urlencode(query_params))  
135       
136     config = {}  
137     config['webapp2_extras.sessions'] = {  
138         'secret_key': 'my-super-secret-key',  
(Pdb) final_customer_count  
20  

实体也显示在数据存储区查看器中。

然而,我的测试一直失败。

$ nosetests --with-gae    
test_guest_can_submit_contact_info (dermalfillersecrets.functional_tests.NewVisitorTest) ... FAIL  

======================================================================  
FAIL: test_guest_can_submit_contact_info (dermalfillersecrets.functional_tests.NewVisitorTest)  
----------------------------------------------------------------------  
Traceback (most recent call last):  
  File "/Users/Bryan/work/GoogleAppEngine/dermalfillersecrets/functional_tests.py", line 75, in test_guest_can_submit_contact_info  
    self.assertNotEqual(orig_customer_count, final_customer_count)  
AssertionError: 0 == 0  

这是 functional_tests.py 文件内容:

import os, sys  
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")  

import unittest  
from selenium import webdriver  
from google.appengine.api import memcache  
from google.appengine.ext import db  
from google.appengine.ext import testbed  
import dev_appserver    
from google.appengine.tools.devappserver2 import devappserver2  


class NewVisitorTest(unittest.TestCase):  

    def setUp(self):  
        self.testbed = testbed.Testbed()  
        self.testbed.activate()  
        #self.testbed.setup_env(app_id='dermalfillersecrets')  
        self.testbed.init_user_stub()  
        self.testbed.init_datastore_v3_stub()  
        self.testbed.init_memcache_stub()  

        # setup the dev_appserver  
        APP_CONFIGS = ['app.yaml']  

        self.browser = webdriver.Firefox()  
        self.browser.implicitly_wait(3)  

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


    def test_guest_can_submit_contact_info(self):  
        from main import Client, Customer  
        # below query didn't work because of eventual consistency  
        #query = Customer.query()  
        client = Client.query( Client.name == "Bryan Wheelock").get()  
        orig_customer_count = Customer.query(ancestor=client.key).count()  
        self.browser.get('http://localhost:8080')  
        self.browser.find_element_by_name('id_name').send_keys("Kallie Wheelock")  
        self.browser.find_element_by_name('id_street').send_keys("123 main st")  
        self.browser.find_element_by_name('id_phone').send_keys('(404)555-1212')  
        self.browser.find_element_by_name('id_zip').send_keys("30306")  
        self.browser.find_element_by_name('submit').submit()  
        # this should return 1 more record   
        import time; time.sleep(10)  
        final_customer_count = Customer.query(ancestor=client.key).count()  
        self.assertNotEqual(orig_customer_count, final_customer_count)  
        assert(Customer.query(Customer.name == "Kallie Wheelock").get())  
        # Delete the Customer record  
        Customer.query(Customer.name =="Kallie Wheelock").delete()import os, sys  
        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")  

        import unittest  
        from selenium import webdriver  
        from google.appengine.api import memcache  
        from google.appengine.ext import db  
        from google.appengine.ext import testbed  
        import dev_appserver    
        from google.appengine.tools.devappserver2 import devappserver2  


        class NewVisitorTest(unittest.TestCase):  

            def setUp(self):  
                self.testbed = testbed.Testbed()  
                self.testbed.activate()  
                #self.testbed.setup_env(app_id='dermalfillersecrets')  
                self.testbed.init_user_stub()  
                self.testbed.init_datastore_v3_stub()  
                self.testbed.init_memcache_stub()  

                # setup the dev_appserver  
                APP_CONFIGS = ['app.yaml']  

                self.browser = webdriver.Firefox()  
                self.browser.implicitly_wait(3)  

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

            def test_guest_can_submit_contact_info(self):  
                from main import Client, Customer  

                client = Client.query( Client.name == "Bryan Wheelock").get()  
                orig_customer_count = Customer.query(ancestor=client.key).count()  
                self.browser.get('http://localhost:8080')  
                self.browser.find_element_by_name('id_name').send_keys("Kallie Wheelock")  
                self.browser.find_element_by_name('id_street').send_keys("123 main st")  
                self.browser.find_element_by_name('id_phone').send_keys('(404)555-1212')  
                self.browser.find_element_by_name('id_zip').send_keys("30306")  
                self.browser.find_element_by_name('submit').submit()  
                # this should return 1 more record   
                import time; time.sleep(10)  
                final_customer_count = Customer.query(ancestor=client.key).count()  
                self.assertNotEqual(orig_customer_count, final_customer_count)  
                assert(Customer.query(Customer.name == "Kallie Wheelock").get())  
                # Delete the Customer record  
                Customer.query(Customer.name =="Kallie Wheelock").delete()

你想测试什么?

如果您尝试测试保存实体的代码,那么您可以编写一个简单的单元测试:保存实体,检查它是否已保存。

如果您尝试测试客户端代码,那么引入延迟会破坏此类测试的想法。不保证最终一致性的上限。您的 UI 代码应该能够处理任何时间延迟 - 从 none 到至少几秒。

例如,如果实体的数量对于用户工作流程的延续很重要,大多数应用程序会阻止 UI(例如显示某种进度条或微调器),直到调用成功完全的。如果等待这个实体被保存不是用户工作流程的一部分,那么就没有必要在 Selenium 中对其进行测试——同样,您可以编写一个简单的单元测试。

我认为问题在于您的测试设置,而不是最终一致性。

有不同的数据存储实例,您需要确保使用的是正确的实例:

  1. dev_appserver.py 有自己的数据存储
  2. Testbed 也有自己的数据存储

默认情况下,Testbed 不访问 dev_appserver.py 使用的数据存储,反之亦然。

由于 Selenium 使用 dev_appserver.py,您需要明确告诉 dev_appserver.py 和 Testbed 使用相同的数据存储。

下面是我的测试设置中的一个片段。我不确定它是否会 运行 原样,但它应该能让你朝着正确的方向前进。

请注意,我还明确地启动和停止了 dev_appserver.py。

import subprocess, time, os, unittest, shlex
from google.appengine.ext import db, testbed
from google.appengine.api import apiproxy_stub, apiproxy_stub_map
from selenium import webdriver

class SeleniumTest(unittest.TestCase):

    def setUp(self):
        # Start the dev server
        cmd = "/.../bin/dev_appserver.py /.../app.yaml --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="dev~myapp")
        self.testbed.activate()
        self.testbed.init_app_identity_stub()
        self.testbed.init_datastore_v3_stub(
            datastore_file="/tmp/datastore/datastore.db", use_sqlite=True)
        self.testbed.init_taskqueue_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_blobstore_stub()
        self.testbed.init_user_stub()
        self.testbed.init_mail_stub()
        self.testbed.init_urlfetch_stub()        
        self.taskqueue_stub = apiproxy_stub_map.apiproxy.GetStub('taskqueue')
        self.mail_stub = apiproxy_stub_map.apiproxy.GetStub('mail')
        self.datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3')
        # Start the headless browser for Selenium tests
        self.driver = webdriver.Firefox()

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