
Prior to this commit, users had to either know in advance or guess the location of the content they were looking for. Top-level pages of the "Developer" section of the documentation, in particular the "Developer" page itself, were listing their sub-pages without directions for users. This commit brings the following changes to improve the navigation: - add directions for users on the "Developer" page and list the three main categories of developer documentation ("Tutorials", "How-to guides", and "Reference") with explanations of their content and target audience; - add categories for content cards on the "Tutorials" and "How-to guides" pages, and fine-tune the toctree of the "Reference" page to more easily locate specific topics; - clarify what are the "Python framework" and the "JavaScript framework" by relabelling them to "Server framework" and "Web framework" on top-level pages, as some users were confused to find that the JS framework was not responsible for the server, and others that the documentation for QWeb template is located in the Python documentation; - extract the "Setup guide" from the "Getting started" tutorial and rename the latter to "Server framework 101" to allow reusing the setup guide in other tutorials and make clear that the "Server framework 101" tutorial is not about the Web framework. task-3802536 closes odoo/documentation#8597 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com> Co-authored-by: Valeriya (vchu) <vchu@odoo.com>
299 lines
13 KiB
ReStructuredText
299 lines
13 KiB
ReStructuredText
===================================
|
|
Safeguard your code with unit tests
|
|
===================================
|
|
|
|
.. important::
|
|
This tutorial is an extension of the :doc:`server_framework_101` tutorial. Make sure you have
|
|
completed it and use the `estate` module you have built as a base for the exercises in this
|
|
tutorial.
|
|
|
|
**Reference**:
|
|
`Odoo's Test Framework: Learn Best Practices <https://www.youtube.com/watch?v=JEIscps0OOQ>`__
|
|
(Odoo Experience 2020) on YouTube.
|
|
|
|
Writing tests is a necessity for multiple reasons. Here is a non-exhaustive list:
|
|
|
|
* Ensure code will not be broken in the future
|
|
* Define the scope of your code
|
|
* Give examples of use cases
|
|
* It is one way to technically document the code
|
|
* Help your coding by defining your goal before working towards it
|
|
|
|
Running Tests
|
|
=============
|
|
|
|
Before knowing how to write tests, we need to know how to run them.
|
|
|
|
.. code-block:: console
|
|
|
|
$ odoo-bin -h
|
|
Usage: odoo-bin [options]
|
|
|
|
Options:
|
|
--version show program's version number and exit
|
|
-h, --help show this help message and exit
|
|
|
|
[...]
|
|
|
|
Testing Configuration:
|
|
--test-file=TEST_FILE
|
|
Launch a python test file.
|
|
--test-enable Enable unit tests.
|
|
--test-tags=TEST_TAGS
|
|
Comma-separated list of specs to filter which tests to
|
|
execute. Enable unit tests if set. A filter spec has
|
|
the format: [-][tag][/module][:class][.method] The '-'
|
|
specifies if we want to include or exclude tests
|
|
matching this spec. The tag will match tags added on a
|
|
class with a @tagged decorator (all Test classes have
|
|
'standard' and 'at_install' tags until explicitly
|
|
removed, see the decorator documentation). '*' will
|
|
match all tags. If tag is omitted on include mode, its
|
|
value is 'standard'. If tag is omitted on exclude
|
|
mode, its value is '*'. The module, class, and method
|
|
will respectively match the module name, test class
|
|
name and test method name. Example: --test-tags
|
|
:TestClass.test_func,/test_module,external Filtering
|
|
and executing the tests happens twice: right after
|
|
each module installation/update and at the end of the
|
|
modules loading. At each stage tests are filtered by
|
|
--test-tags specs and additionally by dynamic specs
|
|
'at_install' and 'post_install' correspondingly.
|
|
--screencasts=DIR Screencasts will go in DIR/{db_name}/screencasts.
|
|
--screenshots=DIR Screenshots will go in DIR/{db_name}/screenshots.
|
|
Defaults to /tmp/odoo_tests.
|
|
|
|
$ # run all the tests of account, and modules installed by account
|
|
$ # the dependencies already installed are not tested
|
|
$ # this takes some time because you need to install the modules, but at_install
|
|
$ # and post_install are respected
|
|
$ odoo-bin -i account --test-enable
|
|
$ # run all the tests in this file
|
|
$ odoo-bin --test-file=addons/account/tests/test_account_move_entry.py
|
|
$ # test tags can help you filter quite easily
|
|
$ odoo-bin --test-tags=/account:TestAccountMove.test_custom_currency_on_account_1
|
|
|
|
Integration Bots
|
|
================
|
|
|
|
.. note:: This section is only for Odoo employees and people that are contributing to
|
|
`github.com/odoo`. We highly recommend having your own CI otherwise.
|
|
|
|
When a test is written, it is important to make sure it always passes when modifications are
|
|
applied to the source code. To automate this task, we use a development practice called
|
|
Continuous Integration (CI). This is why we have some bots running all the tests at different
|
|
moments.
|
|
Whether you are working at Odoo or not, if you are trying to merge something inside `odoo/odoo`,
|
|
`odoo/enterprise`, `odoo/upgrade` or on odoo.sh, you will have to go through the CI. If you are
|
|
working on another project, you should think of adding your own CI.
|
|
|
|
Runbot
|
|
------
|
|
|
|
**Reference**: the documentation related to this topic can be found in
|
|
`Runbot FAQ <https://runbot.odoo.com/doc>`__.
|
|
|
|
Most of the tests are run on `Runbot <https://runbot.odoo.com>`__ every time a commit is pushed on
|
|
GitHub.
|
|
|
|
You can see the state of a commit/branch by filtering on the runbot dashboard.
|
|
|
|
A **bundle** is created for each branch. A bundle consists of a configuration and
|
|
batches.
|
|
|
|
A **batch** is a set of builds, depending on the parameters of the bundle.
|
|
A batch is green (i.e. passes the tests) if all the builds are green.
|
|
|
|
A **build** is when we launch a server. It can be divided in sub-builds. Usually there are builds
|
|
for the community version, the enterprise version (only if there is an enterprise branch but you
|
|
can force the build), and the migration of the branch.
|
|
A build is green if every sub-build is green.
|
|
|
|
A **sub-build** only does some parts of what a full build does. It is used to speed up the CI
|
|
process. Generally it is used to split the post install tests in 4 parallel instances.
|
|
A sub-build is green if all the tests are passing and there are no errors/warnings logged.
|
|
|
|
.. note::
|
|
* All tests are run regardless of the modifications done. Correcting a typo in an error message or
|
|
refactoring a whole module triggers the same tests. All modules will be installed as well. This means
|
|
something might not work even if the Runbot is green, i.e. your changes depend on a module that the
|
|
module the changes are in doesn't depend on.
|
|
* The localization modules (i.e. country-specific modules) are not installed on Runbot (except
|
|
the generic one). Some modules with external dependencies can also be excluded.
|
|
* There is a nightly build running additional tests: module operations, localization, single
|
|
module installs, multi-builds for nondeterministic bugs, etc.
|
|
These are not kept in the standard CI to shorten the time of execution.
|
|
|
|
You can also login to a build built by Runbot. There are 3 users usable: `admin`, `demo` and
|
|
`portal`. The password is the same as the login. This is useful to quickly test things on different
|
|
versions without having to build it locally. The full logs are also available; these are used for
|
|
monitoring.
|
|
|
|
Robodoo
|
|
-------
|
|
|
|
You will most likely have to gain a little bit more experience before having the rights to summon
|
|
robodoo, but here are a few remarks anyways.
|
|
|
|
Robodoo is the guy spamming the CI status as tags on your PRs, but he is also the guy that kindly
|
|
integrates your commits into the main repositories.
|
|
|
|
When the last batch is green, the reviewer can ask robodoo to merge your PR (it is more
|
|
a `rebase` than a `merge`). It will then go to the mergebot.
|
|
|
|
Mergebot
|
|
--------
|
|
|
|
`Mergebot <https://mergebot.odoo.com>`__ is the last testing phase before merging a PR.
|
|
|
|
It will take the commits in your branch not yet present on the target, stage it and rerun the tests
|
|
one more time, including the enterprise version even if you are only changing something in
|
|
community.
|
|
|
|
This step can fail with a `Staging failed` error message. This could be due to
|
|
|
|
* a nondeterministic bug that is already on the target. If you are an Odoo employee, you can check
|
|
those here: https://runbot.odoo.com/runbot/errors
|
|
* a nondeterministic bug that you introduced but wasn't detected in the CI before
|
|
* an incompatibility with another commit merged right before and what you are trying to merge
|
|
* an incompatibility with the enterprise repository if you only did changes in the community repo
|
|
|
|
Always check that the issue does not come from you before asking the merge bot to retry: rebase
|
|
your branch on the target and rerun the tests locally.
|
|
|
|
Modules
|
|
=======
|
|
|
|
Because Odoo is modular, the tests need to be also modular. This means tests are defined in
|
|
the module that adds the functionality you are adding in, and tests cannot depend on functionality
|
|
coming from modules your module doesn't depend on.
|
|
|
|
**Reference**: the documentation related to this topic can be found in
|
|
:ref:`Special Tags<reference/testing/tags>`.
|
|
|
|
.. code-block:: python
|
|
|
|
from odoo.tests.common import TransactionCase
|
|
from odoo.tests import tagged
|
|
|
|
# The CI will run these tests after all the modules are installed,
|
|
# not right after installing the one defining it.
|
|
@tagged('post_install', '-at_install') # add `post_install` and remove `at_install`
|
|
class PostInstallTestCase(TransactionCase):
|
|
def test_01(self):
|
|
...
|
|
|
|
@tagged('at_install') # this is the default
|
|
class AtInstallTestCase(TransactionCase):
|
|
def test_01(self):
|
|
...
|
|
|
|
|
|
If the behavior you want to test can be changed by the installation of another module, you need to
|
|
ensure that the tag `at_install` is set; otherwise, you can use the tag `post_install` to speed up
|
|
the CI and ensure it is not changed if it shouldn't.
|
|
|
|
Writing a test
|
|
==============
|
|
|
|
**Reference**: the documentation related to this topic can be found in
|
|
`Python unittest <https://docs.python.org/3/library/unittest.html>`__
|
|
and :ref:`Testing Odoo<reference/testing>`.
|
|
|
|
Here are a few things to take into consideration before writing a test
|
|
|
|
* The tests should be independent of the data currently in the database (including demo data)
|
|
* Tests should not impact the database by leaving/changing residual data. This is usually done by
|
|
the test framework by doing a rollback. Therefore, you must never call ``cr.commit`` in a test
|
|
(nor anywhere else in the business code).
|
|
* For a bug fix, the test should fail before applying the fix and pass after.
|
|
* Don't test something that is already tested elsewhere; you can trust the ORM. Most of the tests
|
|
in business modules should only test the business flows.
|
|
* You shouldn't need to flush data into the database.
|
|
|
|
.. note:: Remember that ``onchange`` only applies in the Form views, not by changing the attributes
|
|
in python. This also applies in the tests. If you want to emulate a Form view, you can use
|
|
``odoo.tests.common.Form``.
|
|
|
|
The tests should be in a ``tests`` folder at the root of your module. Each test file name
|
|
should start with `test_` and be imported in the ``__init__.py`` of the test folder. You shouldn't
|
|
import the test folder/module in the ``__init__.py`` of the module.
|
|
|
|
.. code-block:: bash
|
|
|
|
estate
|
|
├── models
|
|
│ ├── *.py
|
|
│ └── __init__.py
|
|
├── tests
|
|
│ ├── test_*.py
|
|
│ └── __init__.py
|
|
├── __init__.py
|
|
└── __manifest__.py
|
|
|
|
All the tests should extend ``odoo.tests.common.TransactionCase``. You usually define a
|
|
``setUpClass`` and the tests. After writing the `setUpClass`, you have an `env` available in the
|
|
class and can start interacting with the ORM.
|
|
|
|
These test classes are built on top of the ``unittest`` python module.
|
|
|
|
.. code-block:: python
|
|
|
|
from odoo.tests.common import TransactionCase
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests import tagged
|
|
|
|
# The CI will run these tests after all the modules are installed,
|
|
# not right after installing the one defining it.
|
|
@tagged('post_install', '-at_install')
|
|
class EstateTestCase(TransactionCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# add env on cls and many other things
|
|
super(EstateTestCase, cls).setUpClass()
|
|
|
|
# create the data for each tests. By doing it in the setUpClass instead
|
|
# of in a setUp or in each test case, we reduce the testing time and
|
|
# the duplication of code.
|
|
cls.properties = cls.env['estate.property'].create([...])
|
|
|
|
def test_creation_area(self):
|
|
"""Test that the total_area is computed like it should."""
|
|
self.properties.living_area = 20
|
|
self.assertRecordValues(self.properties, [
|
|
{'name': ..., 'total_area': ...},
|
|
{'name': ..., 'total_area': ...},
|
|
])
|
|
|
|
|
|
def test_action_sell(self):
|
|
"""Test that everything behaves like it should when selling a property."""
|
|
self.properties.action_sold()
|
|
self.assertRecordValues(self.properties, [
|
|
{'name': ..., 'state': ...},
|
|
{'name': ..., 'state': ...},
|
|
])
|
|
|
|
with self.assertRaises(UserError):
|
|
self.properties.forbidden_action_on_sold_property()
|
|
|
|
.. note:: For better readability, split your tests into multiple files depending on the scope of the
|
|
tests. You can also have a Common class that most of the tests should inherit from; this common
|
|
class can define the whole setup for the module. For instance, in
|
|
`account <{GITHUB_PATH}/addons/account/tests/common.py>`__.
|
|
|
|
.. exercise:: Update the code so no one can:
|
|
|
|
- Create an offer for a sold property
|
|
- Sell a property with no accepted offers on it
|
|
|
|
and create tests for both of these cases. Additionally check that selling a property that can
|
|
be sold is correctly marked as sold after selling it.
|
|
|
|
|
|
.. exercise:: Someone keeps breaking the reset of Garden Area and Orientation when you uncheck the
|
|
Garden checkbox. Make sure it doesn't happen again.
|
|
|
|
.. tip:: Tip: remember the note about `Form` a little bit above.
|