Import errors when running nosetests that I can't reproduce outside of nose

Alex Hasha picture Alex Hasha · Apr 24, 2013 · Viewed 8.5k times · Source

I'm running into a mysterious import error when using nosetests to run a test suite that I can't reproduce outside of the nose. Furthermore, the import error disappears when I skip a subset of the tests.

Executive Summary: I am getting an import error in Nose that a) only appears when tests bearing a certain attribute are excluded and b) cannot be reproduced in an interactive python session, even when I ensure that the sys.path is the same for both.

Details:

The package structure looks like this:

project/
    module1/__init__.py
    module1/foo.py
    module1/test/__init__.py
    module1/test/foo_test.py
    module1/test/test_data/foo_test_data.txt
    module2/__init__.py
    module2/bar.py
    module2/test/__init__.py
    module2/test/bar_test.py
    module2/test/test_data/bar_test_data.txt

Some of the tests in foo_test.py are slow, so I've created a @slow decorator to allow me to skip them with a nosetests option:

def slow(func):
    """Decorator sets slow attribute on a test method, so 
       nosetests can skip it in quick test mode."""
    func.slow = True
    return func

class TestFoo(unittest.TestCase):

    @slow
    def test_slow_test(self):
        load_test_data_from("test_data/")
        slow_test_operations_here


    def test_fast_test(self):
        load_test_data_from("test_data/")

When I want to run the fast unit tests only, I use

nosetests -vv -a'!slow'

from the root directory of the project. When I want to run them all, I remove the final argument.

Here comes the detail that I suspect is to blame for this mess. The unit tests need to load test data from files (not best practice, I know.) The files are placed in a directory called "test_data" in each test package, and the unit test code refers to them by a relative path, assuming the unit test is being run from the test/ directory, as shown in the example code above.

To get this to work with running nose from the root directory of the project, I added the following code to init.py in each test package:

import os
import sys

orig_wd = os.getcwd()

def setUp():
    """
    test package setup:  change working directory to the root of the test package, so that 
    relative path to test data will work.
    """
    os.chdir(os.path.dirname(os.path.abspath(__file__)))

def tearDown():
    global orig_wd
    os.chdir(orig_wd)

As far as I understand, nose executes the setUp and tearDown package methods before and after running the tests in that package, which ensures that the unit test can find the appropriate test_data directory, and the working directory is reset to the original value when the tests are complete.

So much for the setup. The problem is, I get an import error only when I run the full suite of tests. The same modules import just fine when I exclude the slow tests. (To clarify, the tests throwing import errors are not slow, so they execute in either scenario.)

$ nosetests
...

ERROR: Failure: ImportError (No module named foo_test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/nose/loader.py", line 413, in loadTestsFromName
    addr.filename, addr.module)
  File "/Library/Python/2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/Library/Python/2.7/site-packages/nose/importer.py", line 80, in importFromDir
    fh, filename, desc = find_module(part, path)
ImportError: No module named foo_test

If I run the test suite without the slow tests, then no error:

$ nosetests -a'!slow'

...

test_fast_test (module1.test.foo_test.TestFoo) ... ok

In a python interactive session, I can import the test module with no trouble:

$ python
Python 2.7.1 (r271:86832, Aug  5 2011, 03:30:24) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import module1.test
>>> module1.test.__path__
['/Users/USER/project/module1/test']
>>> dir(module1.test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'orig_wd', 'os', 'setUp', 'sys', 'tearDown']

When I set a breakpoint in nose/importer.py, things look different:

> /Library/Python/2.7/site-packages/nose/importer.py(83)importFromDir()
-> raise
(Pdb) l
 78                               part, part_fqname, path)
 79                     try:
 80                         fh, filename, desc = find_module(part, path)
 81                     except ImportError, e:
 82                         import pdb; pdb.set_trace()
 83  ->                     raise
 84                     old = sys.modules.get(part_fqname)
 85                     if old is not None:
 86                         # test modules frequently have name overlap; make sure
 87                         # we get a fresh copy of anything we are trying to load
 88                         # from a new path

(Pdb) part
'foo_test'
(Pdb) path
['/Users/USER/project/module1/test']
(Pdb) import module1.test.foo_test
*** ImportError: No module named foo_test
#If I import module1.test, it works, but the __init__.py file is not being executed
(Pdb) import partition.test
(Pdb) del dir
(Pdb) dir(partition.test)
['__doc__', '__file__', '__name__', '__package__', '__path__'] #setUp and tearDown missing?
(Pdb) module1.test.__path__
['/Users/USER/project/module1/test']  #Module path is the same as before.
(Pdb) os.listdir(partition.test.__path__[0])  #All files are right where they should be...
['.svn', '__init__.py', '__init__.pyc', 'foo_test.py', 'foo_test.pyc','test_data']

I see the same screwy results even if I copy sys.path from my interactive session into the pdb session and repeat the above. Can anyone give me any insight about what might be going on? I realize I'm doing several non-standard things at the same time, which could lead to strange interactions. I'd be as interested in advice on how to simplify my architecture as I would be to get an explanation for this bug.

Answer

jdhildeb picture jdhildeb · May 11, 2015

Here is how to track down the context of the error.

nosetests --debug=nose,nose.importer --debug-log=nose_debug <your usual args>

Afterwards, check the nose_debug file. Search for your error message "No module named foo_test". Then look at the preceding few lines to see which files/directories nose was looking at.

In my case, nose was attempting to run some code which I had imported into my codebase - a 3rd party module which contained its own tests, but which I was not intending to include in my test suite. To resolve this, I used the nose-exclude plugin to exclude this directory.