Created
October 19, 2017 04:45
-
-
Save andres-fr/763fa26a544f55982c3915271bc79ca4 to your computer and use it in GitHub Desktop.
This file performs some basic interfacing in Python 2, tested via unittest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""This file performs some basic interfacing in Python 2: | |
1. Defining the methods that have to be overriden with the ABC module | |
2. Defining an input and output signature for those methods | |
3. Testing the validity of different implementations via unittest | |
Note the following: | |
!! In order to check the input/output signatures, the overriden methods have to be run once | |
at construction, which may be undesirable (usually this kind of interfacing happens within | |
a protocol that can take care of that). | |
""" | |
from __future__ import print_function, division | |
from abc import ABCMeta, abstractmethod | |
import unittest | |
#################################################################################################### | |
### DEFINE THE INTERFACE | |
#################################################################################################### | |
class MaybeBadInputSignatureError(Exception): | |
"""Expected to be raised if the call to a function raises a TypeError, most probably due | |
to a bad input signature. Due to its 'maybe-ness', the original error message can be | |
optionally displayed as well. | |
""" | |
def __init__(self, method_name, input_signature, original_errmsg=""): | |
orig_errmsg = "\n(Original error message: %s)"%original_errmsg if original_errmsg else "" | |
Exception.__init__(self, ("In %s method: did you respect the parameter signature %s?"+ | |
orig_errmsg) % (method_name, str(input_signature))) | |
class BadOutputSignatureError(Exception): | |
"""Expected to be raised if the output signature of a method doesn't fit the expected one. | |
""" | |
def __init__(self, method_name, expected_signature, output_signature): | |
Exception.__init__(self,"Expected return value of %s method was %s, but got %s." % | |
(method_name, str(expected_signature), str(output_signature))) | |
class MyInterface: | |
"""This class acts like an interface: it cannot be instantiated because it has an abstract | |
method that has to be implemented by any class that inherits from it. The check methods raise | |
the corresponding exception if the input or output signatures of the implemented method don't | |
match the ones specified in the signatures. | |
IMPORTANT: keep in mind that, in order to check the input and output signatures, the | |
overriden method is run at instantiation, which may be undesirable. | |
""" | |
__metaclass__ = ABCMeta | |
def __init__(self): | |
self.input_signature = [int, int, str] | |
self.output_signature = [int, str] | |
self.__check_method_output(self.__check_method_input()) | |
def __check_method_input(self): | |
try: | |
return self.override_this(*[x() for x in self.input_signature]) | |
except TypeError as err: | |
raise MaybeBadInputSignatureError(self.__class__.__name__+".override_this()", | |
self.input_signature, str(err)) | |
def __check_method_output(self, out): | |
implemented_out_sig = [type(x) for x in out] | |
if implemented_out_sig != self.output_signature: | |
raise BadOutputSignatureError(self.__class__.__name__+".override_this()", | |
self.output_signature, | |
implemented_out_sig) | |
@abstractmethod | |
def override_this(self, *args, **kwargs): | |
raise NotImplementedError | |
#################################################################################################### | |
### DEFINE SOME TEST IMPLEMENTATIONS FOR THE INTERFACE | |
#################################################################################################### | |
class GoodImplementation(MyInterface): | |
"""This is a correct implementation of the interface: | |
1. The Class extends the interface explicitly and implements the required methods | |
2. The input and output signature of the methods comply with the interface | |
""" | |
def override_this(self, int1, int2, str1): | |
return 123, "successfully overriden!" | |
class NoInterface(object): | |
"""This class complies with the interface (methods implemented and their signature) but | |
it doesn't extend it explicitly, which may be undesirable. This can be checked with | |
'isinstance(NoInterface, MyInterface)' as it can be seen in the unit tests. | |
""" | |
def override_this(self, int1, int2, str1): | |
return 123, "this wasn't overriden!" | |
class NoImplementation(MyInterface): | |
"""This class doesn't provide an implementation for all the abstract methods in the interface. | |
Trying to instantiate this raises a 'TypeError: Can't instantiate abstract class with | |
abstract methods'. | |
""" | |
pass | |
class NoActualImplementation(MyInterface): | |
"""This function does implement and override the interface, but no actual functionality is | |
added to the method, so trying to instantiate this still raises the 'NotImplementedError'. | |
""" | |
def override_this(self, int1, int2, str1): | |
super(NoActualImplementation, self).override_this() | |
class BadInputImplementation(MyInterface): | |
"""As defined in the interface, trying to instantiate this class with an incompatible parameter | |
list for the overriden method will raise a MaybeBadInputSignatureError exception. | |
""" | |
def override_this(self, int1, str1): | |
return 123, "a parameter is missing!" | |
class BadOutputImplementation(MyInterface): | |
"""If the signature of the overriden method's return value doesn't comply with the interface, | |
a BadOutputSignatureError will be raised. | |
""" | |
def override_this(self, int1, int2, str1): | |
return "return elements are switched!", 123 | |
#################################################################################################### | |
### UNIT TESTING | |
#################################################################################################### | |
class UnitTestCase(unittest.TestCase): | |
def test_good_implementation(self): | |
"""This test should pass with no problems, as it complies with the interface specifications. | |
""" | |
x = GoodImplementation() | |
self.assertEqual((123, "successfully overriden!"), x.override_this(1,2,"3")) | |
def test_no_interface(self): | |
"""This object compiles, and would work since it implements the needed functionality, but it | |
doesn't extend the interface explicitly. This can be checked with 'isinstance' | |
""" | |
x = NoInterface() | |
self.assertEqual(False, isinstance(x, MyInterface)) | |
def test_no_implementation(self): | |
"""Raises the following error, due to unimplemented abstract methods: | |
TypeError: Can't instantiate abstract class NoImplementation with abstract methods | |
""" | |
self.assertRaises(TypeError, NoImplementation) | |
def test_no_actual_implementation(self): | |
"""Raises a NotImplementedError error, since the method hasn't been actually overriden. | |
""" | |
self.assertRaises(NotImplementedError, NoActualImplementation) | |
def test_bad_input(self): | |
"""This raises the following exception, due to non-compliant input signature: | |
MaybeBadInputSignatureError: In BadInputImplementation.override_this() method: did you | |
respect the parameter signature [<type 'int'>, <type 'int'>, <type 'str'>]? | |
(Original error message: override_this() takes exactly 3 arguments (4 given)) | |
""" | |
self.assertRaises(MaybeBadInputSignatureError, BadInputImplementation) | |
def test_bad_output(self): | |
"""This raises the following exception, due to non-compliant output signature: | |
BadOutputSignatureError: Return value of BadOutputImplementation.override_this() | |
method was [<type 'str'>, <type 'int'>], but [<type 'int'>, <type 'str'>] was expected. | |
""" | |
self.assertRaises(BadOutputSignatureError, BadOutputImplementation) | |
# run the tests! | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment