Una adición a la respuesta @nailxx 's:
También podemos establecer __test__ = False
en la clase padre y luego utilizar una metaclase (ver This question con algunas explicaciones brillantes) para configurarlo de nuevo a True cuando la subclasificación.
(Finalmente, encontré una excusa para usar una metaclase!)
Aunque __test__
es un atributo de subrayado doble, tenemos que establecer explícitamente que True
, ya que no se establece que causaría pitón sólo para las operaciones de búsqueda el atributo más arriba en el MRO y evaluarlo a False
.
Por lo tanto, debemos verificar en instanciación de clase si una de las clases principales tiene __test__ = False
. Si este es el caso y la definición de clase actual no ha establecido __test__
en sí, agregaremos '__test__': True
a los atributos dict.
El código resultante es el siguiente:
class TestWhenSubclassedMeta(type):
"""Metaclass that sets `__test__` back to `True` when subclassed.
Usage:
>>> class GenericTestCase(TestCase, metaclass=TestWhenSubclassed):
... __test__ = False
...
... def test_something(self):
... self.fail("This test is executed in a subclass, only.")
...
...
>>> class SpecificTestCase(GenericTestCase):
... pass
"""
def __new__(mcs, name, bases, attrs):
ATTR_NAME = '__test__'
VALUE_TO_RESET = False
RESET_VALUE = True
values = [getattr(base, ATTR_NAME) for base in bases
if hasattr(base, ATTR_NAME)]
# only reset if the first attribute is `VALUE_TO_RESET`
try:
first_value = values[0]
except IndexError:
pass
else:
if first_value == VALUE_TO_RESET and ATTR_NAME not in attrs:
attrs[ATTR_NAME] = RESET_VALUE
return super().__new__(mcs, name, bases, attrs)
Se podría extender esto a un comportamiento más implícita como “si el nombre comienza con Abstract
, establecer __test__ = False
automáticamente”, pero para mí sería mantener la asignación explícita para mayor claridad.
Let Me pego unittests simples para demostrar el comportamiento - y como un recordatorio de que todo el mundo debería tener los dos minutos para probar su código después de la introducción de una función.
from unittest import TestCase
from .base import TestWhenSubclassedMeta
class SubclassesTestCase(TestCase):
def test_subclass_resetted(self):
class Base(metaclass=TestWhenSubclassedMeta):
__test__ = False
class C(Base):
pass
self.assertTrue(C.__test__)
self.assertIn('__test__', C.__dict__)
def test_subclass_not_resetted(self):
class Base(metaclass=TestWhenSubclassedMeta):
__test__ = True
class C(Base):
pass
self.assertTrue(C.__test__)
self.assertNotIn('__test__', C.__dict__)
def test_subclass_attr_not_set(self):
class Base(metaclass=TestWhenSubclassedMeta):
pass
class C(Base):
pass
with self.assertRaises(AttributeError):
getattr(C, '__test__')
El 'truco __test__ = false' no es muy útil en el caso de una clase padre, esto obligaría a los niños clases para especificar explícitamente' __test__ = TRUE o sería ignorada, que es un peligroso truco para usar. – Guibod