.. _reference/testing: =============== Testing Odoo =============== There are many ways to test an application. In Odoo, we have three kinds of tests - 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 =================== Odoo provides support for testing modules using unittest. To write tests, simply define a ``tests`` sub-package in your module, it will be automatically inspected for test modules. Test modules should have a name starting with ``test_`` and should be imported from ``tests/__init__.py``, e.g. .. code-block:: text your_module |-- ... `-- tests |-- __init__.py |-- test_bar.py `-- test_foo.py and ``__init__.py`` contains:: from . import test_foo, test_bar .. warning:: test modules which are not imported from ``tests/__init__.py`` will not be run The test runner will simply run any test case, as described in the official `unittest documentation`_, but Odoo provides a number of utilities and helpers related to testing Odoo content (modules, mainly): .. autoclass:: odoo.tests.common.TransactionCase :members: browse_ref, ref .. autoclass:: odoo.tests.common.SingleTransactionCase :members: browse_ref, ref .. autoclass:: odoo.tests.common.SavepointCase .. autoclass:: odoo.tests.common.HttpCase :members: browse_ref, ref, url_open, browser_js .. autofunction:: odoo.tests.common.tagged By default, tests are run once right after the corresponding module has been installed. Test cases can also be configured to run after all modules have been installed, and not run right after the module installation:: # coding: utf-8 from odoo.tests import HttpCase, tagged # This test should only be executed after all modules have been installed. @tagged('-at_install', 'post_install') class WebsiteVisitorTests(HttpCase): def test_create_visitor_on_tracked_page(self): Page = self.env['website.page'] The most common situation is to use :class:`~odoo.tests.common.TransactionCase` and test a property of a model in each method:: class TestModelA(common.TransactionCase): def test_some_action(self): record = self.env['model.a'].create({'field': 'value'}) record.some_action() self.assertEqual( record.field, expected_field_value) # other tests... .. note:: Test methods must start with ``test_`` .. autoclass:: odoo.tests.common.Form :members: .. autoclass:: odoo.tests.common.M2MProxy :members: add, remove, clear .. autoclass:: odoo.tests.common.O2MProxy :members: new, edit, remove Running tests ------------- Tests are automatically run when installing or updating modules if :option:`--test-enable ` was enabled when starting the Odoo server. .. _unittest documentation: https://docs.python.org/3/library/unittest.html .. _developer/reference/testing/selection: Test selection -------------- In Odoo, Python tests can be tagged to facilitate the test selection when running tests. Subclasses of :class:`odoo.tests.common.BaseCase` (usually through :class:`~odoo.tests.common.TransactionCase`, :class:`~odoo.tests.common.SavepointCase` or :class:`~odoo.tests.common.HttpCase`) are automatically tagged with ``standard``, ``at_install`` and their source module's name by default. Invocation ^^^^^^^^^^ :option:`--test-tags ` can be used to select/filter tests to run on the command-line. This option defaults to ``+standard`` meaning tests tagged ``standard`` (explicitly or implicitly) will be run by default when starting Odoo with :option:`--test-enable `. When writing tests, the :func:`~odoo.tests.common.tagged` decorator can be used on **test classes** to add or remove tags. The decorator's arguments are tag names, as strings. .. danger:: :func:`~odoo.tests.common.tagged` is a class decorator, it has no effect on functions or methods Tags can be prefixed with the minus (``-``) sign, to *remove* them instead of add or select them e.g. if you don't want your test to be executed by default you can remove the ``standard`` tag: .. code-block:: python from odoo.tests import TransactionCase, tagged @tagged('-standard', 'nice') class NiceTest(TransactionCase): ... This test will not be selected by default, to run it the relevant tag will have to be selected explicitely: .. code-block:: console $ odoo-bin --test-enable --test-tags nice Note that only the tests tagged ``nice`` are going to be executed. To run *both* ``nice`` and ``standard`` tests, provide multiple values to :option:`--test-tags `: on the command-line, values are *additive* (you're selecting all tests with *any* of the specified tags) .. code-block:: console $ odoo-bin --test-enable --test-tags nice,standard The config switch parameter also accepts the ``+`` and ``-`` prefixes. The ``+`` prefix is implied and therefore, totaly optional. The ``-`` (minus) prefix is made to deselect tests tagged with the prefixed tags, even if they are selected by other specified tags e.g. if there are ``standard`` tests which are also tagged as ``slow`` you can run all standard tests *except* the slow ones: .. code-block:: console $ odoo-bin --test-enable --test-tags 'standard,-slow' When you write a test that does not inherit from the :class:`~odoo.tests.common.BaseCase`, this test will not have the default tags, you have to add them explicitely to have the test included in the default test suite. This is a common issue when using a simple ``unittest.TestCase`` as they're not going to get run: .. code-block:: python import unittest from odoo.tests import tagged @tagged('standard', 'at_install') class SmallTest(unittest.TestCase): ... .. _reference/testing/tags: Special tags ^^^^^^^^^^^^ - ``standard``: All Odoo tests that inherit from :class:`~odoo.tests.common.BaseCase` are implicitely tagged standard. :option:`--test-tags ` also defaults to ``standard``. That means untagged test will be executed by default when tests are enabled. - ``at_install``: Means that the test will be executed right after the module installation and before other modules are installed. This is a default implicit tag. - ``post_install``: Means that the test will be executed after all the modules are installed. This is what you want for HttpCase tests most of the time. Note that this is *not exclusive* with ``at_install``, however since you will generally not want both ``post_install`` is usually paired with ``-at_install`` when tagging a test class. - *module_name*: Odoo tests classes extending :class:`~odoo.tests.common.BaseCase` are implicitely tagged with the technical name of their module. This allows easily selecting or excluding specific modules when testing e.g. if you want to only run tests from ``stock_account``: .. code-block:: console $ odoo-bin --test-enable --test-tags stock_account Examples ^^^^^^^^ .. important:: Tests will be executed only in the installed or updated modules. So modules have to be selected with the :option:`-u ` or :option:`-i ` switches. For simplicity, those switches are not specified in the examples below. Run only the tests from the sale module: .. code-block:: console $ odoo-bin --test-enable --test-tags sale Run the tests from the sale module but not the ones tagged as slow: .. code-block:: console $ odoo-bin --test-enable --test-tags 'sale,-slow' Run only the tests from stock or tagged as slow: .. code-block:: console $ odoo-bin --test-enable --test-tags '-standard, slow, stock' .. note:: ``-standard`` is implicit (not required), and present for clarity 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 ---------------- 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. For example, here is what a pyUtils test could look like: .. code-block:: javascript QUnit.module('py_utils'); QUnit.test('simple arithmetic', function (assert) { assert.expect(2); 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 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, ... .. warning:: While the test suite is running, make sure that: - your browser window is focused, - it is not zoomed in/out. It needs to have exactly 100% zoom level. If this is not the case, some tests will fail, without a proper explanation. Testing Infrastructure ---------------------- Here is a high level overview of the most important parts of the testing infrastructure: - there is an asset bundle named `web.qunit_suite`_. This bundle contains the main code (assets common + assets backend), some libraries, the QUnit test runner and the test bundles listed below. - a bundle named `web.tests_assets`_ includes most of the assets and utils required by the test suite: custom QUnit asserts, test helpers, lazy loaded assets, etc. - another asset bundle, `web.qunit_suite_tests`_, contains all the test scripts. This is typically where the test files are added to the suite. - 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 browser_js method spawns a Chrome headless instance. 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