Inheriting from unittest.TestCase for non-test functionality - python

I want to write a class to check sets using exactly the behavior that unittest.TestCase.assertEqual exhibits for testing set equality. It automatically prints a nice message saying which elements are only in the first set and which are only in the second set.
I realize I could implement similar behavior, but since it's already done nicely with unittest.TestCase.assertEqual, I'd prefer to just utilize that (so please no answers that say the unhelpful and already obvious (but not applicable in this case) advice "don't solve this with unittest.TestCase")
Here is my code for the SetChecker class:
import unittest
class SetChecker(unittest.TestCase):
"""
SetChecker(set1, set2) creates a set checker from the two passed Python set
objects. Printing the SetChecker uses unittest.TestCase.assertEqual to test
if the sets are equal and automatically reveal the elements that are in one
set but not the other if they are unequal. This provides an efficient way
to detect differences in possibly large set objects. Note that this is not
a unittest object, just a wrapper to gain access to the helpful behavior of
unittest.TestCase.assertEqual when used on sets.
"""
EQUAL_MSG = "The two sets are equivalent."
def __init__(self, set1, set2, *args, **kwargs):
assert isinstance(set1, set)
assert isinstance(set2, set)
super(self.__class__, self).__init__(*args, **kwargs)
try:
self.assertEqual(set1, set2)
self._str = self.EQUAL_MSG
self._str_lines = [self._str]
self._indxs = None
except AssertionError, e:
self._str = str(e)
self._str_lines = self._str.split('\n')
# Find the locations where a line starts with 'Items '.
# This is the fixed behavior of unittest.TestCase.
self._indxs = [i for i,y in enumerate(self._str_lines)
if y.startswith('Items ')]
def __repr__(self):
"""
Convert SetChecker object into a string to be printed.
"""
return self._str
__str__ = __repr__ # Ensure that `print` and __repr__ do the same thing.
def runTest(self):
"""
Required by any sub-class of unittest.TestCase. Solely used to inherit
from TestCase and is not implemented for any behavior.
"""
pass
def in_first_set_only(self):
"""
Return a list of the items reported to exist only in the first set. If
the sets are equivalent, returns a string saying so.
"""
return (set(self._str_lines[1:self._indxs[1]])
if self._indxs is not None else self.EQUAL_MSG)
def in_second_set_only(self):
"""
Return a list of the items reported to exist only in the second set. If
the sets are equivalent, returns a string saying so.
"""
return (set(self._str_lines[1+self._indxs[1]:])
if self._indxs is not None else self.EQUAL_MSG)
This works fine when I use it in IPython:
In [1]: from util.SetChecker import SetChecker
In [2]: sc = SetChecker(set([1,2,3, 'a']), set([2,3,4, 'b']))
In [3]: sc
Out[3]:
Items in the first set but not the second:
'a'
1
Items in the second set but not the first:
'b'
4
In [4]: print sc
Items in the first set but not the second:
'a'
1
Items in the second set but not the first:
'b'
4
In [5]: sc.in_first_set_only()
Out[5]: set(["'a'", '1'])
In [6]: sc.in_second_set_only()
Out[6]: set(["'b'", '4'])
But now I also want to write unit tests for this class. So I've made a TestSetChecker class. Here is that code:
from util.SetChecker import SetChecker
class TestSetChecker(unittest.TestCase):
"""
Test class for providing efficient comparison and printing of
the difference between to sets.
"""
def setUp(self):
"""
Create examples for testing.
"""
self.set1 = set([1, 2, 3, 'a'])
self.set2 = set([2, 3, 4, 'b'])
self.set3 = set([1,2])
self.set4 = set([1,2])
self.bad_arg = [1,2]
self.expected_first = set(['1', 'a'])
self.expected_second = set(['4', 'b'])
self.expected_equal_message = SetChecker.EQUAL_MSG
self.expected_print_string = (
"Items in the first set but not the second:\n'a'\n1\n"
"Items in the second set but not the first:\n'b'\n4")
def test_init(self):
"""
Test constructor, assertions on args, and that instance is of proper
type and has expected attrs.
"""
s = SetChecker(self.set1, self.set2)
self.assertIsInstance(s, SetChecker)
self.assertTrue(hasattr(s, "_str"))
self.assertTrue(hasattr(s, "_str_lines"))
self.assertTrue(hasattr(s, "_indxs"))
self.assertEqual(s.__repr__, s.__str__)
self.assertRaises(AssertionError, s, *(self.bad_arg, self.set1))
def test_repr(self):
"""
Test that self-printing is correct.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
self.assertEqual(str(s1), self.expected_print_string)
self.assertEqual(str(s2), self.expected_equal_message)
def test_print(self):
"""
Test that calling `print` on SetChecker is correct.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
s1_print_output = s1.__str__()
s2_print_output = s2.__str__()
self.assertEqual(s1_print_output, self.expected_print_string)
self.assertEqual(s2_print_output, self.expected_equal_message)
def test_in_first_set_only(self):
"""
Test that method gives list of set elements found only in first set.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
fs1 = s1.in_first_set_only()
fs2 = s2.in_first_set_only()
self.assertEqual(fs1, self.expected_first)
self.assertEqual(fs2, self.expected_equal_message)
def test_in_second_set_only(self):
"""
Test that method gives list of set elements found only in second set.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
ss1 = s1.in_second_set_only()
ss2 = s2.in_second_set_only()
self.assertEqual(ss1, self.expected_second)
self.assertEqual(ss2, self.expected_equal_message)
if __name__ == "__main__":
unittest.main()
As far as I can tell, TestSetChecker has no differences from the many other unit test classes that I write (apart from the specific functionality it is testing for).
Yet, I am seeing a very unusual __init__ error when I try to execute the file containing the unit tests:
EMS#computer ~/project_dir/test $ python TestSetChecker.py
Traceback (most recent call last):
File "TestSetChecker.py", line 84, in <module>
unittest.main()
File "/opt/python2.7/lib/python2.7/unittest/main.py", line 94, in __init__
self.parseArgs(argv)
File "/opt/python2.7/lib/python2.7/unittest/main.py", line 149, in parseArgs
self.createTests()
File "/opt/python2.7/lib/python2.7/unittest/main.py", line 155, in createTests
self.test = self.testLoader.loadTestsFromModule(self.module)
File "/opt/python2.7/lib/python2.7/unittest/loader.py", line 65, in loadTestsFromModule
tests.append(self.loadTestsFromTestCase(obj))
File "/opt/python2.7/lib/python2.7/unittest/loader.py", line 56, in loadTestsFromTestCase
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
TypeError: __init__() takes at least 3 arguments (2 given)
The directory with the Python unittest source code is read-only in my environment, so I can't add pdb or even print statements there to see what testCaseClass or testCaseNames are at this point where some __init__ fails.
But I can't see any places in my code where I'm failing to provide needed arguments to any __init__ method. I'm wondering if this has something to do with some behind-the-scenes magic with classes that inherit from unittest and with the fact that I'm importing and instantiating a class (SetChecker) within the file that is to be executed for unit tests.
Maybe it checks for all classes in the existing namespace that inherit from TestCase? If so, how do you unit-test the unit tests?
I also tried to first make SetChecker inherit from object and tried to use TestCase like a mix-in, but that created lots of MRO errors and seemed more headache than it was worth.
I've tried searching for this but it's a difficult error to search for (since it does not appear to be a straightforward problem with __init__ arguments).

