Prehistory
Once on a project written in GAE Django, it was necessary to implement testing using Selenium. Unfortunately, it was not possible to find a ready tool for this. Searches across the expanses of the Internet have not yielded positive results.
Wrong turn
The first thing that came to mind was to run the server in setUp using
subprocess.Popen
and complete it in the teardown method
self.server_process.terminate()
At first glance - a working solution. The first question that arose:
How to make a running server instance and testbed use the same database and services?When starting the server, we indicate which database to use:
--use_sqlite --datastore_path=/full/path/to/sqlite_db
We say testbed with which app_id and from which database the test server is launched:
')
self.testbed.setup_env(app_id='some-app-id') self.testbed.init_datastore_v3_stub(use_sqlite=True, datastore_file='/full/path/to/sqlite_db')
It seems to be ready, but there is a problem due, so to speak, to the conflicts of the testbed and our server. The server starts the services it needs, and testbed does the same. In general, "who is the last one and daddy." Could not get a normal working base. As a result, after the selection of the initialization procedure, I succeeded and the first test was successful. It seemed that victory was close, but the thought did not stop me: the further I move with these tests, the deeper I sink into the abyss of "dirty" decisions.
It is necessary to clean the base
In the teardown () method, testbed.deactivate () and test_server.terminate () were called.
The base was not cleared. Adding --clear_datastore to the server startup command broke everything during initialization. Deleting all data from the database and manually clearing the cache also caused conflicts.
db.delete(db.Query()) memcache.flush_all()
I had to add the removal of the os.remove database file, but this did not solve all the problems.
With each solution to one problem, another arose. Hands down, it was getting worse, from what I write. Went to sleep.
All ingenious is simple
Not so simple, but simpler than the one described above. Thanks to my colleague who suggested the idea of using
LiveServerTestCase . It was a salvation.
The base class, which activates the testbed and deactivates it at the end of the entire test case:
class GAELiveServerTestCase(LiveServerTestCase): @classmethod def setUpClass(cls): cls.testbed = testbed.Testbed() cls.testbed.activate() cls.testbed.init_datastore_v3_stub() cls.testbed.init_memcache_stub() cls.testbed.init_channel_stub() cls.testbed.init_urlfetch_stub() cls.testbed.init_user_stub() super(GAELiveServerTestCase, cls).setUpClass() @classmethod def tearDownClass(cls): cls.testbed.deactivate() super(GAELiveServerTestCase, cls).tearDownClass()
Actually class selenium tests.
class SeleniumTestCase(GAELiveServerTestCase): def setUp(self): self.driver = webdriver.Chrome() def tearDown(self): self.driver.close() self.driver.quit() db.delete(db.Query()) memcache.flush_all()
And one important point.
LiveServerTestCase does not run _ah GAE instances. Channels were needed. Having looked in one project and having dug gae-shnyy sorts, an additional view was drawn, one can say with copied logic from gae for jsapi and connect, disconnect, poll signals:
from google.appengine.tools.devappserver2.channel import _JSAPI_PATH def channel_stub_view(request, page): params = request.REQUEST if page == 'jsapi': return HttpResponse(content=open(_JSAPI_PATH).read(), content_type='text/javascript') elif page == 'dev': command = params.get('command', None) token = params.get('channel', None) if command is None or token is None: return HttpResponse(status=400) stub = apiproxy_stub_map.apiproxy.GetStub('channel') try: stub.connect_channel(token) except (channel_service_stub.InvalidTokenError, channel_service_stub.TokenTimedOutError): return HttpResponse(status=401) client_id = stub.validate_token_and_extract_client_id(token) if command == 'connect': return HttpResponse(content='1', content_type='text/plain') elif command == 'poll': message = stub.pop_first_message(token) if message is not None: return HttpResponse(content=message, content_type='application/json') return HttpResponse()
Made available only during tests:
if settings.TESTING: urlpatterns += patterns('', url(r'^_ah/channel/(?P<page>.*)$', 'channel_stub_view'), )
There was one problem when the disconnect should occur (for example, the page was closed), the testbed did not understand this. I solved the problem by initializing channel_stub every time I open the page:
class SeleniumTestCase(GAELiveServerTestCase): def setUp(self): self.driver = webdriver.Chrome() def tearDown(self): self.driver.quit() db.delete(db.Query()) memcache.flush_all() def open_url(self, url): self.get(urljoin(self.live_server_url, url)) self.testbed.init_channel_stub()
PS Wrote a post in the hope that this decision will help someone, and he will not spend a lot of time and nerves on such a task.