Mocking tests, round 2.0

python
maya
testing

#1

We have an ancient thread about the possibility of using test mocks for maya unit tests

I’ve been looking at my test coverage and thinking that it would be good to run hard with mocks – for a number of reasons, including making it easier to manage a build server that could run without Maya. My feeling is that most unit tests really only need to verify that you’ve asked maya to do something and that you respond appropriately when Maya sends back a particular result.

Before I invest a bunch of time in this, has anybody in the four years since the last thread actually done this? Do you have any experience or insights you’d care to share?

I did a quick test and this appears to work:


import sys
import mock
from types import ModuleType

_maya = ModuleType('maya')
_cmds = ModuleType('cmds')
_maya.cmds = mock.MagicMock()
sys.modules['maya'] = _maya
sys.modules['maya.cmds'] = _cmds
# the second module exists only to enable imports, 
# we actually use the mock object in the _maya module

# now you can do this:
import maya.cmds as cmds

print cmds.ls()
# <MagicMock name='mock.ls()' id='51113168'>

cmds.ls.side_effect = ['pCube1']
print cmds.ls()
# pCube1

However, I have not yet gotten around to trying to plumb it through a test suite yet. So – anything to look out for ?

In the meantime, more about mocking for the curious.


#2

As far as I’m aware that would be it. The concern I would have with the above is if later tests need those modules non-mocked for whatever reason they would be stuck. The patch decorator and objects are handy in starting/stopping that process to revert those modules back to an original state.

If I find I need some objects mocked across a suite, I usually have a block like this in my setUp:

# Patches
self.some_module_patch = patch('path/to/some_module')
self.mock_some_module = self.some_module_patch.start()
self.mock_some_module.return_value = mock.MagicMock()  # Or whatever value

And end that patch in the tearDown:

# Patches
self.some_module_patch.stop()

I then know with relative certainty that once my tests are all done my state is back to normal.


#3

I’m also a fan of using mock.patch as a decorator.
Though this can get a rather large stack going if you’re patching a whole lot of functions / classes.

class MockVector:
    pass

@mock.patch('maya.api.OpenMaya.MVector', new=MockVector)
class Test_SomethingWithVectors(unittest.TestCase):
    def test_that_thing(self):
        vec = maya.api.OpenMaya.MVector()

#4

I’ve since abandoned this goal and moved on to bringing Maya along with me wherever tests are run.

It solved the problems I was having at the time and haven’t needed to return.


#5

Had no idea you could do that.
Very cool.

Still suffers from the problem of gui related code return False in standalone, so those would still need to be mocked.


#6

I have a vague memory that mayapy does not do the license checks that regular maya does, its that true? So we could move that container around as needed?


#7

I believe you are correct.


#8

The container contains the full Maya distribution, and a fake display for running GUI-related code. It’s enough to boot up a QApplication and spawn Qt GUIs for testing via mayapy, taking screenshots if need be. I haven’t tested the cmds GUI facilities, but with VNC and a licence server, you can take it a step further and actually run and interact with Maya.


#9

Yeah, my comment was purely about cmds driven GUIs, when run from mayapy they all return False instead of the usual string path to the created widget.