I was able to work around this by making SetChecker inherit from object only, and then inside of SetChecker providing an internal class that inherits from unittest.TestCase.
The problem is that unittest.main inspects the whole namespace of the module it is run from. Any class it finds in that module that inherits from unittest.TestCase will get the full test-suite treatment (it will try to construct instances of the class for each test_ method it can find, or just for runTest if it finds no test_ methods).
In my case, since the set arguments are required, whatever it is that unittest.main is doing, it's passing some argument (probably the name of the function to treat as the test, in this case the string "runTest") but failing to pass the second required argument. Even if this worked with the signature of my class (e.g. suppose that I replaced the two distinct arguments set1 and set2 with a tuple of 2 sets), it would then immediately fail once it tried to do set operations with that string.
There doesn't appear to be an easy way to tell unittest.main to ignore a certain class or classes. So, by making SetChecker just an object that has a TestCase inside of it, unittest.main no longer finds that TestCase and no longer cares.
There was one other bug: in my test_init function, I use assertRaises which expects a callable, but had never given my SetChecker class a __call__ function.
Here's the modification to the SetChecker class that fixed this for me:
class SetChecker(object):
"""
SetChecker(set1, set2) creates a set checker from the two passed Python set
objects. Printing the SetChecker uses unittest.TestCase.assertEqual to test
if the sets are equal and automatically reveal the elements that are in one
set but not the other if they are unequal. This provides an efficient way
to detect differences in possibly large set objects. Note that this is not
a unittest object, just a wrapper to gain access to the helpful behavior of
unittest.TestCase.assertEqual when used on sets.
"""
EQUAL_MSG = "The two sets are equivalent."
class InternalTest(unittest.TestCase):
def runTest(self): pass
def __init__(self, set1, set2):
assert isinstance(set1, set)
assert isinstance(set2, set)
self.int_test = SetChecker.InternalTest()
try:
self.int_test.assertEqual(set1, set2)
self._str = self.EQUAL_MSG
self._str_lines = [self._str]
self._indxs = None
except AssertionError, e:
self._str = str(e)
self._str_lines = self._str.split('\n')
# Find the locations where a line starts with 'Items '.
# This is the fixed behavior of unittest.TestCase.
self._indxs = [i for i,y in enumerate(self._str_lines)
if y.startswith('Items ')]
#classmethod
def __call__(klass, *args, **kwargs):
"""
Makes the class callable such that calling it like a function is the
same as constructing a new instance.
"""
return klass(*args, **kwargs)
# Everything else below is the same...

