Guide to Odoo Community Association Quality Tools Part 2
In the [Guide to Odoo Community Association Quality Tools Part 1] (http://www.mindissoftware.com/2014/10/28/guide-Odoo-OCA-QA-tools-part1/), we discussed how to link a GitHub repository with the OCA quality tools to run tests and code coverage. Here we investigate the implementation details of the Odoo test framework and the OCA quality tools.
Odoo Test Framework
The [Odoo test framework document] (http://openerp-server.readthedocs.org/en/latest/05_test_framework.html) classifies tests into three categories:
- fast_suite tests that can be run right after an addon is
freshly installed. A fast_suite test requires a fresh addon installation
and should run fast – take less than one minute to complete.
- checks tests that check invariants that must be hold at any time, i.e., after other module installation, migration or in product.
- other automatically discovered tests that not listed in in fast_suite and checks lists. This is for a test that requires complex setup and takes long to complete.
By default, tests should be listed in the
checks list. For a test
that requires a fresh installation, add it to
if a test takes long time to run, don’t add it to any list.
During loading modules, Odoo runs module tests when the
test_enable option is
True. The module loading functions,
load_module_graph for ‘at install’ tests
load_modules for ‘post install’ tests, in
run_unit_tests method defined in
run_unit_tests method checks the
tests subdirectory of a module
and runs tests defined in the
tests directory. All tests must be
defined in a module name starting with ‘test_‘. Odoo uses
unittest2 test suite and test case class.
Therefore the suggested file structure for tests of an addon is as the following:
my_addon/ # an Odoo addon named 'my_addon'. __init__.py # package init file, don't import 'tests'. models/ # folder for addon models. foo.py bar.py tests/ # tests folder, must be named as 'tests' __init__.py # tests package init file that should # import some of the tests sub-modules and # add them into 'fast_suite' or 'checks' lists. test_foo.py # a test module filename must start with 'test_'. test_bar.py # another test module.
tests/__inti__.py file is as the following:
from . import test_foo from . import test_bar fast_suite = [ ] checks = [ test_foo, test_bar, ]
Package Import Issues
In Odoo addon implementation, there is a subtle but inconvenient issue: when Odoo loads an addon, it adds a ‘openerp.addons’ prefix to all modules in an addon.
We can use either absolute import or relative import in a module. However, none of them works in all cases.
Absolute Import Issues
The absolute import works in Odoo runtime, however
it is ugly in an IDE such as PyCharm because all importing
statements are wrong. Instead of
one has to use
Not only it is lengthy, but also it is wrong locally because
there is no such module available until it is loaded by Odoo
Another side effect is that you cannot run unittest locally that is not a good case too.
Relative Import Issues
Using relative import solves the IDE syntax error issue.
For example, in ‘test_foo’, we can write
from ..models import foo.
An IDE should be able to find the right relative module within
the addon. The relative import syntax also works correctly in
However, there is an issue when run unit tests in the PyCharm IDE using the relative import syntax. The problem is that PyCharm runs ‘test_foo’ as a top level module, not as a module in a package. Python throws an exception
ValueError: Attempted relative import in non-package”.
The details can be found in
[a discussion in stackoverflow.com]
To fix it, we have to detect the
__name__ value of the current module
and use different import syntax in different cases. The
test_foo when run
test_foo directly from an IDE or
from a command line. Giving that the
__name__ doesn’t have a relative path
value in those situations, the correct import code for the above
structure is as the following:
# use absolute import for PyCharm, relative import for Odoo if '.' not in __name__: from my_addon.models import foo else: from ..models import foo
With the above fix, we can run the test both in the PyCharm and in the Odoo runtime. It is not idea because we have to use the extra code in all imports of an addon module.
Absolute Import vs. Relative Import
According to [this stackoverflow discussion] (http://stackoverflow.com/questions/4209641/absolute-vs-explicit-relative-import-of-python-module), relative imports for intra-package are no longer discouraged. As long as we don’t use implicit relative imports, relative imports are as unambiguous as absolute imports.
An implicit import is an import statement in Python 2.x that does not has
a ‘.’ in its path. For example, the above
tests/__inti__.py file can
be written as:
``python import test_foo # implicit relative import, not recommended import test_bar # implicit relative import, not recommended
fast_suite = [ ]
checks = [ test_foo, test_bar, ]
The implicit relative import style is not recommended because it is **implicit**. Python 3 has disable implicit imports altogether. ## The Odoo OCA Maintainer Quality Tools The test functions are defined in script files in the `travis` directory in the [Odoo OCA maintainer quality tools repository] (https://github.com/OCA/maintainer-quality-tools). It is a good idea to use a clone to decouple an Odoo addon project from this repository because it may change in the future. The `travis_install_nightly` shell script download source code of specified Odoo version from GitHub Odoo repository archive. It then installs Python packages required by Odoo (defined in requirements.txt in the same `travis` folder) and testing. It uses `QUnitSuite`, `flake8`, `pylint` and `converalls` packages for unit test and code coverage analysis. The `travis_run_tests` script is the main entry of a test. It runs `test_flak8`, `test_pylint` and `test_server` scripts in a subprocess and summarize the test results. The `test_server` script runs tests for Odoo addons. It allows a user to use 'INCLUDE`, 'EXCLUDE' to define included or excluded addons. It eventually runs tests using a command that is similar to the following:
coverage run /home/travis/odoo-8.0/openerp-server –test-enable -d openerp_test –stop-after-init –log-level=info –addons-path=/home/travis/build/foo-github-name/foo-repository-name,/home/travis/odoo-8.0/addons –init=foo ```