From: Changjune K. <jun...@ha...> - 2001-11-21 05:16:21
|
Have you ever wished that Python had interface enforcement like Java? You can have it with unittest module, even better than Java's. There are two ways, which can be combined for purpose. I have been using these ways and have been had lots of benefits from them. I'd like to share them with you and want to hear your comments. 1) Syntactic Checking Put the following method in the TestCase class or inherit and make a new class with it: def assertImplements(self, implementation, interface, msg=None): from pydoc import allmethods from inspect import getargspec, formatargspec infMethods=allmethods(interface) for methodName,method in infMethods.items(): print method.im_class,methodName impMethod=getattr(implementation,methodName,None) if impMethod is None: raise self.failureException, (msg or '%s.%s is not implemented in %s.' %(method.im_class.__name__,methodName,implementation.__name__)) impFunc=impMethod.im_func impFuncSig=getargspec(impFunc) infFuncSig=getargspec(getattr(interface,methodName).im_func) if impFuncSig != infFuncSig: raise self.failureException, (msg or '%s: %s != %s' % (methodName,formatargspec(*impFuncSig),formatargspec(*infFuncSig))) When you have an interface like, class BagInterface: def isEmpty(self): raise NotImplementedError class StackInterface(BagInterface): def push(self, anObject): raise NotImplementedError def pop(self): raise NotImplementedError And you have this implementation: class ListStack: ''' Implements StackInterface ''' def __init__(self): self._s=[] def push(self, anObject): self._s.append(anObject) def pop(self): return self._s.pop() def isEmpty(self): return len(self._s)==0 Now you can check this in your test file as, self.assertImplements(ListStack,StackInterface) It does check the existence of all methods of the interface in the implementation, and the signature -- arguments specs. Even the variable name. You might think it too constrained or weird but I found this more helpful than not, because Python isn't statically typed. You can't be sure if it implements with right arguments or not, but if you can check the variable names, you can be quite sure you haven't confused the arguement order and etc. This works well when you use good naming convention for arguments. There are surely some more sophistication that can be added on this. 2) Semantic Checking You know interfaces sometimes can deceive you -- it could be fictitious without semantic guarantee. With unittests, however, the interfaces can guarantee semantics as well. The following code will tell, (If the client implements StackInterface with ListStack, she'll make a unittest module for that and name it "test_StackList". In the test, she just imports all the tests from StackInterface so that interface compliances are guaranteed. She might add some ListStack specific testcases in the same module test_StackList.) #------------------------------------------------ #StackInterface.py import unittest class StackInterface: #or put this whole class as a doc string '''Anything that implements this should pass the unittests in StackInterface with its own MyImpl ''' def push(self, anObject): raise NotImplementedError def pop(self): raise NotImplementedError def isEmpty(self): raise NotImplementedError class _Object: #helper object representing _anything_ pass class TestStackInterface(unittest.TestCase): def setUp(self): self.stack=MyImpl() def testPushAndPop(self): o=_Object() self.stack.push(o) self.assertEqual(o, self.stack.pop()) def testPopOnEmpty(self): self.assert_(self.stack.isEmpty()) self.assertRaises(IndexError,self.stack.pop) def testPushAllAndPopAll(self): oList=[_Object() for i in range(10)] for each in oList: self.stack.push(each) oList.reverse() for each in oList: self.assertEqual(each, self.stack.pop()) MyImpl=StackInterface #please override this with the real implementation if __name__=='__main__': # you won't run this module directly though unittest.main() #------------------------------------------------ #ListStack.py from StackInterface import StackInterface class ListStack(StackInterface): def __init__(self): self._s=[] def push(self, anObject): self._s.append(anObject) def pop(self): return self._s.pop() def isEmpty(self): return len(self._s)==0 #------------------------------------------------ #test_ListStack.py import unittest import StackInterface from ListStack import ListStack StackInterface.MyImpl=ListStack from StackInterface import * #if you want to implement several #interfaces just import all of them here if __name__=='__main__': unittest.main() |