I have a package I've created which contains a base class, from which future projects will inherit. I'm trying to automate testing, and have turned to nosetests.
I am a novice to Python, so apologies if my issue lies is something trivial/rudimentary.
This project leverages the popular Python Finite State Machine package transitions, from which my base class inherits State (as seen in my states.py).
Upon running $nosetests
from within base_tester/
, I receive 3 repeated errors (only 1 pasted for brevity):
E.EE.
======================================================================
ERROR: Failure: TypeError (__init__() missing 1 required positional argument: 'name')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/.pyenv/versions/3.7.1/lib/python3.7/site-packages/nose/failure.py", line 39, in runTest
raise self.exc_val.with_traceback(self.tb)
File "/home/user/.pyenv/versions/3.7.1/lib/python3.7/site-packages/nose/loader.py", line 523, in makeTest
return self._makeTest(obj, parent)
File "/home/user/.pyenv/versions/3.7.1/lib/python3.7/site-packages/nose/loader.py", line 582, in _makeTest
return MethodTestCase(obj)
File "/home/user/.pyenv/versions/3.7.1/lib/python3.7/site-packages/nose/case.py", line 346, in __init__
self.inst = self.cls()
File "/home/user/repos/openrov/production/testers/base_tester/base_tester/states.py", line 6, in __init__
super().__init__(*args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 'name'
However, when I create a small test project, and implement an inheriting class, it works as intended. I'm left to believe there's something wrong with what I'm doing in test.py, but cannot figure out what.
Thank you!
Project structure:
base_tester/
├── base_tester/
│ ├── __init__.py
│ └── states.py
├── setup.py
└── test/
├── __init__.py
└── test.py
states.py:
from transitions import State
# Parent class
class TestParent(State):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# To be implemented by inheriting classes
def run_test(self):
raise NotImplementedError()
base_tester/init.py:
from base_tester.states import *
test/test.py:
import unittest
from base_tester import TestParent
class Test1(TestParent):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run_test(self):
pass
class Tester(unittest.TestCase):
test1 = Test1(name='test1')
def test_1(self):
self.assertTrue(True)
def main(self):
pass
if __name__ == '__main__':
unittest.main()
The problem lies within the naming of methods in TestParent
and Test1
. More specifically, it is Test1/TestParent.run_test
that causes trouble. I assume that nose
considers these methods to be isolated tests. As a result it tries to create an instance of the class in question and fails since TestParent
,Test1
and State
have no default name
.
See the docs about finding tests. It mentions:
If it looks like a test, it’s a test. Names of directories, modules, classes and functions are compared against the testMatch regular expression, and those that match are considered tests. Any class that is a unittest.TestCase subclass is also collected, so long as it is inside of a module that looks like a test.
That's also the reason why you get the error message multiple times. Your output suggests that you (attempt to) run five tests from which three fail.
I only got 4 but also three failing tests. To illustrate the discovery a bit better I added a default name
to TestParent
and let TestParent.run_test
pass. If I execute nosetests -v
now I get the following output:
base_tester.TestParent.run_test ... ok
test.test.Test1.run_test ... ok
test.test.TestParent.run_test ... ok
test_1 (test.test.Tester) ... ok
TestParent.run_test
is executed twice due to the import. A simple solution would be to avoid method names that match the test pattern regular expression (mentioned in the docs) which by default is (?:\b|_)[Tt]est
. Sometimes however having test
in method names cant be avoided for semantical reasons.
The test discovery mentions the following:
If an object defines a __test__ attribute that does not evaluate to True, that object will not be collected, nor will any objects it contains.
In your case adding __test__ = False
to TestParent
should solve the issue as well:
# Parent class
class TestParent(State):
__test__ = False # avoid nosetests test discover
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# To be implemented by inheriting classes
def run_test(self):
raise NotImplementedError()