diff -r 875bdc0fe8ce -r 105011657405 doc/book/en/development/testing.rst --- a/doc/book/en/development/testing.rst Fri Apr 23 17:07:55 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,258 +0,0 @@ -.. -*- coding: utf-8 -*- - -Tests -===== - -Unit tests ----------- - -The *CubicWeb* framework provides the `CubicWebTC` test base class in -the module `cubicweb.devtools.testlib`. - -Tests shall be put into the mycube/test directory. Additional test -data shall go into mycube/test/data. - -It is much advised to write tests concerning entities methods, hooks -and operations, security. The CubicWebTC base class has convenience -methods to help test all of this. - -.. note:: - - In the realm of views, there is not much to do but check that the - views are valid XHTML. See :ref:`automatic_views_tests` for - details. Integration of CubicWeb tests with UI testing tools such as - `selenium`_ are currently under invesitgation. - -.. _selenium: http://seleniumhq.org/projects/ide/ - -Most unit tests need a live database to work against. This is achieved -by CubicWeb using automatically sqlite (bundled with Python, see -http://docs.python.org/library/sqlite3.html) as a backend. - -The database is stored in the mycube/test/tmpdb, -mycube/test/tmpdb-template files. If it does not (yet) exists, it will -be built automatically when the test suit starts. - -.. warning:: - - Whenever the schema changes (new entities, attributes, relations) - one must delete these two files. Changes concerned only with entity - or relation type properties (constraints, cardinalities, - permissions) and generally dealt with using the - `sync_schema_props_perms()` fonction of the migration environment - need not a database regeneration step. - -Unit test by example -```````````````````` - -We start with an example extracted from the keyword cube (available -from http://www.cubicweb.org/project/cubicweb-keyword). - -.. sourcecode:: python - - from cubicweb.devtools.testlib import CubicWebTC - from cubicweb import ValidationError - - class ClassificationHooksTC(CubicWebTC): - - def setup_database(self): - req = self.request() - group_etype = req.execute('Any X WHERE X name "CWGroup"').get_entity(0,0) - c1 = req.create_entity('Classification', name=u'classif1', - classifies=group_etype) - user_etype = req.execute('Any X WHERE X name "CWUser"').get_entity(0,0) - c2 = req.create_entity('Classification', name=u'classif2', - classifies=user_etype) - self.kw1 = req.create_entity('Keyword', name=u'kwgroup', included_in=c1) - self.kw2 = req.create_entity('Keyword', name=u'kwuser', included_in=c2) - - def test_cannot_create_cycles(self): - # direct obvious cycle - self.assertRaises(ValidationError, self.kw1.set_relations, - subkeyword_of=self.kw1) - # testing indirect cycles - kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, ' - 'SK subkeyword_of K WHERE C name "classif1", K eid %s' - % self.kw1.eid).get_entity(0,0) - self.kw1.set_relations(subkeyword_of=kw3) - self.assertRaises(ValidationError, self.commit) - -The test class defines a `setup_database` method which populates the -database with initial data. Each test of the class runs with this -pre-populated database. - -The test case itself checks that an Operation does it job of -preventing cycles amongst Keyword entities. - -You can see an example of security tests in the -:ref:`adv_tuto_security`. - -It is possible to have these tests run continuously using `apycot`_. - -.. _apycot: http://www.logilab.org/project/apycot - -Managing connections or users -+++++++++++++++++++++++++++++ - -Since unit tests are done with the SQLITE backend and this does not -support multiple connections at a time, you must be careful when -simulating security, changing users. - -By default, tests run with a user with admin privileges. This -user/connection must never be closed. - -Before a self.login, one has to release the connection pool in use -with a self.commit, self.rollback or self.close. - -The `login` method returns a connection object that can be used as a -context manager: - -.. sourcecode:: python - - with self.login('user1') as user: - req = user.req - req.execute(...) - -On exit of the context manager, either a commit or rollback is issued, -which releases the connection. - -When one is logged in as a normal user and wants to switch back to the -admin user without committing, one has to use -self.restore_connection(). - -Usage with restore_connection: - -.. sourcecode:: python - - # execute using default admin connection - self.execute(...) - # I want to login with another user, ensure to free admin connection pool - # (could have used rollback but not close here - # we should never close defaut admin connection) - self.commit() - cnx = self.login('user') - # execute using user connection - self.execute(...) - # I want to login with another user or with admin user - self.commit(); cnx.close() - # restore admin connection, never use cnx = self.login('admin'), it will return - # the default admin connection and one may be tempted to close it - self.restore_connection() - -.. warning:: - - Do not use the references kept to the entities created with a - connection from another ! - -Email notifications tests -------------------------- - -When running tests potentially generated e-mails are not really sent -but is found in the list `MAILBOX` of module -`cubicweb.devtools.testlib`. - -You can test your notifications by analyzing the contents of this list, which -contains objects with two attributes: - -* `recipients`, the list of recipients -* `msg`, object email.Message - -Let us look at simple example from the ``blog`` cube. - -.. sourcecode:: python - - from cubicweb.devtools.testlib import CubicWebTC, MAILBOX - - class BlogTestsCubicWebTC(CubicWebTC): - """test blog specific behaviours""" - - def test_notifications(self): - req = self.request() - cubicweb_blog = req.create_entity('Blog', title=u'cubicweb', - description=u'cubicweb is beautiful') - blog_entry_1 = req.create_entity('BlogEntry', title=u'hop', - content=u'cubicweb hop') - blog_entry_1.set_relations(entry_of=cubicweb_blog) - blog_entry_2 = req.create_entity('BlogEntry', title=u'yes', - content=u'cubicweb yes') - blog_entry_2.set_relations(entry_of=cubicweb_blog) - self.assertEquals(len(MAILBOX), 0) - self.commit() - self.assertEquals(len(MAILBOX), 2) - mail = MAILBOX[0] - self.assertEquals(mail.subject, '[data] hop') - mail = MAILBOX[1] - self.assertEquals(mail.subject, '[data] yes') - -.. _automatic_views_tests: - -Automatic views testing ------------------------ - -This is done automatically with the AutomaticWebTest class. At cube -creation time, the mycube/test/test_mycube.py file contains such a -test. The code here has to be uncommented to be usable, without -further modification. - -The ``auto_populate`` method uses a smart algorithm to create -pseudo-random data in the database, thus enabling the views to be -invoked and tested. - -Depending on the schema, hooks and operations constraints, it is not -always possible for the automatic auto_populate to proceed. - -It is possible of course to completely redefine auto_populate. A -lighter solution is to give hints (fill some class attributes) about -what entities and relations have to be skipped by the auto_populate -mechanism. These are: - -* `no_auto_populate`, may contain a list of entity types to skip -* `ignored_relations`, may contain a list of relation types to skip -* `application_rql`, may contain a list of rql expressions that - auto_populate cannot guess by itself; these must yield resultsets - against which views may be selected. - - -Test APIS ---------- - -Using Pytest -```````````` - -The `pytest` utility (shipping with `logilab-common`_, which is a -mandatory dependency of CubicWeb) extends the Python unittest -functionality and is the preferred way to run the CubicWeb test -suites. Bare unittests also work the usual way. - -.. _logilab-common: http://www.logilab.org/project/logilab-common - -To use it, you may: - -* just launch `pytest` in your cube to execute all tests (it will - discover them automatically) -* launch `pytest unittest_foo.py` to execute one test file -* launch `pytest unittest_foo.py bar` to execute all test methods and - all test cases whose name contain `bar` - -Additionally, the `-x` option tells pytest to exit at the first error -or failure. The `-i` option tells pytest to drop into pdb whenever an -exception occurs in a test. - -When the `-x` option has been used and the run stopped on a test, it -is possible, after having fixed the test, to relaunch pytest with the -`-R` option to tell it to start testing again from where it previously -failed. - -Using the `TestCase` base class -``````````````````````````````` - -The base class of CubicWebTC is logilab.common.testlib.TestCase, which -provides a lot of convenient assertion methods. - -.. autoclass:: logilab.common.testlib.TestCase - :members: - -CubicWebTC API -`````````````` -.. autoclass:: cubicweb.devtools.testlib.CubicWebTC - :members: