A gotcha when generating code coverage with mocks in Python
I’m a Java guy originally but lately I’ve been rolling up my sleeves and wrestling some pythons. Despite some early “crybabyness” that I didn’t like the environment and that I wasn’t alone, things soon settled into a rhythm that seemed to be working, until I got mocked.
Running nosetests with coverage is pretty simple:
nosetests --with-coverage --cover-erase --cover-package=some_package --cover-inclusive --cover-branches --cover-html --cover-html-dir=coverage-result
Unfortunately no matter what I did in the test, I always just got this error:
some_package.some_module NoSource: No source for code: '/some_path/some_package/some_module.py'
Now it might seem obvious from the error what the problem was, but in my case the “/some_path/some_package/some_module.py” did exist, so it had to be something else.
Immediately assuming the problem must be in the nosetest package as it “could not possibly be my own code” I set about creating a sample case to let the folks at nosetests know just how much smarter than them I truly was.
Naturally my sample test case worked as it should. It wasn’t nosetest.
It turns out the problem was my use of the Mock object in python. My test was verifying some file system operations and I needed to make sure that under certain conditions a check for the existence of a file would return “False”, so I had code in the test that looked like this:
from mock import Mock
os.path.exists = Mock()
os.path.exists.return_value = False
Well it turns out that this changes the behavior of the “os.path.exists” method for everyone, which when you consider that this is a global python library it makes complete sense. As a result the coverage process that is supposed to produce output files was getting all confused.
The correct way to mock out objects within a unit test is using the “patch” syntax around the test methods itself:
from mock import patch
def test_some_file_awesomeness(self, mockPathExists):
mockPathExists.return_value = False
This results in the patching of the os.path.exists method being scoped to the test, and only the test.
So naturally this can only mean one of two things:
1. Python is a terrible language that should be taken out and shot
2. I need to RTFM
I’ll leave it to you to decide.