Writing tests for your module using Python unit tests

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.

Getting ready

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.

How to do it…

In order to write unit tests for the module, perform the following steps:

  1. Create a subdirectory called tests inside the addon module directory:
    $mkdir my_module/tests
    
  2. Create an __init__.py file in that directory with the following contents:
    from . import test_library
  3. Create a 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
    
  4. Create a TestCase class:
    class LibraryTestCase(TransactionCase):
    
  5. Add a 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',
             }
        )
  6. Add a 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')
  7. Add a second 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
        )

How it works…

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:

  • The 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)
  • The tearDown() method rolls back the database transaction so that the tests are run in isolation

Note

If your test case redefines these two methods, be sure to call the super() implementation.

The 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

assertEqual(a, b)

a == b

assertNotEqual(a, b)

a != b

assertTrue(x)

bool(x) is True

assertFalse(x)

bool(x) is False

assertIn(a, b)

a in b

assertNotIn(a, b)

a not in b

assertRaises(exc, fun, *args, **kwargs)

fun(*args, **kwargs) raises exc

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.

There's more…

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset