698 lines
24 KiB
ReStructuredText
698 lines
24 KiB
ReStructuredText
.. highlight:: javascript
|
|
|
|
.. _testing:
|
|
|
|
Testing in OpenERP Web
|
|
======================
|
|
|
|
Javascript Unit Testing
|
|
-----------------------
|
|
|
|
OpenERP Web 7.0 includes means to unit-test both the core code of
|
|
OpenERP Web and your own javascript modules. On the javascript side,
|
|
unit-testing is based on QUnit_ with a number of helpers and
|
|
extensions for better integration with OpenERP.
|
|
|
|
To see what the runner looks like, find (or start) an OpenERP server
|
|
with the web client enabled, and navigate to ``/web/tests`` e.g. `on
|
|
OpenERP's CI <http://trunk.runbot.openerp.com/web/tests>`_. This will
|
|
show the runner selector, which lists all modules with javascript unit
|
|
tests, and allows starting any of them (or all javascript tests in all
|
|
modules at once).
|
|
|
|
.. image:: ./images/runner.png
|
|
:align: center
|
|
|
|
Clicking any runner button will launch the corresponding tests in the
|
|
bundled QUnit_ runner:
|
|
|
|
.. image:: ./images/tests.png
|
|
:align: center
|
|
|
|
Writing a test case
|
|
-------------------
|
|
|
|
The first step is to list the test file(s). This is done through the
|
|
``test`` key of the openerp manifest, by adding javascript files to it
|
|
(next to the usual YAML files, if any):
|
|
|
|
.. code-block:: python
|
|
|
|
{
|
|
'name': "Demonstration of web/javascript tests",
|
|
'category': 'Hidden',
|
|
'depends': ['web'],
|
|
'test': ['static/test/demo.js'],
|
|
}
|
|
|
|
and to create the corresponding test file(s)
|
|
|
|
.. note::
|
|
|
|
Test files which do not exist will be ignored, if all test files
|
|
of a module are ignored (can not be found), the test runner will
|
|
consider that the module has no javascript tests.
|
|
|
|
After that, refreshing the runner selector will display the new module
|
|
and allow running all of its (0 so far) tests:
|
|
|
|
.. image:: ./images/runner2.png
|
|
:align: center
|
|
|
|
The next step is to create a test case::
|
|
|
|
openerp.testing.section('basic section', function (test) {
|
|
test('my first test', function () {
|
|
ok(false, "this test has run");
|
|
});
|
|
});
|
|
|
|
All testing helpers and structures live in the ``openerp.testing``
|
|
module. OpenERP tests live in a :js:func:`~openerp.testing.section`,
|
|
which is itself part of a module. The first argument to a section is
|
|
the name of the section, the second one is the section body.
|
|
|
|
:js:func:`test <openerp.testing.case>`, provided by the
|
|
:js:func:`~openerp.testing.section` to the callback, is used to
|
|
register a given test case which will be run whenever the test runner
|
|
actually does its job. OpenERP Web test case use standard `QUnit
|
|
assertions`_ within them.
|
|
|
|
Launching the test runner at this point will run the test and display
|
|
the corresponding assertion message, with red colors indicating the
|
|
test failed:
|
|
|
|
.. image:: ./images/tests2.png
|
|
:align: center
|
|
|
|
Fixing the test (by replacing ``false`` to ``true`` in the assertion)
|
|
will make it pass:
|
|
|
|
.. image:: ./images/tests3.png
|
|
:align: center
|
|
|
|
Assertions
|
|
----------
|
|
|
|
As noted above, OpenERP Web's tests use `qunit assertions`_. They are
|
|
available globally (so they can just be called without references to
|
|
anything). The following list is available:
|
|
|
|
.. js:function:: ok(state[, message])
|
|
|
|
checks that ``state`` is truthy (in the javascript sense)
|
|
|
|
.. js:function:: strictEqual(actual, expected[, message])
|
|
|
|
checks that the actual (produced by a method being tested) and
|
|
expected values are identical (roughly equivalent to ``ok(actual
|
|
=== expected, message)``)
|
|
|
|
.. js:function:: notStrictEqual(actual, expected[, message])
|
|
|
|
checks that the actual and expected values are *not* identical
|
|
(roughly equivalent to ``ok(actual !== expected, message)``)
|
|
|
|
.. js:function:: deepEqual(actual, expected[, message])
|
|
|
|
deep comparison between actual and expected: recurse into
|
|
containers (objects and arrays) to ensure that they have the same
|
|
keys/number of elements, and the values match.
|
|
|
|
.. js:function:: notDeepEqual(actual, expected[, message])
|
|
|
|
inverse operation to :js:func:`deepEqual`
|
|
|
|
.. js:function:: throws(block[, expected][, message])
|
|
|
|
checks that, when called, the ``block`` throws an
|
|
error. Optionally validates that error against ``expected``.
|
|
|
|
:param Function block:
|
|
:param expected: if a regexp, checks that the thrown error's
|
|
message matches the regular expression. If an
|
|
error type, checks that the thrown error is of
|
|
that type.
|
|
:type expected: Error | RegExp
|
|
|
|
.. js:function:: equal(actual, expected[, message])
|
|
|
|
checks that ``actual`` and ``expected`` are loosely equal, using
|
|
the ``==`` operator and its coercion rules.
|
|
|
|
.. js:function:: notEqual(actual, expected[, message])
|
|
|
|
inverse operation to :js:func:`equal`
|
|
|
|
Getting an OpenERP instance
|
|
---------------------------
|
|
|
|
The OpenERP instance is the base through which most OpenERP Web
|
|
modules behaviors (functions, objects, …) are accessed. As a result,
|
|
the test framework automatically builds one, and loads the module
|
|
being tested and all of its dependencies inside it. This new instance
|
|
is provided as the first positional parameter to your test
|
|
cases. Let's observe by adding javascript code (not test code) to the
|
|
test module:
|
|
|
|
.. code-block:: python
|
|
|
|
{
|
|
'name': "Demonstration of web/javascript tests",
|
|
'category': 'Hidden',
|
|
'depends': ['web'],
|
|
'js': ['static/src/js/demo.js'],
|
|
'test': ['static/test/demo.js'],
|
|
}
|
|
|
|
::
|
|
|
|
// src/js/demo.js
|
|
openerp.web_tests_demo = function (instance) {
|
|
instance.web_tests_demo = {
|
|
value_true: true,
|
|
SomeType: instance.web.Class.extend({
|
|
init: function (value) {
|
|
this.value = value;
|
|
}
|
|
})
|
|
};
|
|
};
|
|
|
|
and then adding a new test case, which simply checks that the
|
|
``instance`` contains all the expected stuff we created in the
|
|
module::
|
|
|
|
// test/demo.js
|
|
test('module content', function (instance) {
|
|
ok(instance.web_tests_demo.value_true, "should have a true value");
|
|
var type_instance = new instance.web_tests_demo.SomeType(42);
|
|
strictEqual(type_instance.value, 42, "should have provided value");
|
|
});
|
|
|
|
DOM Scratchpad
|
|
--------------
|
|
|
|
As in the wider client, arbitrarily accessing document content is
|
|
strongly discouraged during tests. But DOM access is still needed to
|
|
e.g. fully initialize :js:class:`widgets <~openerp.web.Widget>` before
|
|
testing them.
|
|
|
|
Thus, a test case gets a DOM scratchpad as its second positional
|
|
parameter, in a jQuery instance. That scratchpad is fully cleaned up
|
|
before each test, and as long as it doesn't do anything outside the
|
|
scratchpad your code can do whatever it wants::
|
|
|
|
// test/demo.js
|
|
test('DOM content', function (instance, $scratchpad) {
|
|
$scratchpad.html('<div><span class="foo bar">ok</span></div>');
|
|
ok($scratchpad.find('span').hasClass('foo'),
|
|
"should have provided class");
|
|
});
|
|
test('clean scratchpad', function (instance, $scratchpad) {
|
|
ok(!$scratchpad.children().length, "should have no content");
|
|
ok(!$scratchpad.text(), "should have no text");
|
|
});
|
|
|
|
.. note::
|
|
|
|
The top-level element of the scratchpad is not cleaned up, test
|
|
cases can add text or DOM children but shoud not alter
|
|
``$scratchpad`` itself.
|
|
|
|
Loading templates
|
|
-----------------
|
|
|
|
To avoid the corresponding processing costs, by default templates are
|
|
not loaded into QWeb. If you need to render e.g. widgets making use of
|
|
QWeb templates, you can request their loading through the
|
|
:js:attr:`~TestOptions.templates` option to the :js:func:`test case
|
|
function <openerp.testing.case>`.
|
|
|
|
This will automatically load all relevant templates in the instance's
|
|
qweb before running the test case:
|
|
|
|
.. code-block:: python
|
|
|
|
{
|
|
'name': "Demonstration of web/javascript tests",
|
|
'category': 'Hidden',
|
|
'depends': ['web'],
|
|
'js': ['static/src/js/demo.js'],
|
|
'test': ['static/test/demo.js'],
|
|
'qweb': ['static/src/xml/demo.xml'],
|
|
}
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- src/xml/demo.xml -->
|
|
<templates id="template" xml:space="preserve">
|
|
<t t-name="DemoTemplate">
|
|
<t t-foreach="5" t-as="value">
|
|
<p><t t-esc="value"/></p>
|
|
</t>
|
|
</t>
|
|
</templates>
|
|
|
|
::
|
|
|
|
// test/demo.js
|
|
test('templates', {templates: true}, function (instance) {
|
|
var s = instance.web.qweb.render('DemoTemplate');
|
|
var texts = $(s).find('p').map(function () {
|
|
return $(this).text();
|
|
}).get();
|
|
|
|
deepEqual(texts, ['0', '1', '2', '3', '4']);
|
|
});
|
|
|
|
Asynchronous cases
|
|
------------------
|
|
|
|
The test case examples so far are all synchronous, they execute from
|
|
the first to the last line and once the last line has executed the
|
|
test is done. But the web client is full of :doc:`asynchronous code
|
|
</async>`, and thus test cases need to be async-aware.
|
|
|
|
This is done by returning a :js:class:`deferred <Deferred>` from the
|
|
case callback::
|
|
|
|
// test/demo.js
|
|
test('asynchronous', {
|
|
asserts: 1
|
|
}, function () {
|
|
var d = $.Deferred();
|
|
setTimeout(function () {
|
|
ok(true);
|
|
d.resolve();
|
|
}, 100);
|
|
return d;
|
|
});
|
|
|
|
This example also uses the :js:class:`options parameter <TestOptions>`
|
|
to specify the number of assertions the case should expect, if less or
|
|
more assertions are specified the case will count as failed.
|
|
|
|
Asynchronous test cases *must* specify the number of assertions they
|
|
will run. This allows more easily catching situations where e.g. the
|
|
test architecture was not warned about asynchronous operations.
|
|
|
|
.. note::
|
|
|
|
Asynchronous test cases also have a 2 seconds timeout: if the test
|
|
does not finish within 2 seconds, it will be considered
|
|
failed. This pretty much always means the test will not
|
|
resolve. This timeout *only* applies to the test itself, not to
|
|
the setup and teardown processes.
|
|
|
|
.. note::
|
|
|
|
If the returned deferred is rejected, the test will be failed
|
|
unless :js:attr:`~TestOptions.fail_on_rejection` is set to
|
|
``false``.
|
|
|
|
RPC
|
|
---
|
|
|
|
An important subset of asynchronous test cases is test cases which
|
|
need to perform (and chain, to an extent) RPC calls.
|
|
|
|
.. note::
|
|
|
|
Because they are a subset of asynchronous cases, RPC cases must
|
|
also provide a valid :js:attr:`assertions count
|
|
<TestOptions.asserts>`.
|
|
|
|
By default, test cases will fail when trying to perform an RPC
|
|
call. The ability to perform RPC calls must be explicitly requested by
|
|
a test case (or its containing test suite) through
|
|
:js:attr:`~TestOptions.rpc`, and can be one of two modes: ``mock`` or
|
|
``rpc``.
|
|
|
|
.. _testing-rpc-mock:
|
|
|
|
Mock RPC
|
|
++++++++
|
|
|
|
The preferred (and fastest from a setup and execution time point of
|
|
view) way to do RPC during tests is to mock the RPC calls: while
|
|
setting up the test case, provide what the RPC responses "should" be,
|
|
and only test the code between the "user" (the test itself) and the
|
|
RPC call, before the call is effectively done.
|
|
|
|
To do this, set the :js:attr:`rpc option <TestOptions.rpc>` to
|
|
``mock``. This will add a third parameter to the test case callback:
|
|
|
|
.. js:function:: mock(rpc_spec, handler)
|
|
|
|
Can be used in two different ways depending on the shape of the
|
|
first parameter:
|
|
|
|
* If it matches the pattern ``model:method`` (if it contains a
|
|
colon, essentially) the call will set up the mocking of an RPC
|
|
call straight to the OpenERP server (through XMLRPC) as
|
|
performed via e.g. :js:func:`openerp.web.Model.call`.
|
|
|
|
In that case, ``handler`` should be a function taking two
|
|
arguments ``args`` and ``kwargs``, matching the corresponding
|
|
arguments on the server side and should simply return the value
|
|
as if it were returned by the Python XMLRPC handler::
|
|
|
|
test('XML-RPC', {rpc: 'mock', asserts: 3}, function (instance, $s, mock) {
|
|
// set up mocking
|
|
mock('people.famous:name_search', function (args, kwargs) {
|
|
strictEqual(kwargs.name, 'bob');
|
|
return [
|
|
[1, "Microsoft Bob"],
|
|
[2, "Bob the Builder"],
|
|
[3, "Silent Bob"]
|
|
];
|
|
});
|
|
|
|
// actual test code
|
|
return new instance.web.Model('people.famous')
|
|
.call('name_search', {name: 'bob'}).then(function (result) {
|
|
strictEqual(result.length, 3, "shoud return 3 people");
|
|
strictEqual(result[0][1], "Microsoft Bob",
|
|
"the most famous bob should be Microsoft Bob");
|
|
});
|
|
});
|
|
|
|
* Otherwise, if it matches an absolute path (e.g. ``/a/b/c``) it
|
|
will mock a JSON-RPC call to a web client controller, such as
|
|
``/web/webclient/translations``. In that case, the handler takes
|
|
a single ``params`` argument holding all of the parameters
|
|
provided over JSON-RPC.
|
|
|
|
As previously, the handler should simply return the result value
|
|
as if returned by the original JSON-RPC handler::
|
|
|
|
test('JSON-RPC', {rpc: 'mock', asserts: 3, templates: true}, function (instance, $s, mock) {
|
|
var fetched_dbs = false, fetched_langs = false;
|
|
mock('/web/database/get_list', function () {
|
|
fetched_dbs = true;
|
|
return ['foo', 'bar', 'baz'];
|
|
});
|
|
mock('/web/session/get_lang_list', function () {
|
|
fetched_langs = true;
|
|
return [['vo_IS', 'Hopelandic / Vonlenska']];
|
|
});
|
|
|
|
// widget needs that or it blows up
|
|
instance.webclient = {toggle_bars: openerp.testing.noop};
|
|
var dbm = new instance.web.DatabaseManager({});
|
|
return dbm.appendTo($s).then(function () {
|
|
ok(fetched_dbs, "should have fetched databases");
|
|
ok(fetched_langs, "should have fetched languages");
|
|
deepEqual(dbm.db_list, ['foo', 'bar', 'baz']);
|
|
});
|
|
});
|
|
|
|
.. note::
|
|
|
|
Mock handlers can contain assertions, these assertions should be
|
|
part of the assertions count (and if multiple calls are made to a
|
|
handler containing assertions, it multiplies the effective number
|
|
of assertions).
|
|
|
|
.. _testing-rpc-rpc:
|
|
|
|
Actual RPC
|
|
++++++++++
|
|
|
|
A more realistic (but significantly slower and more expensive) way to
|
|
perform RPC calls is to perform actual calls to an actually running
|
|
OpenERP server. To do this, set the :js:attr:`rpc option
|
|
<~TestOptions.rpc>` to ``rpc``, it will not provide any new parameter
|
|
but will enable actual RPC, and the automatic creation and destruction
|
|
of databases (from a specified source) around tests.
|
|
|
|
First, create a basic model we can test stuff with:
|
|
|
|
.. code-block:: javascript
|
|
|
|
from openerp.osv import orm, fields
|
|
|
|
class TestObject(orm.Model):
|
|
_name = 'web_tests_demo.model'
|
|
|
|
_columns = {
|
|
'name': fields.char("Name", required=True),
|
|
'thing': fields.char("Thing"),
|
|
'other': fields.char("Other", required=True)
|
|
}
|
|
_defaults = {
|
|
'other': "bob"
|
|
}
|
|
|
|
then the actual test::
|
|
|
|
test('actual RPC', {rpc: 'rpc', asserts: 4}, function (instance) {
|
|
var Model = new instance.web.Model('web_tests_demo.model');
|
|
return Model.call('create', [{name: "Bob"}])
|
|
.then(function (id) {
|
|
return Model.call('read', [[id]]);
|
|
}).then(function (records) {
|
|
strictEqual(records.length, 1);
|
|
var record = records[0];
|
|
strictEqual(record.name, "Bob");
|
|
strictEqual(record.thing, false);
|
|
// default value
|
|
strictEqual(record.other, 'bob');
|
|
});
|
|
});
|
|
|
|
This test looks like a "mock" RPC test but for the lack of mock
|
|
response (and the different ``rpc`` type), however it has further
|
|
ranging consequences in that it will copy an existing database to a
|
|
new one, run the test in full on that temporary database and destroy
|
|
the database, to simulate an isolated and transactional context and
|
|
avoid affecting other tests. One of the consequences is that it takes
|
|
a *long* time to run (5~10s, most of that time being spent waiting for
|
|
a database duplication).
|
|
|
|
Furthermore, as the test needs to clone a database, it also has to ask
|
|
which database to clone, the database/super-admin password and the
|
|
password of the ``admin`` user (in order to authenticate as said
|
|
user). As a result, the first time the test runner encounters an
|
|
``rpc: "rpc"`` test configuration it will produce the following
|
|
prompt:
|
|
|
|
.. image:: ./images/db-query.png
|
|
:align: center
|
|
|
|
and stop the testing process until the necessary information has been
|
|
provided.
|
|
|
|
The prompt will only appear once per test run, all tests will use the
|
|
same "source" database.
|
|
|
|
.. note::
|
|
|
|
The handling of that information is currently rather brittle and
|
|
unchecked, incorrect values will likely crash the runner.
|
|
|
|
.. note::
|
|
|
|
The runner does not currently store this information (for any
|
|
longer than a test run that is), the prompt will have to be filled
|
|
every time.
|
|
|
|
Testing API
|
|
-----------
|
|
|
|
.. js:function:: openerp.testing.section(name[, options], body)
|
|
|
|
A test section, serves as shared namespace for related tests (for
|
|
constants or values to only set up once). The ``body`` function
|
|
should contain the tests themselves.
|
|
|
|
Note that the order in which tests are run is essentially
|
|
undefined, do *not* rely on it.
|
|
|
|
:param String name:
|
|
:param TestOptions options:
|
|
:param body:
|
|
:type body: Function<:js:func:`~openerp.testing.case`, void>
|
|
|
|
.. js:function:: openerp.testing.case(name[, options], callback)
|
|
|
|
Registers a test case callback in the test runner, the callback
|
|
will only be run once the runner is started (or maybe not at all,
|
|
if the test is filtered out).
|
|
|
|
:param String name:
|
|
:param TestOptions options:
|
|
:param callback:
|
|
:type callback: Function<instance, $, Function<String, Function, void>>
|
|
|
|
.. js:class:: TestOptions
|
|
|
|
the various options which can be passed to
|
|
:js:func:`~openerp.testing.section` or
|
|
:js:func:`~openerp.testing.case`. Except for
|
|
:js:attr:`~TestOptions.setup` and
|
|
:js:attr:`~TestOptions.teardown`, an option on
|
|
:js:func:`~openerp.testing.case` will overwrite the corresponding
|
|
option on :js:func:`~openerp.testing.section` so
|
|
e.g. :js:attr:`~TestOptions.rpc` can be set for a
|
|
:js:func:`~openerp.testing.section` and then differently set for
|
|
some :js:func:`~openerp.testing.case` of that
|
|
:js:func:`~openerp.testing.section`
|
|
|
|
.. js:attribute:: TestOptions.asserts
|
|
|
|
An integer, the number of assertions which should run during a
|
|
normal execution of the test. Mandatory for asynchronous tests.
|
|
|
|
.. js:attribute:: TestOptions.setup
|
|
|
|
Test case setup, run right before each test case. A section's
|
|
:js:func:`~TestOptions.setup` is run before the case's own, if
|
|
both are specified.
|
|
|
|
.. js:attribute:: TestOptions.teardown
|
|
|
|
Test case teardown, a case's :js:func:`~TestOptions.teardown`
|
|
is run before the corresponding section if both are present.
|
|
|
|
.. js:attribute:: TestOptions.fail_on_rejection
|
|
|
|
If the test is asynchronous and its resulting promise is
|
|
rejected, fail the test. Defaults to ``true``, set to
|
|
``false`` to not fail the test in case of rejection::
|
|
|
|
// test/demo.js
|
|
test('unfail rejection', {
|
|
asserts: 1,
|
|
fail_on_rejection: false
|
|
}, function () {
|
|
var d = $.Deferred();
|
|
setTimeout(function () {
|
|
ok(true);
|
|
d.reject();
|
|
}, 100);
|
|
return d;
|
|
});
|
|
|
|
.. js:attribute:: TestOptions.rpc
|
|
|
|
RPC method to use during tests, one of ``"mock"`` or
|
|
``"rpc"``. Any other value will disable RPC for the test (if
|
|
they were enabled by the suite for instance).
|
|
|
|
.. js:attribute:: TestOptions.templates
|
|
|
|
Whether the current module (and its dependencies)'s templates
|
|
should be loaded into QWeb before starting the test. A
|
|
boolean, ``false`` by default.
|
|
|
|
The test runner can also use two global configuration values set
|
|
directly on the ``window`` object:
|
|
|
|
* ``oe_all_dependencies`` is an ``Array`` of all modules with a web
|
|
component, ordered by dependency (for a module ``A`` with
|
|
dependencies ``A'``, any module of ``A'`` must come before ``A`` in
|
|
the array)
|
|
|
|
* ``oe_db_info`` is an object with 3 keys ``source``, ``supadmin`` and
|
|
``password``. It is used to pre-configure :ref:`actual RPC
|
|
<testing-rpc-rpc>` tests, to avoid a prompt being displayed
|
|
(especially for headless situations).
|
|
|
|
Running through Python
|
|
----------------------
|
|
|
|
The web client includes the means to run these tests on the
|
|
command-line (or in a CI system), but while actually running it is
|
|
pretty simple the setup of the pre-requisite parts has some
|
|
complexities.
|
|
|
|
1. Install unittest2_ and QUnitSuite_ in your Python environment. Both
|
|
can trivially be installed via `pip <http://pip-installer.org>`_ or
|
|
`easy_install
|
|
<http://packages.python.org/distribute/easy_install.html>`_.
|
|
|
|
The former is the unit-testing framework used by OpenERP, the
|
|
latter is an adapter module to run qunit_ test suites and convert
|
|
their result into something unittest2_ can understand and report.
|
|
|
|
2. Install PhantomJS_. It is a headless
|
|
browser which allows automating running and testing web
|
|
pages. QUnitSuite_ uses it to actually run the qunit_ test suite.
|
|
|
|
The PhantomJS_ website provides pre-built binaries for some
|
|
platforms, and your OS's package management probably provides it as
|
|
well.
|
|
|
|
If you're building PhantomJS_ from source, I recommend preparing
|
|
for some knitting time as it's not exactly fast (it needs to
|
|
compile both `Qt <http://qt-project.org/>`_ and `Webkit
|
|
<http://www.webkit.org/>`_, both being pretty big projects).
|
|
|
|
.. note::
|
|
|
|
Because PhantomJS_ is webkit-based, it will not be able to test
|
|
if Firefox, Opera or Internet Explorer can correctly run the
|
|
test suite (and it is only an approximation for Safari and
|
|
Chrome). It is therefore recommended to *also* run the test
|
|
suites in actual browsers once in a while.
|
|
|
|
.. note::
|
|
|
|
The version of PhantomJS_ this was build through is 1.7,
|
|
previous versions *should* work but are not actually supported
|
|
(and tend to just segfault when something goes wrong in
|
|
PhantomJS_ itself so they're a pain to debug).
|
|
|
|
3. Set up :ref:`OpenERP Command <openerpcommand:openerp-command>`,
|
|
which will be used to actually run the tests: running the qunit_
|
|
test suite requires a running server, so at this point OpenERP
|
|
Server isn't able to do it on its own during the building/testing
|
|
process.
|
|
|
|
4. Install a new database with all relevant modules (all modules with
|
|
a web component at least), then restart the server
|
|
|
|
.. note::
|
|
|
|
For some tests, a source database needs to be duplicated. This
|
|
operation requires that there be no connection to the database
|
|
being duplicated, but OpenERP doesn't currently break
|
|
existing/outstanding connections, so restarting the server is
|
|
the simplest way to ensure everything is in the right state.
|
|
|
|
5. Launch ``oe run-tests -d $DATABASE -mweb`` with the correct
|
|
addons-path specified (and replacing ``$DATABASE`` by the source
|
|
database you created above)
|
|
|
|
.. note::
|
|
|
|
If you leave out ``-mweb``, the runner will attempt to run all
|
|
the tests in all the modules, which may or may not work.
|
|
|
|
If everything went correctly, you should now see a list of tests with
|
|
(hopefully) ``ok`` next to their names, closing with a report of the
|
|
number of tests run and the time it took:
|
|
|
|
.. literalinclude:: test-report.txt
|
|
:language: text
|
|
|
|
Congratulation, you have just performed a successful "offline" run of
|
|
the OpenERP Web test suite.
|
|
|
|
.. note::
|
|
|
|
Note that this runs all the Python tests for the ``web`` module,
|
|
but all the web tests for all of OpenERP. This can be surprising.
|
|
|
|
.. _qunit: http://qunitjs.com/
|
|
|
|
.. _qunit assertions: http://api.qunitjs.com/category/assert/
|
|
|
|
.. _unittest2: http://pypi.python.org/pypi/unittest2
|
|
|
|
.. _QUnitSuite: http://pypi.python.org/pypi/QUnitSuite/
|
|
|
|
.. _PhantomJS: http://phantomjs.org/
|