This is a post from my old blog over on colinwren.is, originally published in 2015
The Odoo test runner is by default more of an integration test suite than a unit test suite, being a test runner that requires a database and a module to be installed for it to run the tests for said module.
There a number of reasons that this is less than ideal for those using a TDD approach, namely that for TDD to work you want:
- Tests to be fast so you can test often and get feedback faster
- Tests to work on a unit level so you’re testing the code you’re writing (Integration tests compliment this later)
The basics of an Odoo module test
In Odoo your modules’ models & methods are available to other users via a registry. This gives more flexibility to developers who want to do things like extend or overwrite core functionality as well as develop a namespace that might not be reflected via the module structure.
However with the flexibility comes the compromise of speed. Using registries forces developers to have the database running so they can access the code, this works when running the server but sucks for testing as it adds an overhead. (There is a cool little buildout recipe from Anybox that allows Nose to see the Odoo module namespace if you use buildout).
A simple test looks like this:
In the above test I’m making sure that my module’s method
get_foo is returning the string foo. As this test only requires me to call a method that doesn't depend on any database interaction this is fine but if get_foo was doing a read of the
res.user model for a user's name (from a demo fixture file included in the module) then my
setUp method would look something like this:
If a demo fixture file isn’t used then the user would be created in the database in the
setUp method instead. This still requires database interaction though.
Integration tests are still an important set of tests to run as they allow developers to ensure that their code runs correctly when all the other dependencies of the system are in place and these tests can help catch when a lower level change in the code base affects your code.
So how do we speed things up
As mentioned early the need for a database, demo data fixtures and the module installed pushes you down a path of integration testing but there is a means to patch out registries. Every registry object has two methods that allow you to patch out the return values of a call to the original classes’ methods. These methods are
_revert_method which allow you to patch a method and revert to the original method respectively.
So going back to our example test as the get_foo method is calling the underlying read method on the
res.user model we can just patch the read method and return the string we would hope for. This simulates the value having been in the database but doesn't add the overhead of actually having to go and get the value.
Here’s an example of how to mock out a method on
res.user in a test:
Here’s an example of how to call the original method from inside the mock:
Here’s an example of how to use the args and kwargs sent to the registry to send back conditional information.
Some finer details
As the methods only really replacing the method called they don’t offer some of the niceties of using the mock library. You are unable to track the calls made to the mock method and the args & kwargs are not processed in a way that makes it easy to understand what they mean in regard to the original method’s needs.
Here’s a hack using global vars to solve the call tracking problem:
Speed increases using _patch_method
The speed increases gained using
_patch_method over demo fixtures or building the data in the
setUp step are immense in the tests as part of my example project mocking was 177 times quicker than creating data in
setUp and 15 times quicker than using a demo data file.
Testing times from example project
Using the tests in my example project which tested a simple function to get a user name from
res.users and hash the name using md5. I collected three runs and have provided an average so you can see the speed increase.