Background
We have a special setup for which I want to write a TestCase base class / mixin that can be used to provide functionality out-of-the-box for every TestCase. The class which provides functionality to the TestCase needs to be a single instance and is therefore implemented as singleton.
Observation
When making it a singleton,
This is a very strange behavior I currently can not explain.
Reproduction
I did test a minimal working example (MWE) to reproduce this issue and it is reproducible with the following minimal code. Note that I tried to minimize as much as possible. The code therefore does not have any other purpose than this reproduction and does not follow any coding standards.
test_reproduction.py
import unittest
from unittest import TestCase
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class SpecialMethods(metaclass=Singleton):
pass
class SpecialTestCase(TestCase, SpecialMethods):
def __init__(self, methodName="runTest") -> None:
TestCase.__init__(self, methodName)
SpecialMethods.__init__(self)
class TestSomething(SpecialTestCase):
def test_something1(self):
print('print test_something1')
self.assertTrue(True)
def test_something2(self):
print('print test_something2')
self.assertTrue(True)
def test_something3(self):
print('print test_something3')
self.assertTrue(True)
if __name__ == "__main__":
unittest.main()
When executed with: python3 -m unittest -v test_reproduction
this yields the following output:
test_something1 (test_reproduction.TestSomething) ... print test_something1
ok
test_something1 (test_reproduction.TestSomething) ... print test_something1
ok
test_something1 (test_reproduction.TestSomething) ... print test_something1
ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Notice that only test_something1 is executed but three times. test_something2 and test_something3 are not executed.
Question
Does anyone have an explanation for what is going on? And any chance that I can make it work via Mixins (I already have other solutions, I'm only interested in making it work via Mixin).
There are a few things that feel wrong with your design - but you are getting what you are asking for: you create an ICBM, complete with multi-ogives to have a "singleton" by using a metaclass to create a singleton. Than, that is what you get.
We usually don't need to care how many instances or what does unittest does with the test classes we create for running all the tests: but it is obvious that you modified simple and common class behavior, that just make it break. In other words: unittest obviously have internal mechanisms to create a new instance of a class for each test it will run, and this does not work with your arrangement.
Instead of trying to figure out the inner workings of unittest, and how it marks in each instance which test it is running, it is better just to move away from your mess.
First: singletons are overrated. Metaclasses for creating a singleton are doubly overrated and wrong.
Second: multiple inheritance is good in very specific cases. Wanting to combine a class that "must be a singleton" with a class that needs multiple instances to run the tests, obviously, is not one of those cases. Use composition instead.
So, start with:
class SpecialMethods: # <- that, just create the class
def my_special_method_1(self):
# do special things
...
SpecialMethods = SpecialMethods() # <- here is your singleton.
# no one is creating another instance of this by accident
class TestSomething(TestCase): # no even need for a intermediate class or a custom __init__
def test_something1(self):
print('print test_something1')
SpecialMethods.my_special_method1() # <surprise: this just works!!
self.assertTrue(True)
As a final, not fully related tip: maybe you could use "pytest" for your tests instead of unittest: tests needs a lot less boiler-test code, including there is no need for the creation of artificial classes just to name-space the test functions.