1 .. -*- coding: utf-8 -*- |
|
2 |
|
3 Tests |
|
4 ===== |
|
5 |
|
6 Unit tests |
|
7 ---------- |
|
8 |
|
9 The *CubicWeb* framework provides the `CubicWebTC` test base class in |
|
10 the module `cubicweb.devtools.testlib`. |
|
11 |
|
12 Tests shall be put into the mycube/test directory. Additional test |
|
13 data shall go into mycube/test/data. |
|
14 |
|
15 It is much advised to write tests concerning entities methods, hooks |
|
16 and operations, security. The CubicWebTC base class has convenience |
|
17 methods to help test all of this. |
|
18 |
|
19 .. note:: |
|
20 |
|
21 In the realm of views, there is not much to do but check that the |
|
22 views are valid XHTML. See :ref:`automatic_views_tests` for |
|
23 details. Integration of CubicWeb tests with UI testing tools such as |
|
24 `selenium`_ are currently under invesitgation. |
|
25 |
|
26 .. _selenium: http://seleniumhq.org/projects/ide/ |
|
27 |
|
28 Most unit tests need a live database to work against. This is achieved |
|
29 by CubicWeb using automatically sqlite (bundled with Python, see |
|
30 http://docs.python.org/library/sqlite3.html) as a backend. |
|
31 |
|
32 The database is stored in the mycube/test/tmpdb, |
|
33 mycube/test/tmpdb-template files. If it does not (yet) exists, it will |
|
34 be built automatically when the test suit starts. |
|
35 |
|
36 .. warning:: |
|
37 |
|
38 Whenever the schema changes (new entities, attributes, relations) |
|
39 one must delete these two files. Changes concerned only with entity |
|
40 or relation type properties (constraints, cardinalities, |
|
41 permissions) and generally dealt with using the |
|
42 `sync_schema_props_perms()` fonction of the migration environment |
|
43 need not a database regeneration step. |
|
44 |
|
45 Unit test by example |
|
46 ```````````````````` |
|
47 |
|
48 We start with an example extracted from the keyword cube (available |
|
49 from http://www.cubicweb.org/project/cubicweb-keyword). |
|
50 |
|
51 .. sourcecode:: python |
|
52 |
|
53 from cubicweb.devtools.testlib import CubicWebTC |
|
54 from cubicweb import ValidationError |
|
55 |
|
56 class ClassificationHooksTC(CubicWebTC): |
|
57 |
|
58 def setup_database(self): |
|
59 req = self.request() |
|
60 group_etype = req.execute('Any X WHERE X name "CWGroup"').get_entity(0,0) |
|
61 c1 = req.create_entity('Classification', name=u'classif1', |
|
62 classifies=group_etype) |
|
63 user_etype = req.execute('Any X WHERE X name "CWUser"').get_entity(0,0) |
|
64 c2 = req.create_entity('Classification', name=u'classif2', |
|
65 classifies=user_etype) |
|
66 self.kw1 = req.create_entity('Keyword', name=u'kwgroup', included_in=c1) |
|
67 self.kw2 = req.create_entity('Keyword', name=u'kwuser', included_in=c2) |
|
68 |
|
69 def test_cannot_create_cycles(self): |
|
70 # direct obvious cycle |
|
71 self.assertRaises(ValidationError, self.kw1.set_relations, |
|
72 subkeyword_of=self.kw1) |
|
73 # testing indirect cycles |
|
74 kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, ' |
|
75 'SK subkeyword_of K WHERE C name "classif1", K eid %s' |
|
76 % self.kw1.eid).get_entity(0,0) |
|
77 self.kw1.set_relations(subkeyword_of=kw3) |
|
78 self.assertRaises(ValidationError, self.commit) |
|
79 |
|
80 The test class defines a `setup_database` method which populates the |
|
81 database with initial data. Each test of the class runs with this |
|
82 pre-populated database. |
|
83 |
|
84 The test case itself checks that an Operation does it job of |
|
85 preventing cycles amongst Keyword entities. |
|
86 |
|
87 You can see an example of security tests in the |
|
88 :ref:`adv_tuto_security`. |
|
89 |
|
90 It is possible to have these tests run continuously using `apycot`_. |
|
91 |
|
92 .. _apycot: http://www.logilab.org/project/apycot |
|
93 |
|
94 Managing connections or users |
|
95 +++++++++++++++++++++++++++++ |
|
96 |
|
97 Since unit tests are done with the SQLITE backend and this does not |
|
98 support multiple connections at a time, you must be careful when |
|
99 simulating security, changing users. |
|
100 |
|
101 By default, tests run with a user with admin privileges. This |
|
102 user/connection must never be closed. |
|
103 |
|
104 Before a self.login, one has to release the connection pool in use |
|
105 with a self.commit, self.rollback or self.close. |
|
106 |
|
107 The `login` method returns a connection object that can be used as a |
|
108 context manager: |
|
109 |
|
110 .. sourcecode:: python |
|
111 |
|
112 with self.login('user1') as user: |
|
113 req = user.req |
|
114 req.execute(...) |
|
115 |
|
116 On exit of the context manager, either a commit or rollback is issued, |
|
117 which releases the connection. |
|
118 |
|
119 When one is logged in as a normal user and wants to switch back to the |
|
120 admin user without committing, one has to use |
|
121 self.restore_connection(). |
|
122 |
|
123 Usage with restore_connection: |
|
124 |
|
125 .. sourcecode:: python |
|
126 |
|
127 # execute using default admin connection |
|
128 self.execute(...) |
|
129 # I want to login with another user, ensure to free admin connection pool |
|
130 # (could have used rollback but not close here |
|
131 # we should never close defaut admin connection) |
|
132 self.commit() |
|
133 cnx = self.login('user') |
|
134 # execute using user connection |
|
135 self.execute(...) |
|
136 # I want to login with another user or with admin user |
|
137 self.commit(); cnx.close() |
|
138 # restore admin connection, never use cnx = self.login('admin'), it will return |
|
139 # the default admin connection and one may be tempted to close it |
|
140 self.restore_connection() |
|
141 |
|
142 .. warning:: |
|
143 |
|
144 Do not use the references kept to the entities created with a |
|
145 connection from another ! |
|
146 |
|
147 Email notifications tests |
|
148 ------------------------- |
|
149 |
|
150 When running tests potentially generated e-mails are not really sent |
|
151 but is found in the list `MAILBOX` of module |
|
152 `cubicweb.devtools.testlib`. |
|
153 |
|
154 You can test your notifications by analyzing the contents of this list, which |
|
155 contains objects with two attributes: |
|
156 |
|
157 * `recipients`, the list of recipients |
|
158 * `msg`, object email.Message |
|
159 |
|
160 Let us look at simple example from the ``blog`` cube. |
|
161 |
|
162 .. sourcecode:: python |
|
163 |
|
164 from cubicweb.devtools.testlib import CubicWebTC, MAILBOX |
|
165 |
|
166 class BlogTestsCubicWebTC(CubicWebTC): |
|
167 """test blog specific behaviours""" |
|
168 |
|
169 def test_notifications(self): |
|
170 req = self.request() |
|
171 cubicweb_blog = req.create_entity('Blog', title=u'cubicweb', |
|
172 description=u'cubicweb is beautiful') |
|
173 blog_entry_1 = req.create_entity('BlogEntry', title=u'hop', |
|
174 content=u'cubicweb hop') |
|
175 blog_entry_1.set_relations(entry_of=cubicweb_blog) |
|
176 blog_entry_2 = req.create_entity('BlogEntry', title=u'yes', |
|
177 content=u'cubicweb yes') |
|
178 blog_entry_2.set_relations(entry_of=cubicweb_blog) |
|
179 self.assertEquals(len(MAILBOX), 0) |
|
180 self.commit() |
|
181 self.assertEquals(len(MAILBOX), 2) |
|
182 mail = MAILBOX[0] |
|
183 self.assertEquals(mail.subject, '[data] hop') |
|
184 mail = MAILBOX[1] |
|
185 self.assertEquals(mail.subject, '[data] yes') |
|
186 |
|
187 .. _automatic_views_tests: |
|
188 |
|
189 Automatic views testing |
|
190 ----------------------- |
|
191 |
|
192 This is done automatically with the AutomaticWebTest class. At cube |
|
193 creation time, the mycube/test/test_mycube.py file contains such a |
|
194 test. The code here has to be uncommented to be usable, without |
|
195 further modification. |
|
196 |
|
197 The ``auto_populate`` method uses a smart algorithm to create |
|
198 pseudo-random data in the database, thus enabling the views to be |
|
199 invoked and tested. |
|
200 |
|
201 Depending on the schema, hooks and operations constraints, it is not |
|
202 always possible for the automatic auto_populate to proceed. |
|
203 |
|
204 It is possible of course to completely redefine auto_populate. A |
|
205 lighter solution is to give hints (fill some class attributes) about |
|
206 what entities and relations have to be skipped by the auto_populate |
|
207 mechanism. These are: |
|
208 |
|
209 * `no_auto_populate`, may contain a list of entity types to skip |
|
210 * `ignored_relations`, may contain a list of relation types to skip |
|
211 * `application_rql`, may contain a list of rql expressions that |
|
212 auto_populate cannot guess by itself; these must yield resultsets |
|
213 against which views may be selected. |
|
214 |
|
215 |
|
216 Test APIS |
|
217 --------- |
|
218 |
|
219 Using Pytest |
|
220 ```````````` |
|
221 |
|
222 The `pytest` utility (shipping with `logilab-common`_, which is a |
|
223 mandatory dependency of CubicWeb) extends the Python unittest |
|
224 functionality and is the preferred way to run the CubicWeb test |
|
225 suites. Bare unittests also work the usual way. |
|
226 |
|
227 .. _logilab-common: http://www.logilab.org/project/logilab-common |
|
228 |
|
229 To use it, you may: |
|
230 |
|
231 * just launch `pytest` in your cube to execute all tests (it will |
|
232 discover them automatically) |
|
233 * launch `pytest unittest_foo.py` to execute one test file |
|
234 * launch `pytest unittest_foo.py bar` to execute all test methods and |
|
235 all test cases whose name contain `bar` |
|
236 |
|
237 Additionally, the `-x` option tells pytest to exit at the first error |
|
238 or failure. The `-i` option tells pytest to drop into pdb whenever an |
|
239 exception occurs in a test. |
|
240 |
|
241 When the `-x` option has been used and the run stopped on a test, it |
|
242 is possible, after having fixed the test, to relaunch pytest with the |
|
243 `-R` option to tell it to start testing again from where it previously |
|
244 failed. |
|
245 |
|
246 Using the `TestCase` base class |
|
247 ``````````````````````````````` |
|
248 |
|
249 The base class of CubicWebTC is logilab.common.testlib.TestCase, which |
|
250 provides a lot of convenient assertion methods. |
|
251 |
|
252 .. autoclass:: logilab.common.testlib.TestCase |
|
253 :members: |
|
254 |
|
255 CubicWebTC API |
|
256 `````````````` |
|
257 .. autoclass:: cubicweb.devtools.testlib.CubicWebTC |
|
258 :members: |
|