Yu Chen
Yu Chen

Reputation: 7460

Python unittest loader throws TypeError during an __init__() call

I've written a bunch of tests for my Python application, but they've suddenly appeared to no longer be working properly!

I've created a bunch of tests inside a tests module:

enter image description here

Inside of tests.__main__.py, I've included the following code to load my test suite:

import unittest

if __name__ == "__main__":
    loader = unittest.TestLoader()
    start_dir = "."
    suite = loader.discover(start_dir=start_dir, pattern='*_test.py')

    runner = unittest.TextTestRunner()
    runner.run(suite)

I know I am going to sound like a complete noob, but these tests were working perfectly for me about an hour ago. I would issue them by typing python3 -m tests from my base directory. However, I'm now receiving a really strange TypeError:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/yuchen/Desktop/myproj/myapp/tests/__main__.py", line 6, in <module>
    suite = loader.discover(start_dir=start_dir, pattern='*_test.py')
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 341, in discover
    tests = list(self._find_tests(start_dir, pattern))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 406, in _find_tests
    yield from self._find_tests(full_path, pattern, namespace)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 406, in _find_tests
    yield from self._find_tests(full_path, pattern, namespace)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 398, in _find_tests
    full_path, pattern, namespace)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 452, in _find_test_path
    return self.loadTestsFromModule(module, pattern=pattern), False
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 123, in loadTestsFromModule
    tests.append(self.loadTestsFromTestCase(obj))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 92, in loadTestsFromTestCase
    loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/suite.py", line 24, in __init__
    self.addTests(tests)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/suite.py", line 57, in addTests
    for test in tests:
TypeError: __init__() takes 0 positional arguments but 2 were given

I've followed the stack trace down to what I believe is the appropriate file causing the issue, the suite.py file of unittest:

"""TestSuite"""

import sys

from . import case
from . import util

__unittest = True


def _call_if_exists(parent, attr):
    func = getattr(parent, attr, lambda: None)
    func()


class BaseTestSuite(object):
    """A simple test suite that doesn't provide class or module shared fixtures.
    """
    _cleanup = True

    def __init__(self, tests=()):
        self._tests = []
        self._removed_tests = 0
        print("Tests", tests) # <--- this was added by me
        self.addTests(tests) 

However, the __init__() signature doesn't appear to require 0 positional arguments. Moreover, it's clearly executing, since after I added in print("Tests", tests) in the code above, I saw the following output before the error was thrown:

Tests: []
Tests: <map object at 0x11033c0b8>
Tests: <map object at 0x11033c0b8>
Tests: <map object at 0x11033c0b8>
Tests: [<unittest.suite.TestSuite tests=[... a list of my tests]]
Tests: <map object at 0x110483978>
Tests: <map object at 0x110483978>
Traceback (most recent call last):

I'm pretty at a loss for what is going on. The error printed in the stack trace doesn't appear to correspond at all with what I'm seeing in the source code.

Edit (Resolution):

The accepted answer was spot on. I had written another test class, and decided to simply add in a stub for the __init__ method:

class MyUnfinishedTest(BaseTest):

    def __init__():
        pass

Then I had forgotten about it and worked on other stuff, and ran the tests. That's when I started seeing my error. After removing this class from my test module, the tests ran smoothly.

Upvotes: 2

Views: 494

Answers (1)

Will Keeling
Will Keeling

Reputation: 23004

One of the reasons that the traceback seems a little confusing is that the line:

for test in tests

is actually hitting a generator, which is discovering and loading the tests on the fly.

So I'm guessing that this:

TypeError: __init__() takes 0 positional arguments but 2 were given

actually means that the discovery process has found a test class it cannot load, possibly because that class has its own __init__ method with a different number of arguments to what discover expects.

I would double-check your test classes for __init__ methods, and possibly also change this line:

start_dir = "."

to:

start_dir = os.path.dirname(__file__)

That would mean that the discovery process would start looking from the tests folder down, which is safer than "." (current working directory) because the current working directory might change depending upon where you start the tests from (and may end up picking up tests that you weren't expecting).

Upvotes: 1

Related Questions