summaryrefslogtreecommitdiff
path: root/doc/reference/testing.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/reference/testing.rst')
-rw-r--r--doc/reference/testing.rst259
1 files changed, 239 insertions, 20 deletions
diff --git a/doc/reference/testing.rst b/doc/reference/testing.rst
index d14b3c25838..9196fd876c9 100644
--- a/doc/reference/testing.rst
+++ b/doc/reference/testing.rst
@@ -10,9 +10,9 @@ Testing Odoo
There are many ways to test an application. In Odoo, we have three kinds of
tests
-- python unit tests: useful for testing model business logic
-- js unit tests: this is necessary to test the javascript code in isolation
-- tours: this is a form of integration testing. The tours ensure that the
+- Python unit tests (see `Testing Python code`_): useful for testing model business logic
+- JS unit tests (see `Testing JS code`_): useful to test the javascript code in isolation
+- Tours (see `Integration Testing`_): tours simulate a real situation. They ensures that the
python and the javascript parts properly talk to each other.
Testing Python code
@@ -257,35 +257,242 @@ Run only the tests from stock or tagged as slow:
Testing JS code
===============
+Testing a complex system is an important safeguard to prevent regressions and to
+guarantee that some basic functionality still works. Since Odoo has a non trivial
+codebase in Javascript, it is necessary to test it. In this section, we will
+discuss the practice of testing JS code in isolation: these tests stay in the
+browser, and are not supposed to reach the server.
+
Qunit test suite
----------------
-Odoo Web includes means to unit-test both the core code of
-Odoo 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 Odoo.
+The Odoo framework uses the QUnit_ library testing framework as a test runner.
+QUnit defines the concepts of *tests* and *modules* (a set of related tests),
+and gives us a web based interface to execute the tests.
-To see what the runner looks like, find (or start) an Odoo server
-with the web client enabled, and navigate to ``/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).
+For example, here is what a pyUtils test could look like:
-.. image:: ./images/runner.png
- :align: center
+.. code-block:: javascript
+
+ QUnit.module('py_utils');
+
+ QUnit.test('simple arithmetic', function (assert) {
+ assert.expect(2);
-Clicking any runner button will launch the corresponding tests in the
-bundled QUnit_ runner:
+ var result = pyUtils.py_eval("1 + 2");
+ assert.strictEqual(result, 3, "should properly evaluate sum");
+ result = pyUtils.py_eval("42 % 5");
+ assert.strictEqual(result, 2, "should properly evaluate modulo operator");
+ });
+
+The main way to run the test suite is to have a running Odoo server, then
+navigate a web browser to ``/web/tests``. The test suite will then be executed
+by the web browser Javascript engine.
.. image:: ./images/tests.png
:align: center
-Writing a test case
--------------------
+The web UI has many useful features: it can run only some submodules, or
+filter tests that match a string. It can show every assertions, failed or passed,
+rerun specific tests, ...
-This section will be updated as soon as possible.
-.. _qunit: http://qunitjs.com/
+Testing Infrastructure
+----------------------
+
+Here is a high level overview of the most important parts of the testing
+infrastructure:
+
+- there is an asset bundle named `web.js_tests_assets`_. This bundle contains
+ the main code (assets common + assets backend), some libraries, the QUnit test
+ runner, and some additional helper code
+
+- another asset bundle, `web.qunit_suite`_, contains all the tests (and the
+ js_tests_assets code). Almost all the test files should be added to this
+ bundle
+
+- there is a `controller`_ in web, mapped to the route */web/tests*. This controller
+ simply renders the *web.qunit_suite* template.
+
+- to execute the tests, one can simply point its browser to the route */web/tests*.
+ In that case, the browser will download all assets, and QUnit will take over.
+
+- there is some code in `qunit_config.js`_ which logs in the console some
+ information when a test passes or fails.
+
+- we want the runbot to also run these tests, so there is a test (in `test_js.py`_)
+ which simply spawns a browser and points it to the *web/tests* url. Note that
+ the phantom_js method does not spawn phantom_js, but Chrome headless instead.
+
+
+Modularity and testing
+----------------------
+
+With the way Odoo is designed, any addon can modify the behaviour of other parts
+of the system. For example, the *voip* addon can modify the *FieldPhone* widget
+to use extra features. This is not really good from the perspective of the
+testing system, since this means that a test in the addon web will fail whenever
+the voip addon is installed (note that the runbot runs the tests with all addons
+installed).
+
+At the same time, our testing sytem is good, because it can detect whenever
+another module breaks some core functionality. There is no complete solution to
+this issue. For now, we solve this on a case by case basis.
+
+Usually, it is not a good idea to modify some other behaviour. For our voip
+example, it is certainly cleaner to add a new *FieldVOIPPhone* widget and
+modify the few views that needs it. This way, the *FieldPhone* widget is not
+impacted, and both can be tested.
+
+Adding a new test case
+----------------------
+
+Let us assume that we are maintaining an addon *my_addon*, and that we
+want to add a test for some javascript code (for example, some utility function
+myFunction, located in *my_addon.utils*). The process to add a new test case is
+the following:
+
+1. create a new file *my_addon/static/tests/utils_tests.js*. This file contains the basic code to
+ add a QUnit module *my_addon > utils*.
+
+ .. code-block:: javascript
+
+ odoo.define('my_addon.utils_tests', function (require) {
+ "use strict";
+
+ var utils = require('my_addon.utils');
+
+ QUnit.module('my_addon', {}, function () {
+
+ QUnit.module('utils');
+
+ });
+ });
+
+
+2. In *my_addon/assets.xml*, add the file to the main test assets:
+
+ .. code-block:: xml
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <odoo>
+ <template id="qunit_suite" name="my addon tests" inherit_id="web.qunit_suite">
+ <xpath expr="//script[last()]" position="after">
+ <script type="text/javascript" src="/my_addon/static/tests/utils_tests.js"/>
+ </xpath>
+ </template>
+ </odoo>
+
+3. Restart the server and update *my_addon*, or do it from the interface (to
+ make sure the new test file is loaded)
+
+4. Add a test case after the definition of the *utils* sub test suite:
+
+ .. code-block:: javascript
+
+ QUnit.test("some test case that we want to test", function (assert) {
+ assert.expect(1);
+
+ var result = utils.myFunction(someArgument);
+ assert.strictEqual(result, expectedResult);
+ });
+
+5. Visit */web/tests/* to make sure the test is executed
+
+Helper functions and specialized assertions
+-------------------------------------------
+
+Without help, it is quite difficult to test some parts of Odoo. In particular,
+views are tricky, because they communicate with the server and may perform many
+rpcs, which needs to be mocked. This is why we developed some specialized
+helper functions, located in `test_utils.js`_.
+
+- Mock test functions: these functions help setting up a test environment. The
+ most important use case is mocking the answers given by the Odoo server. These
+ functions use a `mock server`_. This is a javascript class that simulates
+ answers to the most common model methods: read, search_read, nameget, ...
+
+- DOM helpers: useful to simulate events/actions on some specific target. For
+ example, testUtils.dom.click performs a click on a target. Note that it is
+ safer than doing it manually, because it also checks that the target exists,
+ and is visible.
+
+- create helpers: they are probably the most important functions exported by
+ `test_utils.js`_. These helpers are useful to create a widget, with a mock
+ environment, and a lot of small detail to simulate as much as possible the
+ real conditions. The most important is certainly `createView`_.
+
+- `qunit assertions`_: QUnit can be extended with specialized assertions. For
+ Odoo, we frequently test some DOM properties. This is why we made some
+ assertions to help with that. For example, the *containsOnce* assertion takes
+ a widget/jQuery/HtmlElement and a selector, then checks if the target contains
+ exactly one match for the css selector.
+
+For example, with these helpers, here is what a simple form test could look like:
+
+.. code-block:: javascript
+
+ QUnit.test('simple group rendering', function (assert) {
+ assert.expect(1);
+
+ var form = testUtils.createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<group>' +
+ '<field name="foo"/>' +
+ '</group>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.containsOnce(form, 'table.o_inner_group');
+
+ form.destroy();
+ });
+
+Notice the use of the testUtils.createView helper and of the containsOnce
+assertion. Also, the form controller was properly destroyed at the end of
+the test.
+
+Best Practices
+--------------
+
+In no particular order:
+
+- all test files should be added in *some_addon/static/tests/*
+- for bug fixes, make sure that the test fails without the bug fix, and passes
+ with it. This ensures that it actually works.
+- try to have the minimal amount of code necessary for the test to work.
+- usually, two small tests are better than one large test. A smaller test is
+ easier to understand and to fix.
+- always cleanup after a test. For example, if your test instantiates a widget,
+ it should destroy it at the end.
+- no need to have full and complete code coverage. But adding a few tests helps
+ a lot: it makes sure that your code is not completely broken, and whenever a
+ bug is fixed, it is really much easier to add a test to an existing test suite.
+- if you want to check some negative assertion (for example, that a HtmlElement
+ does not have a specific css class), then try to add the positive assertion in
+ the same test (for example, by doing an action that changes the state). This
+ will help avoid the test to become dead in the future (for example, if the css
+ class is changed).
+
+Tips
+----
+
+- running only one test: you can (temporarily!) change the *QUnit.test(...)*
+ definition into *QUnit.only(...)*. This is useful to make sure that QUnit
+ only runs this specific test.
+- debug flag: most create utility functions have a debug mode (activated by the
+ debug: true parameter). In that case, the target widget will be put in the DOM
+ instead of the hidden qunit specific fixture, and more information will be
+ logged. For example, all mocked network communications will be available in the
+ console.
+- when working on a failing test, it is common to add the debug flag, then
+ comment the end of the test (in particular, the destroy call). With this, it
+ is possible to see the state of the widget directly, and even better, to
+ manipulate the widget by clicking/interacting with it.
Integration Testing
@@ -297,3 +504,15 @@ write another kind of test: tours. A tour is a mini scenario of some interestin
business flow. It explains a sequence of steps that should be followed. The
test runner will then create a phantom_js browser, point it to the proper url
and simulate the click and inputs, according to the scenario.
+
+
+.. _qunit: http://qunitjs.com/
+.. _qunit_config.js: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/qunit_config.js#L49
+.. _web.js_tests_assets: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/views/webclient_templates.xml#L427
+.. _web.qunit_suite: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/views/webclient_templates.xml#L509
+.. _controller: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/controllers/main.py#L637
+.. _test_js.py: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/tests/test_js.py#L13
+.. _test_utils.js: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/test_utils.js
+.. _mock server: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/mock_server.js
+.. _qunit assertions: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/qunit_asserts.js
+.. _createView: https://github.com/odoo/odoo/blob/51ee0c3cb59810449a60dae0b086b49b1ed6f946/addons/web/static/tests/helpers/test_utils_create.js#L267 \ No newline at end of file