With python and nosetests I have the following setup:
- package
- __init__.py
- test1.py
- test2.py
The __init__.py module contains a set up function
def setup():
print("Setup called")
var = 42
which will be used later to create a unique identified (different between running the tests, but the same for all the tests inside the package).
How can the tests itself access this variable (in this example case var)? The test scripts are just some stubs:
from nose.tools import assert_true
class TestSuite(object):
def test1(self):
# How to get content of 'var' here?
assert_true(True)
Is there some pythonic way to do this, or just use an environment variable to do this?
nose calls .setup() methods inside classes:
class Test:
def setup(self):
self.var = 1
def test_print_var(self):
print(self.var)
This also applies to methods inherited from elsewhere:
class TestBase:
def setup(self):
self.var = 1
class Test(TestBase):
def test_print_var(self):
print(self.var)
Related
Context
I have a test class where all my tests inherit from. It cant run by itself as it really doesnt contain any setup info
I wanted to add a test which is executed by ALL tests (adding it to the baseclass seems logical)
But now I notice the basetestclass( => Foo) which I import is being detected as a test itself and runs and is visible in the reports
Code
the base class in base.py
from unittest import TestCase
class Foo(TestCase):
#classmethod
def setUpClass(cls):
# prepare the generic setup stuff based on what is defined in the child class
print("setupclass Foo done")
def test_run_in_all_inherited_tests(self):
print("fooBar")
assert True
the real test in test_something.py
from base import Foo # <= This is being detected as a testclass object and thus will be executed
class TestFoo(Foo):
#classmethod
def setUpClass(cls):
# define specific test setup
super().setUpClass()
print("setup TestFoo done")
def test_pass(self):
pass
def test_assert(self):
assert False
This triggers a testrun of the imported Foo
The Question
How can I import Foo without that its being detected as a 'test'
If I remove the test to run in all tests all is fine.
Adding #nottest decorator to Foo wont work since then also all inherited classes are defined nottest.
It needs to run on nose, pytest and unittest testrunners
I noticed if I changed the import statement like below that it also works. But that would mean adjusting a few hundreds of testfiles in different repos. (I'd like to avoid that)
import base
class TestFoo(base.Foo):
The key to the answer seems to be that each test has an attribute __test__ which is set to True when it is a test.
Setting it to False when the class should not be a test will then let the test collector ignore this class.
The answer assumes I can only do changes in the base.py
In python 3.9 classmethod and property decorators can be combined so I wrote a separate answer for that
answer for < py3.9
the base class in base.py
from unittest import TestCase
class MetaFoo(type):
#property
def __test__(cls):
return cls != Foo
class Foo(TestCase, metaclass=MetaFoo):
#classmethod
def setUpClass(cls):
# prepare the generic setup stuff based on what is defined in the child class
print("setupclass Foo done")
def test_run_in_all_inherited_tests(self):
print("fooBar")
assert True
answer for >= py3.9
the base class in base.py
from unittest import TestCase
class Foo(TestCase):
#classmethod
#property
def __test__(cls):
return cls != Foo
#classmethod
def setUpClass(cls):
# prepare the generic setup stuff based on what is defined in the child class
print("setupclass Foo done")
def test_run_in_all_inherited_tests(self):
print("fooBar")
assert True
the actual test
test_something.py
from base import Foo # <= This will not be detected as a test anymore as __test__ returns False
class TestFoo(Foo):
#classmethod
def setUpClass(cls):
# define specific test setup
super().setUpClass()
print("setup TestFoo done")
def test_pass(self):
pass
def test_assert(self):
assert False
This doesnt trigger a testrun of the imported Foo anymore
I am creating an object within conftest.py and use it in some fixtures. I also need to use this object within my test-modules. Currently I am importing conftest.py inside my test-modules and make use of that 'helper' object. I am pretty sure that this is not the recommended way. I am looking forward to your suggestions.
Thank you :)
Following is the dummy-coded version of my question:
conftest.py
import pytest
class Helper():
def __init__(self, img_path:str):
self.img_path = img_path
def grayscale(self):
pass
def foo(self):
pass
helper = Helper("sample.png")
#pytest.fixture()
def sample():
return helper.grayscale()
test_module.py
import conftest
helper = conftest.helper
def test_method1(sample):
helper.foo()
...
As already commented, I also handled such scenarios by fixtures before if I have a helper class in tests.
conftest.py
import pytest
class Helper():
def __init__(self, img_path: str):
self.img_path = img_path
def grayscale(self):
pass
def foo(self):
pass
#pytest.fixture(scope="session")
def helper():
return Helper("sample.png")
#pytest.fixture()
def sample(helper):
return helper.grayscale()
test_module.py
def test_method1(helper, sample):
helper.foo()
I have a code structure where we have a lot of sub classes from a common base class.
I want in an automated fashion test each subclass without having a separate class definition inheriting from TestCase for each subclass.
with a classs like
class MyBaseClass:
...
I can get a list of all subclasses that inherits from MyBaseClass with
import all_module_that_includes_subclasses
list_of_all_subclasses = MyBaseClass.__subclasses__()
But how do I connect to this list of scubclasses to avoid manually createing TestCases for each subclass like:
class TestSubClass1(TestCase):
def test_method_1(self):
...
...
class TestSubClass2(TestCase):
def test_method_1(self):
...
...
etc.
I thought of setting up an instance of all classes in setUp() method and looping through them in each def test_method_#(). This will somewhat work, I think, but it will break as soon as one single class fails. I want to be able to run through all classes and get a full report what class failed and in what tests.
Thankful for help :)
You can create those test cases dynamically using the load_tests protocol. For this you simply define a top-level function called load_tests which should return a TestSuite.
import unittest
class MyBaseClass:
pass
class Foo(MyBaseClass):
pass
class Bar(MyBaseClass):
pass
class TestBases:
# Wrapped into other class, so it won't be discovered by unittest.
class TestCase(unittest.TestCase):
subclass: MyBaseClass
def test(self):
self.assertIsInstance(self.subclass(), MyBaseClass)
def load_tests(loader, tests, pattern):
for cls in MyBaseClass.__subclasses__():
test_cls = type(f'Test{cls.__name__}', (TestBases.TestCase,), dict(subclass=cls))
tests.addTests(loader.loadTestsFromTestCase(test_cls))
return tests
if __name__ == '__main__':
unittest.main()
This runs the two dynamically generated test cases TestFoo and TestBar:
$ python /tmp/test.py -v
test (__main__.TestFoo) ... ok
test (__main__.TestBar) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
Python's unittest framework defines the addTypeEqualityFunc method, which "Registers a type-specific method called by assertEqual() to check if two objects of exactly the same typeobj (not subclasses) compare equal."
What are the scope an mechanism of this registration? The documentation does not discuss either, and "python registration" is resistant to searching.
Mostly, I am curious about whether this "registration" is some kind of assignment in local scope, or if Python has a broader concept of registration I haven't been able to unearth in the docs.
It applies to the current test only. Python unittest does not have some broader concept of "registration" in this case. Both the tests below will pass:
# run these tests with "python -m unittest" in same directory as this file.
from unittest import TestCase
class Potato:
pass
def potato_compare(potato1, potato2, msg=None):
return True
class Test1(TestCase):
def test_one(self):
spud = Potato()
yam = Potato()
self.addTypeEqualityFunc(Potato, potato_compare)
self.assertEqual(spud, yam)
def test_two(self):
spud = Potato()
yam = Potato()
self.assertNotEqual(spud, yam)
If you want it to apply to every test, you can add it to setUp:
class Test2(TestCase):
def setUp(self):
self.addTypeEqualityFunc(Potato, potato_compare)
def test_one(self):
spud = Potato()
yam = Potato()
self.assertEqual(spud, yam)
def test_two(self):
spud = Potato()
yam = Potato()
self.assertEqual(spud, yam)
Warning: This functionality does not recurse into containers. For example, [spud] and [yam] will not be recognized as equal lists.
How can I add simple test method from unittest.TestCase to TestSuite. As I see it is only possible to add whole class only to suite, for example I want something like this:
import unittest
class MyBaseTestCase(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.abs = "test"
class MyTestClass(MyBaseTestCase):
def test_abs(self):
if self.abs:
pass
class MyTestSuite(unittest.TestSuite):
def __init__(self):
super().__init__()
self.addTest(MyTestClass.test_abs)
Here I get an error: AttributeError: 'TeamcityTestResult' object has no attribute 'abs'. It seems like it runs as a test, but setUpClass does not calls.
How did you run the test suite? I used your code and ran it using 'python3 -m unittest test.py':
import unittest
class MyBaseTestCase(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.abs = "test"
class MyTestClass(MyBaseTestCase):
def test_abs(self):
if self.abs:
pass
class MyTestSuite(unittest.TestSuite):
def __init__(self):
super().__init__()
self.addTest(MyTestClass.test_abs)
And it works.