I'm currently trying to rework how we do unit testing of our code. For this we decided to go with an intermediate format of serialized test descriptions in xml. The general structure consisted of a Base class (derived from unittest.Testcase) and then classes per configuration (previously a bunch of python files in their own folders) that inherit from this Base Class.
We've changed the way we call the and discover the unit tests, but I'm having a problem with regards to the SetupClass method of unittest.
Previously, this method had been overwritten in the Base Class and it needed to be called with a number of variables distinct for each test case, which we did from the derived classes SetupClass methods (call to super(...).SetupClass(,vars...).
Now that we're trying to get rid of the python files that hold this configuration in rather verbose and hard to track/configure way, I've run into a problem.
Example of an xml config file:
<TestConfig>
<Cases>
<TestCase>
<Name>Run Time</Name>
<GUID>1234-5678</GUID>
<Comparison>LessThan</Comparison>
<Value>22</Value>
</TestCase>
<TestCase>
<Name>Memory Usage</Name>
<GUID>5678-1234</GUID>
<Comparison>Equal</Comparison>
<Value>150</Value>
</TestCase>
</Cases>
</TestConfig>
My current implementation of the Base Class:
class MyUnitTestBase(unittest.TestCase):
#classmethod
def SetupClass(self, var1, var2,...):
some_internal_setup_stuff using var1 etc.
Now I'm searching for all the config files in my folder structure and using that list to try and setup a test suite of all the tests I want to run.
def parseTestSuite(cfg_files):
all_suites = unittest.TestSuite()
for cfg_file in cfg_files:
suite = unittest.TestSuite()
class MyUnitTestDerived(MyUnitTestBase):
pass
try:
tree = eTree.parse(cfg_file)
root = tree.getroot()
for section in root[0]:
for item in section:
test_item = TestItem() #parsing helper
test_item.parse_from_xml(item) #my parsing of the settings
test_name = 'test_%s' % (test_item.name).replace(" ","_")
setattr(MyUnitTestDerived, test_name, test_generator(test_item.comparison, test_item.exp_value,test_item.comp_value))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(MyUnitTestDerived))
except:
raise
all_suites.addTests(suite)
return all_suites
and to help with the test method generation:
def test_generator(comparison, exp_value, comp_value):
if comparison=="LessThan":
def test(self):
self.assertLessEqual(exp_value, comp_value)
elsif comparison=="Equal":
def test(self):
self.assertEqual(exp_value, comp_value)
elif xxx:
etc...
return test
The detection seems to work, as does the parsing and the filling of the suite with the correct tests seems to work as well.
What I'm struggling with is however that I need to call the setupClass method with variables, whereas here I cannot. I haven't figured out yet where I would add this and how I could make this work again with the old BaseClass implementation of it.
One thing I've tried (and I'm sure I'm going to python-hell for this) is replacing the pass with:
#classmethod
def setUpClass(self, var1 = var1_from_conf, var2 = var2_from_conf, etc):
return super(MyUnitTestDerived, self).setUpClass(var1 = var1_from_conf, var2 = var2_from_conf, etc)
in order to force default setup to use the variables from the config that are already known at that point in time, but it doesn't seem to even ever call this setUpClass (tried with debug print).
I'd appreciate any sort of help you can give me. I know it is quite a convoluted problem, but I've tried to break it down into as simple a case as I could.
Related
I'm fairly new to testing in Python and I've come upon some weird behavior which I can't understand.
I have mocked class MockedClass and I'm using it in two test classes like this (both classes are in the same file):
First class:
class TestClass1(unittest.TestCase):
def setUp(self):
self.mocked_object_1 = MockedClass()
Second class:
class TestClass2(unittest.TestCase):
def setUp(self):
self.mocked_object_2 = MockedClass()
Mocked objects are passed as arguments to tested functions. What happens now is that when these two tests are run separately (in different files), all tests in those classes are successful, but when they are both run in the same file, all test cases in TestClass2 fail.
I managed to solve this (somehow) by declaring global temp_mocked_object and doing this:
class TestClass1(unittest.TestCase):
def setUp(self):
temp_mocked_object = self.mocked_object_1
self.mocked_object_1 = MockedClass()
def tearDown(self):
self.mocked_object_1 = temp_mocked_object
But I don't see why would this problem even occur, much less do I understand why this solution worked. What confuses me the most is that second test fails despite different mocked object names.
Mocked class is structured this way:
class MockedClass(someScript.OriginalClass):
def __init__(self):
# some unimportant objects are initialized here
I'm working in older Python version (2.7), but I think that doesn't matter.
So I would greatly appreciate some kind of explanation about what's going on here, since I couldn't find the answer elsewhere.
I am trying to create test classes that aren't unittest based.
This method under this class
class ClassUnderTestTests:
def test_something(self):
cannot be detected and run when you call py.test from the command line or when you run this test in PyCharm (it's on its own module).
This
def test_something(self):
same method outside of a class can be detected and run.
I'd like to group my tests under classes and unless I'm missing something I'm following the py.test spec to do that.
Environment: Windows 7, PyCharm with py.test set as the test runner.
By convention it searches for
Test prefixed test classes (without an init method)
eg.
# content of test_class.py
class TestClass:
def test_one(self):
x = "this"
assert 'h' in x
def test_two(self):
x = "hello"
assert hasattr(x, 'check')
# this works too
#staticmethod
def test_three():
pass
# this doesn't work
##classmethod
#def test_three(cls):
# pass
See the docs:
Group multiple tests in a class
Conventions for Python test discovery
The accepted answer is not incorrect, but it is incomplete. Also, the link it contains to the documentation no longer works, nor does the updated link in the a comment on that answer.
The current documentation can now be found here. The relevant bits of that doc are:
...
In those directories, search for test_*.py or *_test.py files, imported by their test package name.
From those files, collect test items:
...
test prefixed test functions or methods inside Test prefixed test classes (without an __init__ method)
The key bit that is missing in the accepted answer is that not only must the class name start with Test and not have an __init__ method, but also, the name of the file containing the class MUST be of one of the forms test_*.py or *_test.py.
Where I got tripped up here, and I assume many others will too, is that I generally name my Python source files containing only a class definition to directly mirror the name of the class. So if my class is named MyClass, I normally put its code in a file named MyClass.py. I then put test code for my class in a file named TestMyClass.py. This won't work with PyTest. To let PyTest do its thing with my test class, I need to name the file for this class test_MyClass.py or MyClass_test.py. I chose the last form so that I generally add a '_test' suffix to the file names I'd otherwise choose that need to be named so that PyTest will parse them looking for tests.
I'm working on a module using sockets with hundreds of test cases. Which is nice. Except now I need to test all of the cases with and without socket.setdefaulttimeout( 60 )... Please don't tell me cut and paste all the tests and set/remove a default timeout in setup/teardown.
Honestly, I get that having each test case laid out on it's own is good practice, but i also don't like to repeat myself. This is really just testing in a different context not different tests.
i see that unittest supports module level setup/teardown fixtures, but it isn't obvious to me how to convert my one test module into testing itself twice with two different setups.
any help would be much appreciated.
you could do something like this:
class TestCommon(unittest.TestCase):
def method_one(self):
# code for your first test
pass
def method_two(self):
# code for your second test
pass
class TestWithSetupA(TestCommon):
def SetUp(self):
# setup for context A
do_setup_a_stuff()
def test_method_one(self):
self.method_one()
def test_method_two(self):
self.method_two()
class TestWithSetupB(TestCommon):
def SetUp(self):
# setup for context B
do_setup_b_stuff()
def test_method_one(self):
self.method_one()
def test_method_two(self):
self.method_two()
The other answers on this question are valid in as much as they make it possible to actually perform the tests under multiple environments, but in playing around with the options I think I like a more self contained approach. I'm using suites and results to organize and display results of tests. In order to run one tests with two environments rather than two tests I took this approach - create a TestSuite subclass.
class FixtureSuite(unittest.TestSuite):
def run(self, result, debug=False):
socket.setdefaulttimeout(30)
super().run(result, debug)
socket.setdefaulttimeout(None)
...
suite1 = unittest.TestSuite(testCases)
suite2 = FixtureSuite(testCases)
fullSuite = unittest.TestSuite([suite1,suite2])
unittest.TextTestRunner(verbosity=2).run(fullSuite)
I would do it like this:
Make all of your tests derive from your own TestCase class, let's call it SynapticTestCase.
In SynapticTestCase.setUp(), examine an environment variable to determine whether to set the socket timeout or not.
Run your entire test suite twice, once with the environment variable set one way, then again with it set the other way.
Write a small shell script to invoke the test suite both ways.
If your code does not call socket.setdefaulttimeout then you can run tests the following way:
import socket
socket.setdeaulttimeout(60)
old_setdefaulttimeout, socket.setdefaulttimeout = socket.setdefaulttimeout, None
unittest.main()
socket.setdefaulttimeout = old_setdefaulttimeout
It is a hack, but it can work
You could also inherit and rerun the original suite, but overwrite the whole setUp or a part of it:
class TestOriginal(TestCommon):
def SetUp(self):
# common setUp here
self.current_setUp()
def current_setUp(self):
# your first setUp
pass
def test_one(self):
# your test
pass
def test_two(self):
# another test
pass
class TestWithNewSetup(TestOriginal):
def current_setUp(self):
# overwrite your first current_setUp
I am testing classes that parse XML and create DB objects (for a Django app).
There is a separate parser/creater class for each different XML type that we read (they all create essentially the same objects). Each parser class has the same superclass so they all have the same interface.
How do I define one set of tests, and provide a list of the parser classes, and have the set of tests run using each parser class? The parser class would define a filename prefix so that it reads the proper input file and the desired result file.
I want all the tests to be run (it shouldn't stop when one breaks), and when one breaks it should report the parser class name.
With nose, you can define test generators. You can define the test case and then write a test generator which will yield one test function for each parser class.
If you are using unittest, which has the advantage of being supported by django and installed on most systems, you can do something like:
class TestBase(unittest.TestCase)
testing_class = None
def setUp(self):
self.testObject = testing_class(foo, bar)
and then to run the tests:
for cls in [class1, class2, class3]:
testclass = type('Test'+cls.__name, (TestBase, ), {'testing_class': cls})
suite = unittest.TestLoader().loadTestsFromTestCase(testclass)
unittest.TextTestRunner(verbosity=2).run(suite)
I haven't tested this code but I've done stuff like this before.
I have a group of test cases that all should have exactly the same test done, along the lines of "Does method x return the name of an existing file?"
I thought that the best way to do it would be a base class deriving from TestCase that they all share, and simply add the test to that class. Unfortunately, the testing framework still tries to run the test for the base class, where it doesn't make sense.
class SharedTest(TestCase):
def x(self):
...do test...
class OneTestCase(SharedTest):
...my tests are performed, and 'SharedTest.x()'...
I tried to hack in a check to simply skip the test if it's called on an object of the base class rather than a derived class like this:
class SharedTest(TestCase):
def x(self):
if type(self) != type(SharedTest()):
...do test...
else:
pass
but got this error:
ValueError: no such test method in <class 'tests.SharedTest'>: runTest
First, I'd like any elegant suggestions for doing this. Second, though I don't really want to use the type() hack, I would like to understand why it's not working.
You could use a mixin by taking advantage that the test runner only runs tests inheriting from unittest.TestCase (which Django's TestCase inherits from.) For example:
class SharedTestMixin(object):
# This class will not be executed by the test runner (it inherits from object, not unittest.TestCase.
# If it did, assertEquals would fail , as it is not a method that exists in `object`
def test_common(self):
self.assertEquals(1, 1)
class TestOne(TestCase, SharedTestMixin):
def test_something(self):
pass
# test_common is also run
class TestTwo(TestCase, SharedTestMixin):
def test_another_thing(self):
pass
# test_common is also run
For more information on why this works do a search for python method resolution order and multiple inheritance.
I faced a similar problem. I couldn't prevent the test method in the base class being executed but I ensured that it did not exercise any actual code. I did this by checking for an attribute and returning immediately if it was set. This attribute was only set for the Base class and hence the tests ran everywhere else but the base class.
class SharedTest(TestCase):
def setUp(self):
self.do_not_run = True
def test_foo(self):
if getattr(self, 'do_not_run', False):
return
# Rest of the test body.
class OneTestCase(SharedTest):
def setUp(self):
super(OneTestCase, self).setUp()
self.do_not_run = False
This is a bit of a hack. There is probably a better way to do this but I am not sure how.
Update
As sdolan says a mixin is the right way. Why didn't I see that before?
Update 2
(After reading comments) It would be nice if (1) the superclass method could avoid the hackish if getattr(self, 'do_not_run', False): check; (2) if the number of tests were counted accurately.
There is a possible way to do this. Django picks up and executes all test classes in tests, be it tests.py or a package with that name. If the test superclass is declared outside the tests module then this won't happen. It can still be inherited by test classes. For instance SharedTest can be located in app.utils and then used by the test cases. This would be a cleaner version of the above solution.
# module app.utils.test
class SharedTest(TestCase):
def test_foo(self):
# Rest of the test body.
# module app.tests
from app.utils import test
class OneTestCase(test.SharedTest):
...