YAML is not the only way of writing tests with Odoo. If you are familiar with the Python unit testing tools, you will be pleased to know that these are also available within the Odoo framework. In this recipe, we will see how to write Python unit tests for the my_module
methods we wrote in Chapter 5, Basic Server Side Development, in the recipe Define Model methods and use the API decorators.
This recipe assumes you have a instance ready with the code for the my_module
module defined in Chapter 3, Creating Odoo Modules, and the code from Chapter 5, Basic Server Side Development, in the recipe Define Model methods and use the API decorators.
In order to write unit tests for the module, perform the following steps:
tests
inside the addon
module directory:$mkdir my_module/tests
__init__.py
file in that directory with the following contents:from . import test_library
test_library.py
file in tests/
subdirectory. Inside the test_library.py file
, import the Odoo test base class:from openerp.test.common import TransactionCase
TestCase
class:class LibraryTestCase(TransactionCase):
setUp
method that creates a book:def setUp(self): super(LibraryTestCase, self).setUp() book_model = self.env['library.book'].sudo( self.ref('base.user_demo') ) self.book = book_model.create(
{'name': 'Test book', 'state': 'draft', } )
test
method changing the state:def test_change_draft_available(self): '''test changing state from draft to available''' self.book.change_state('available') self.assertEqual(self.book.state, 'available')
test
method trying to make an illegal state change:def test_change_available_draft_no_effect(self): '''test forbidden state change from available to draft''' self.book.change_state('available') self.book.change_state('draft') self.assertEqual( self.book.state, 'available', 'the state cannot change from available to %s' % self.book.state )
We create a Python subpackage in our module called tests
and add a test module with a name starting with test_
. This is the convention used by Odoo for test discovery.
In this file, we import the base test class, TransactionCase
, from openerp.test.common
. This class extends the unittest.TestCase
class from the Python standard library to make it suitable for use in Odoo:
setUp()
method initializes the self.env
attribute, which you can use to perform the usual operations (see the recipes in Chapter 5, Basic Server Side Development)tearDown()
method rolls back the database transaction so that the tests are run in isolationThe tests are defined in methods named with a test
prefix. The test runner will run then one after the other with a call to setUp()
before each test method and a call to tearDown()
after each. Inside the method, you can use all the usual assertion methods from unittest.TestCase
. Here are the most commonly used ones:
Method |
Checks that |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
All these methods except assertRaises()
accept an optional msg
argument, which will be displayed in the error message when the assertion is not true. Otherwise, a standard error message is used.
assertRaises
is best used as a context manager. Suppose you want to test that modifying a record raises a UserError
exception. You can write the following test:
class TestCase(TransactionCase): # setUp method defines self.record def testWriteRaisesUserError(self): with self.assertRaises(UserError): self.record.write({'some_field': some_value})
The test will succeed if the exception passed to assertRaises
is generated by the block of code; otherwise it will fail.
For more information on unit tests in Python, please refer to the standard library documentation at https://docs.python.org/2.7/library/unittest.html#test-cases.
Note that in the setUp()
method, we use sudo()
to change the user in the environment of self.book_model
. This ensures that the tests will not run using the administrator user, which bypasses the access rules and that the security rules we set up in our module are getting exercised.
See the following recipe, Run server tests, to see how to run the test.
The module openerp.test.common
defines several classes that can be used as base classes for test cases:
TransactionCase
: Each test method is run independently and the database transaction is rolled back after each. This means that any changes made in one test method are not seen by the other test methods. You can use the traditional setUp()
and tearDown()
methods to perform test initialization and cleanup.SingleTransactionCase
: All the tests methods are run in the same transaction, which is only rolled back after the last method. If you need to perform initialization and cleanup for the test case, you need to extend the setUpClass()
and tearDownClass()
methods. Don't forget to decorate these with @classmethod
, or you will get strange errors when calling the super()
implementation.SavePointCase
: It is an extension of SingleTransactionCase
that creates a database SAVEPOINT
before running test methods and restoring them after the test is run. The net effect is that you can run your tests in isolation as with TransactionCase
without having to pay the price of a costly setUp()
method recreating all the data between all tests—the initialization is performed in setUpClass()
and we roll back the transaction to the saved state after each test.The module also defines two decorators, at_install(bool)
and post_install(bool)
. By default, the tests for a given addon module are run just after the module is initialized, before initializing the next addon module; this corresponds to decorating the test methods with both @at_install(True)
and @post_install(False)
. Sometimes, you can need to change this. A typical case is the following: module_a
and module_b
both extend the same model, but they do not depend on one another. Both add a required field to the model field_a
and field_b
and provide a default value for that field. In the tests of these modules, new records are created. If both modules are installed when the tests are run, the tests will fail, whereas if only module_a
or module_b
is installed, the tests will pass. The reason is that if, for instance, module_a
is loaded first, when the tests create a new record the default value for field_b
is not computed because module_b
is not loaded yet. However, the database NOT NULL
constraint for field_b
is present and will prevent the record from being created. A solution is to decorate the test methods of both modules with @at_install(False)
and @post_install(True)
, which will force the tests to be run after all modules have been initialized.