0

I wish to add dynamic tests to a python unittest class during setup. Is there any way to get this working?

I know that this works based on the answers on this page:

def generate_test(a, b):
    def test(self):
        self.assertEqual(a, b)

    return test


def add_test_methods(test_class):
    test_list = [[1, 1, '1'], [5, 5, '2'], [0, 0, '3']]
    for case in test_list:
        test = generate_test(case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)


class TestScenario(unittest.TestCase):
    def setUp(self):
        print("setup")


add_test_methods(TestScenario)

if __name__ == '__main__':
    unittest.main(verbosity=1)

But this doesn't:

class TestScenario(unittest.TestCase):
    def setUp(self):
        add_test_methods(TestScenario)

It is unable to find any tests:

Process finished with exit code 5

Empty suite

Empty suite

Any idea why this doesn't work and how could I get it working?

Thanks.

UPDATE:

Tried to invoke add_test_methods from inside the TestScenario in this fashion, but it too doesn't work as it cannot resolve the TestScenario class and throws this error: "ERROR: not found: TestScenario"

class TestScenario(unittest.TestCase):
    add_test_methods(TestScenario)

    def setUp(self):
        pass
Sennin
  • 1,001
  • 2
  • 10
  • 17
  • 1
    I'm going to surmise it's because `unittest` relies on the methods being present *before* `setUp` is called. – juanpa.arrivillaga May 05 '23 at 17:27
  • So have you tried simply calling `add_test_methods` on the class outside of `setUp`, e.g., right after your `class TestScenario` statement, put `add_test_methods(TestScenario)` – juanpa.arrivillaga May 05 '23 at 17:29
  • The test runner executes `setUp` prior to *executing* each method found in the class. You might try using `setUpClass` instead, but I don't recall if that, too, is not executed until it's known that the class contains any tests. (I vaguely recall that `setUpClass` is like `setUp`, only the test runner ensures it is only called before the first test in the class is executed.) – chepner May 05 '23 at 18:46
  • Yes the setUpClass method also doesn't work ( reults in the same 'Empty suite' response). @juanpa.arrivillaga that's an interesting suggesstion but the issue is it is the unable to find the TestScenario class if I do this: class TestScenario(unittest.TestCase): add_test_methods(TestScenario) def setUp(self): pass – Sennin May 06 '23 at 12:21
  • @Sennin sorry, can you show what you tried formatted in the question? – juanpa.arrivillaga May 06 '23 at 18:18
  • You misunderstood me, you need to put `add_test_methods(TestScenario)` **after** the full class definition statement. It would be indented at the level of the module, right after you define the class. Note, the error that would be thrown in this case is a `NameError` – juanpa.arrivillaga May 06 '23 at 19:00
  • @juanpa.arrivillaga sorry for the confusion. Yes if I put 'add_test_methods(TestScenario)' after the class definition, it works. But for me, ideally I wanted to call add_test_methods from the setup/setupClass methods ..as I want to perform some operations before the dynamic tests are generated. – Sennin May 07 '23 at 01:36
  • @Sennin so just perform them? I don't understand what the problem is. – juanpa.arrivillaga May 07 '23 at 02:48

1 Answers1

0

Metclasses is what you are looking for. By passing the metaclass argument to your test class, you can customize the class body. You can create "a dynamic form of the class statement" using the type() method, which allows you to customize the instance creation process AKA __new__ method, with your dynamic methods.

import unittest

class TestSequenceMeta(type):
    def __new__(mcs, name, bases, class_body_methods_dict):
        def _generate_test(a, b):
            def test(self):
                ret = self.assertEqual(a, b)
                print(f"self.assertEqual({a}, {b})")
            return test

        def _get_test_methods(mcs):
            test_list = [[1, 1, '1'], [5, 5, '2'], [0, 0, '3']]
            for case in test_list:
                test_name = "test_%s" % case[2]
                class_body_methods_dict[test_name] = _generate_test(case[0], case[1])
            return class_body_methods_dict

        return type.__new__(mcs, name, bases, _get_test_methods(class_body_methods_dict))
     

class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):
    def setUp(self):
        print("setUp")



if __name__ == '__main__':
    unittest.main(verbosity=1)

So from the previous code you will get:

setUp
self.assertEqual(1, 1)
.setUp
self.assertEqual(5, 5)
.setUp
self.assertEqual(0, 0)
.
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

As expected.