Achieving Code Coverage With Automated Test Cases in Odoo

Use built-in testing framework that comes with Odoo

How, when, and how much code coverage with automated test cases?

Odoo is an open-source ERP system written in Python. It provides a framework for developing business applications. When it comes to code coverage with automated test cases in Odoo, there are several best practices that you can follow.

How to achieve code coverage with automated test cases in Odoo:
To achieve code coverage with automated test cases in Odoo, you can use the built-in testing framework that comes with Odoo. The framework allows you to write test cases in Python and execute them in a controlled environment.

Here's an example of a test case for a custom Odoo module that tests the creation of a new record:

Example of a simple test case


from odoo.tests.common import TransactionCase

class TestMyModule(TransactionCase):

​def setUp(self):
​super(TestMyModule, self).setUp()

​def test_00_check_create_record(self):
​# Create a new record
​record = self.env["my.module"].create({"name": "Test 1"})

​# Check Assert for record was successfully created.
​self.assertTrue(record, "Record is not created!")
​self.assertEqual(record.name, "Test 1", "Record name is not matched!")


Different Classes of Test Cases

When testing an Odoo application, you can use Python's built-in testing framework called unit test to create different classes of test cases.

Here are some of the most commonly used classes:

TestCase: This is the base class for all test cases. You can use it to define common setup and teardown methods that should be executed before and after each test method.

SimpleTestCase: This class provides some simple assertions that you can use to check for specific conditions in your test cases. For example, you can use assertEqual to check that two values are equal.

TransactionCase: This class is useful when testing database-related functionality. It wraps each test method in a transaction so that any changes made to the database during the test are rolled back at the end of the test.

HttpCase: This class is used to test web-related functionality. It provides methods for sending HTTP requests and checking the response.

FunctionalCase: This class is a more advanced version of HttpCase. It provides additional methods for interacting with the Odoo user interface, such as filling out forms and clicking buttons.

SecurityCase: This class is used to test security-related functionality, such as access controls and permissions.

PerformanceCase: This class is used to test the performance of an Odoo application. It provides methods for measuring the time taken to execute certain operations, such as searching for records or computing the total cost of an order.

These are just a few of the classes that you can use when testing an Odoo application in Python. The specific classes you use will depend on the type of functionality you are testing and the level of detail you require in your tests.

You can also check with different methods of the assert (See the tables below for common uses of assert methods):

Methods:

  1. assertEqual(a, b):  a == b
  2. assertNotEqual(a, b): a != b
  3. assertTrue(a): bool(a) is True
  4. assertFalse(a): bool(a) is False
  5. assertIn(a, b): a in b
  6. assertNotIn(a, b): a not in b
  7. assertRaises(exception): Check the exception
  8. assertLogs(logger, level): The with​ block logs on the logger with the minimum level
  9. assertNoLogs(logger, level): The with​ block doesn't log on logger with the minimum level

assertRaises(exception, *, msg=None):

Test that an exception is raised when callable is called with any positional or keyword arguments that are also passed to assertRaises(). The test passes if an exception is raised, is an error if another exception is raised, or fails if no exception is raised. To catch any of a group of exceptions, a tuple containing the exception classes may be passed as an exception.

If only the exception and possibly the msg arguments are given, return a context manager so that the code under test can be written inline rather than as a function:

Syntax of assertRaises:

with self.assertRaises(SomeException):

	​do_something() # which should raise the SomeException

Examples of the assertRaises:

Note:

​- What we have to achieve in this test case and how you can use the assertRaises method.

​- When the record of the state is waiting, you can not cancel the record. If you try to cancel that record it will raise a UserError.

​- Assume the action_cancel() method is to cancel the record.

​- Same you can use with different different types of Raises based on your Exception/Errors.

​- Like AccessError,  ValidationError, AssertionError, and UserError


from odoo.tests.common import TransactionCase

from odoo.exceptions import UserError
class TestMyModule(TransactionCase):

​def setUp(self):
​super(TestMyModule, self).setUp()
​# Create a new record
​self.test_record = self.env["my.module"].create({"name": "Test 1", "state": "draft"})

​def test_00_check_create_record(self​
​# Check Assert for record was in draft state
​self.assertEqual(self.test_record.state, "draft", "Record name is not matched!")

​# Change the state from Draft to Waiting state​
​self.test_record.state = "waiting"

​# Check assertRaises
​with self.assertRaises(UserError):
​self.test_record.action_cancel()

assertLogs(logger=None, level=None):

A context manager to test that at least one message is logged on the logger or one of its children, with at least the given level.

If given, the logger should be logging.Logger object or str giving the name of a logger. The default is the root logger, which will catch all messages that were not blocked by a non-propagating descendent logger.

If given, the level should be either a numeric logging level or its string equivalent (for example either "ERROR" or logging. ERROR). The default is logging.INFO.

The test passes if at least one message emitted inside the block matches the logger and level conditions, otherwise, it fails.

The object returned by the context manager is a recording helper which keeps track of the matching log messages. It has two attributes:

records

​A list of logging.LogRecord objects of the matching log messages.

output

​A list of str objects with the formatted output of matching messages.

Example:

with self.assertLog("foo", level='INFO') as cm:
​logging.getLogger("foo").info("first message")
​logging.getLogger("foo.bar").error("second message")
self.assertEqual(cm.output, ["INFO:foo:first message", "ERROR:foo.bar:second message"])
with self.assertLogs(level="WARNING") as log_catcher:
​test_module_form = Form(self.env['my.module'])
​test_module_form.name = "Test Record"
​test_module = test_module_form.save()
​_logger.warning('Dummy')
self.assertEqual(len(log_catcher.output), 1, "Useless warnings: \n%s" % "\n".join(log_catcher.output[:-1]))


When to write automated test cases in Odoo:


It is recommended to write automated test cases for Odoo modules as early as possible in the development process. This helps to catch bugs and errors before they become more difficult and expensive to fix.

You should write automated test cases whenever you make changes to your code, such as adding new features, fixing bugs, or refactoring code. This ensures that your changes do not introduce new bugs or break existing functionality.

How much code coverage is recommended for Odoo modules:


The recommended amount of code coverage for Odoo modules depends on the complexity of the module and the criticality of its functionality. In general, code coverage of at least 80% is recommended.

To achieve this level of code coverage, you should aim to cover all the important use cases and edge cases of your module. This includes testing all the different paths through your code and handling all possible error conditions.

In addition to unit tests, you should also consider writing integration tests that test the interactions between different Odoo modules and system components. This ensures that your module works correctly in the context of the wider system.

Here's an example of coverage of a custom Odoo module:

You can check in the terminal for the coverage of the module as an example:
 - coverage run --source=${path to the module} ${SERVER-RUN-COMMAND} -u ${MODULE-NAME} --test-enable

To check in the terminal how much you covered the code of your custom module:

- coverage report -m
It will be shown in the terminal how much code is covered in percentage (also shown as a percentage per file).

To check which code is covered and which is not covered you have to run the command

- coverage html
The new file will be generated in the folder path of your odoo: odoo >> htmlcov >> index.html


Share this post
Code Quality Resolves Performance Issue
Performance issues while developing modules