Related

How to use MaxLen of typing.Annotation of python 3.9?

I'm aware there's this new typing format Annotated where you can specify some metadata to the entry variables of a function. From the docs, you could specify the maximum length of a incoming list such as:
Annotated can be used with nested and generic aliases:
T = TypeVar('T')
Vec = Annotated[list[tuple[T, T]], MaxLen(10)]
V = Vec[int]
V == Annotated[list[tuple[int, int]], MaxLen(10)]
But I cannot finish to comprehend what MaxLen is. Are you supposed to import a class from somewhere else? I've tried importing typing.MaxLen but doesn't seems to exists (I'm using Python 3.9.6, which I think it should exist here...?).
Example code of what I imagined it should have worked:
from typing import List, Annotated, MaxLen
def function(foo: Annotated[List[int], MaxLen(10)]):
# ...
return True
Where can one find MaxLen?
EDIT:
It seems like MaxLen is some sort of class you have to create. The problem is that I cannot see how you should do it. Are there public examples? How can someone implement this function?
As stated by AntiNeutronicPlasma, Maxlen is just an example so you'll need to create it yourself.
Here's an example for how to create and parse a custom annotation such as MaxLen to get you started.
First, we define the annotation class itself. It's a very simple class, we only need to store the relevant metadata, in this case, the max value:
class MaxLen:
def __init__(self, value):
self.value = value
Now, we can define a function that uses this annotation, such as the following:
def sum_nums(nums: Annotated[List[int], MaxLen(10)]):
return sum(nums)
But it's going to be of little use if nobody checks for it. So, one option could be to implement a decorator that checks your custom annotations at runtime. The functions get_type_hints, get_origin and get_args from the typing module are going to be your best friends. Below is an example of such a decorator, which parses and enforces the MaxLen annotation on list types:
from functools import wraps
from typing import get_type_hints, get_origin, get_args, Annotated
def check_annotations(func):
#wraps(func)
def wrapped(**kwargs):
# perform runtime annotation checking
# first, get type hints from function
type_hints = get_type_hints(func, include_extras=True)
for param, hint in type_hints.items():
# only process annotated types
if get_origin(hint) is not Annotated:
continue
# get base type and additional arguments
hint_type, *hint_args = get_args(hint)
# if a list type is detected, process the args
if hint_type is list or get_origin(hint_type) is list:
for arg in hint_args:
# if MaxLen arg is detected, process it
if isinstance(arg, MaxLen):
max_len = arg.value
actual_len = len(kwargs[param])
if actual_len > max_len:
raise ValueError(f"Parameter '{param}' cannot have a length "
f"larger than {max_len} (got length {actual_len}).")
# execute function once all checks passed
return func(**kwargs)
return wrapped
(Note that this particular example only works with keyword arguments, but you could probably find a way to make it work for normal arguments too).
Now, you can apply this decorator to any function, and your custom annotation will get parsed:
from typing import Annotated, List
#check_annotations
def sum_nums_strict(nums: Annotated[List[int], MaxLen(10)]):
return sum(nums)
Below is an example of the code in action:
>>> sum_nums(nums=list(range(5)))
10
>>> sum_nums(nums=list(range(15)))
105
>>> sum_nums_strict(nums=list(range(5)))
10
>>> sum_nums_strict(nums=list(range(15)))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "annotated_test.py", line 29, in wrapped
raise ValueError(f"Parameter '{param}' cannot have a length "
ValueError: Parameter 'nums' cannot have a length larger than 10 (got length 15).

In Python, how does one override the behavior of a class instance typed on a line by itself

Without print (which I believe invokes str()) what happens when a variable is on a line by itself.
This is a bit contrived, I know, but I ran into this in a Jupyter notebook when testing a class I'm creating and now I'm curious. I can't seem to find the right set of Google search terms to find the answer in the docs.
I defined a class thusly:
class ExceptionList(BaseException):
pass
# I've implemented, in very standard ways, the following methods
# __str__()
# __getitem__()
# __delitem__()
# __repr__()
# I doubt any other specifics of the class are pertinent
EDIT
Here is the repr() implementation:
def __repr__(self):
return "{}({})".format(self.__class__.__name__, repr(self.__exception_list))
P.S. I coded that method based on http://brennerm.github.io/posts/python-str-vs-repr.html
EDIT
My implementation of repr() causes this behavior:
e = ExceptionList(["Oh, no"])
e
"ExceptionList(['Oh, no'])"
So consider:
e1 = Exception("Oh no!")
e2 = ExceptionList("Oh no!")
In separate notebook cells:
e1
Exception('Oh no!')
e2
__main__.ExceptionList()
Incidentally (maybe?) the output of:
e2.__class__
is close:
__main__.ExceptionList
Does it just have something to do with the scope in which the class was defined? Is it some special behavior of builtins?
Is this behavior this result of invoking some method that I'm unaware of? I tried implementing all of the methods produced with dir() though I'm willing to bet that's not exhaustive.
It probably doesn't matter for my implementation but now I need to know!
Boilerplate Hater Deterrent:
I don't know anything.
I'm a terrible programmer.
"I thought a python was a snake..."
I'm barely qualified to use a toaster.
Please forgive this post's pathetic usage of SO disk space.
If the sole content of a line is a variable or object, then that line is evaluated and the variable or object is returned and not stored to a variable.
a = 5 + 2 # 5+2 is evaluated and stored in a
5 + 2 # 5+2 is evaluated
a # a is evaluated
class SomeCustomClass:
def __init__(self, *args):
pass
scc = SomeCustomClass()
scc # returns a reference to this instance, but ref is not stored
Many python interfaces like Jupyter, IPython, IDLE will display the evaluation of the line.
>>> 5+2
7
>>> a=5+2
[Nothing]
>>> a
7
>>> scc
<__main__.SomeCustomClass instance at 0x7fb6d5943d40>
The <__main__.SomeCustomClass instance at 0x7fb6d5943d40> is called a representation of the class. If you look at Python's Data Model you will see that this representation is specified by the object's __repr__ method.
Modifying SomeCustomClass:
class SomeCustomClass:
def __init__(self, *args):
pass
def __repr__(self):
return "SomeCustomClass repr"
>>> scc = SomeCustomClass()
>>> scc
SomeCustomClass repr
>>> s = str(scc) #implicit call to __repr__
>>> s
SomeCustomClass repr
Now adding __str__ method:
class SomeCustomClass:
def __init__(self, *args):
pass
def __repr__(self):
return "SomeCustomClass repr"
def __str__(self):
return "SomeCustomClass str"
>>> scc = SomeCustomClass()
>>> scc
SomeCustomClass repr
>>> s = str(scc) #explicit call to __str__
>>> s
SomeCustomClass str

How can I unittest an Enum's __init__ method?

I'm struggling to unit test the __init__ method on an enumeration I'm using. The difficulty is that I can't extend the enum (so can't implicitly call __init__). When I try to directly call it from my test it insists that the first parameter should be an instance of that enum. I can't do this, because I can't assume any of the enum's properties in advance.
With a contrived example of the problem, let's say I have an enum of even numbers:
class EvenNumbers(Enum):
two = 2
four = 4
I want to do some value checking so that no-one accidentally adds an odd number to this enum:
class EvenNumbers(Enum):
two = 2
four = 4
def __init__(self, value):
assert value % 2 == 0
Now I want to write a test to make sure that this works. Two approaches seem intuitive but don't work:
def test_approach_1():
'''
Try to extend the Enum with a bad value. This fails
because I can't extend an Enum.
'''
class BadEnum(EvenNumbers):
three = 3
def test_approach_2():
'''
Try to call the __init__ method directly. This fails
because I need to supply an instance of the enum as
the first parameter and I can't assume any values are
available.
'''
EvenNumbers.__init__(None, 3)
How should I unit test my __init__ method?
When testing an Enum that is a one-off (in other words, you only have one Enum class that is restricted to even numbers), then just test that Enum's values:
for enum in EvenNumbers:
self.assertTrue(enum.value % 2 == 0)
If you have behavior you want shared among many enumerations, or the behavior being tested has to do with the actual creation of the members, then make a base class with just the behavior (no members):
class EvenNumbers(Enum):
def __init__(self, value):
if value % 2 ! = 0:
raise ValueError('%d is not an even number' % value)
def double(self):
return self.value * 2
and then you can create test enumerations:
class TestEvenEnum(unittest.TestCase):
def test_bad_value(self):
with self.assertRaises(ValueError):
class NotEven(EvenNumbers):
one = 1
def test_good_value(self):
class YesEven(EvenNumbers):
two = 2
self.assertEqual(YesEven.two.double(), 4)
The solution I'm currently working with is to put the value checking logic into an empty enum:
class EvenNumbersBase(Enum):
def __init__(self, val): assert val % 2 == 0
class EvenNumbers(EvenNumbersBase):
two = 2
four = 4
This allows me to test that EvenNumbers inherits from EvenNumbersBase. I can subsequently try to extend EvenNumbersBase with different values to test the init method.
This feels like jumping through a few hoops. Can you suggest a better way?

How to make one member of class to be both field and method?

I have one class A which extends B, and B has one method count(). Now I want to allow user call both A.count and A.count(). A.count means count is one field of A while A.count() means it is method derived from B.
This is impossible in Python, and here's why:
You can always assign a method (or really any function) to a variable and call it later.
hello = some_function
hello()
is semantically identical to
some_function()
So what would happen if you had an object of your class A called x:
x = A()
foo = x.count
foo()
The only way you could do this is by storing a special object in x.count that is callable and also turns into e.g. an integer when used in that way, but that is horrible and doesn't actually work according to specification.
As i said, it's not exactly impossible, as told by other answers. Lets see a didactic example:
class A(object):
class COUNT(object):
__val = 12345
def __call__(self, *args, **kwargs):
return self.__val
def __getattr__(self, item):
return self.__val
def __str__(self):
return str(self.__val)
count = COUNT()
if __name__ == '__main__':
your_inst = A()
print(your_inst.count)
# outputs: 12345
print(your_inst.count())
# outputs: 12345
As you may notice, you need to implement a series of things to accomplish that kind of behaviour. First, your class will need to implement the attribute count not as the value type that you intent, but as an instance of another class, which will have to implement, among other things (to make that class behave, by duck typing, as the type you intent) the __call__ method, that should return the same as you A class __getattr__, that way, the public attribute count will answer as a callable (your_inst.count()) or, as you call, a field (your_inst.count), the same way.
By the way, i don't know if the following is clear to you or not, but it may help you understand why it isn't as trivial as one may think it is to make count and count() behave the same way:
class A(object):
def count(self):
return 123
if __name__ == '__main__':
a = A()
print(type(a.count))
# outputs: <class 'method'>
print(type(a.count()))
# outputs: <class 'int'>
. invokes the a class __getattr__ to get the item count. a.count will return the referente to that function (python's function are first class objects), the second one, will do the same, but the parentheses will invoke the __call__ method from a.count.

extending built-in python dict class

I want to create a class that would extend dict's functionalities. This is my code so far:
class Masks(dict):
def __init__(self, positive=[], negative=[]):
self['positive'] = positive
self['negative'] = negative
I want to have two predefined arguments in the constructor: a list of positive and negative masks. When I execute the following code, I can run
m = Masks()
and a new masks-dictionary object is created - that's fine. But I'd like to be able to create this masks objects just like I can with dicts:
d = dict(one=1, two=2)
But this fails with Masks:
>>> n = Masks(one=1, two=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'two'
I should call the parent constructor init somewhere in Masks.init probably. I tried it with **kwargs and passing them into the parent constructor, but still - something went wrong. Could someone point me on what should I add here?
You must call the superclass __init__ method. And if you want to be able to use the Masks(one=1, ..) syntax then you have to use **kwargs:
In [1]: class Masks(dict):
...: def __init__(self, positive=(), negative=(), **kwargs):
...: super(Masks, self).__init__(**kwargs)
...: self['positive'] = list(positive)
...: self['negative'] = list(negative)
...:
In [2]: m = Masks(one=1, two=2)
In [3]: m['one']
Out[3]: 1
A general note: do not subclass built-ins!!!
It seems an easy way to extend them but it has a lot of pitfalls that will bite you at some point.
A safer way to extend a built-in is to use delegation, which gives better control on the subclass behaviour and can avoid many pitfalls of inheriting the built-ins. (Note that implementing __getattr__ it's possible to avoid reimplementing explicitly many methods)
Inheritance should be used as a last resort when you want to pass the object into some code that does explicit isinstance checks.
Since all you want is a regular dict with predefined entries, you can use a factory function.
def mask(*args, **kw):
"""Create mask dict using the same signature as dict(),
defaulting 'positive' and 'negative' to empty lists.
"""
d = dict(*args, **kw)
d.setdefault('positive', [])
d.setdefault('negative', [])

Categories

Resources