1 #!/usr/bin/env python 2 # encoding: utf-8 3 4 import unittest 5 6 """ 7 the simplyest way to test return value 8 No needs to use stub 9 """ 10 class LogAnalyzer_0(object): 11 def IsValidLogFileName(self, fileName): 12 return str(fileName).endswith('.sln') 13 """ 14 However somethimes we have to rely on the extenal class or method 15 that we cannot control on it or it has not been finished yet 16 This is when we need stub to help us. eg, draw_from_weighted_range() and randrange(), interacting with filesystem. 17 Fakes here include stub (assert on CUT) and mock (assert on Fake) we talk about stub and mock in later posts. 18 Say our IsValidLogFileName() method needs read through the config file and return true if extension is supported in config file, 19 20 There two big types to inject fakes into MUT(Method Under Test): 21 1.Test are performed on the MUT itself (eg. assert(mut.dosomething(),true) 22 1.1 Abstracting concrete objects into interfaces or delegates 23 How: Extract an interface to allow replacing or extending underlying impl 24 1.2 Refactoring to allow injection of faked implementations of those delegates or interface. 25 How: 26 1.2.1.Inject stub in code under test using factory design (layer of indirection 2 faking a member in factory class) 27 the difference is that the object initiating the stub request is the code under test. 28 the fake instances was set by code external to the code under test before the test started in the below. 29 A test configures the factory class to re turn a stub object. The class usess the factory class to get the 30 stub instance, which in production code would return an object that is not a stub 31 Preferred to using this layer 32 1.2.2 Injection of a stub in test code (layer of indiretion 1 faking a member in class under test) 33 1.2.2.1 Inject stub via ctor (cumbersome whenyou have many dependencies) 34 1.2.2.2 Inject stub via setter/getter 35 This is much simpler than ctor injection as each test can set only the dependencies 36 that it needs to get the test underway; 37 Use this when you want to signify that the dependency is optional or the dependency has 38 a default instance created that does not create any problems; 39 40 1.2.4.Inject stub impl via parameter 41 2.Test are performed on the class that inhetites from MUT (eg. assert(mut_child.dosomething(),true) 42 It is also known as Extract and override, which is is good to for sumulating inputs into your code under test 43 (in other words, return values from dependency). but it is cumbersome when you want t verify and check interactions 44 that are coming out of the code under test int othe dependency (in other words, it is good to play stub but very bad to play mock) 45 46 2.1 use local virtual factory method to get instance of stub 47 The time not to use this is there is an interface ready to fake or there is already a place that seam can be injected. 48 2.2 use extract and override to return a logical result instead of calling an actual denpendency 49 This uses a simple faked result instead of a stub 50 Much easier than 2.1 preferred to use 51 """ 52 53 # Refered to "1.1 Abstracting concrete objects into interfaces or delegates" 54 class ExtensionMgr_AbstractedInterface(object): 55 def IsValid(self, filename): # should be overwriten by child 56 pass 57 58 class FileExtensionMgr_ConcreteImpl(ExtensionMgr_AbstractedInterface): 59 def IsValid(self, filename): 60 return str(filename).endswith('.sln') 61 62 # Stubs 63 class ExtendMgrStub(ExtensionMgr_AbstractedInterface): 64 def __init__(self): 65 self.mWillBeValid = False 66 return ExtensionMgr_AbstractedInterface.__init__(self) 67 68 def IsValid(self, filename): 69 return self.mWillBeValid 70 71 class ExtendMgrStub_WithoutIngeritingFrom_ExtensionMgr_AbstractedInterface(object): 72 def __init__(self): 73 self.mWillBeValid = False 74 75 def IsValid(self, filename): 76 return self.mWillBeValid 77 78 79 # Refered to 1.2.2.1 Inject stub impl via ctor (cumbersome whenyou have many dependencies) 80 class LogAnalyzer_StubInjectedViaCtor(object): 81 def __init__(self, iExtensionMgr): 82 self.mIExtensionMgr = iExtensionMgr 83 84 def IsValidLogFileName(self, fileName): 85 self.mIExtensionMgr.IsValid(fileName) 86 87 # Refered to "1.2.2.2 Inject stub impl via a setter and ggeter" 88 class LogAnalyzer_StubInjectedViaPropertySetter(object): 89 def __init__(self): 90 self.mIExtensionMgr = FileExtensionMgr_ConcreteImpl() 91 92 def IsValidLogFileName(self, fileName): 93 self.mIExtensionMgr.IsValid(fileName) 94 95 def SetIExtensionMgr(self, ext): 96 self.mIExtensionMgr = ext 97 98 def GetIExtensionMgr(self): 99 return self.mIExtensionMgr 100 101 # Refered to "1.2.1.Inject stub in code under test using factory design" 102 class ExtensionMgrFactory(object): 103 iExtMgr = None 104 105 @staticmethod 106 def Create(): 107 # define factory that can use and return custom manager instance 108 if ExtensionMgrFactory.iExtMgr is None: 109 ExtensionMgrFactory.iExtMgr = FileExtensionMgr_ConcreteImpl() 110 else: 111 return ExtensionMgrFactory.iExtMgr 112 113 @staticmethod 114 def SetExtMgr(extmgr): 115 ExtensionMgrFactory.iExtMgr = extmgr 116 117 class LogAnalyzer_StubInjectedViaFactory(object): 118 def __init__(self): 119 self.mIExtensionMgr = ExtensionMgrFactory.Create() 120 121 def IsValidLogFileName(self, fileName): 122 self.mIExtensionMgr.IsValid(fileName) 123 124 125 #Referred to "2.1 use local virtual factory method to get instance of stub" 126 class LogAnalyzer_StubInjectedViaLocalFactoryMethod(object): 127 def IsValidLogFileName(self, fileName): 128 self.GetMgr().IsValid(fileName) 129 def GetMgr(self): 130 return FileExtensionMgr_ConcreteImpl() 131 132 class TestableLogAnalyzer_ReturnStub(LogAnalyzer_StubInjectedViaLocalFactoryMethod): 133 def __init__(self, iExtensionMgr): 134 self.mIExtensionMgr = iExtensionMgr 135 def GetMgr(self): 136 return self.mIExtensionMgr 137 138 139 #Referred to "2.2 use extract and override to return a logical result instead of calling an actual denpendency" 140 class LogAnalyzer_OverrideMethodReturnsResult(object): 141 def __init__(self): 142 self.mIExtensionMgr = FileExtensionMgr_ConcreteImpl() 143 def IsValidLogFileName(self, fileName): 144 self.IsValidExtension(fileName) 145 def IsValidExtension(self,filename): 146 return self.mIExtensionMgr.IsValid(filename) 147 148 class TestableLogAnalyzer_OverrideMethodReturnsResult(LogAnalyzer_OverrideMethodReturnsResult): 149 def __init__(self, is_valid_entension): 150 self.is_valid_entension = is_valid_entension 151 def IsValidExtension(self,filename): 152 return self.is_valid_entension 153 154 155 156 # cut means by class under test mut means by method under test 157 class LogAnalyzerTestCase(unittest.TestCase): 158 159 # No stub used just simply perform the test 160 def test_IsValidLogFileName_BadExtension_ReturnFalse_NoStub(self): 161 logAnalyzer0 = LogAnalyzer_0() 162 ret = logAnalyzer0.IsValidLogFileName('fn1.sl') 163 self.assertFalse(ret) 164 165 # StubIjectedViaCtor 166 def test_IsValidLogFileName_BadExtension_ReturnFalse_StubIjectedViaCtor(self): 167 ext = ExtendMgrStub() 168 ext.mWillBeValid = False 169 logAnalyzer = LogAnalyzer_StubInjectedViaCtor(ext) 170 ret = logAnalyzer.IsValidLogFileName('fn1.sl') 171 self.assertFalse(ret) 172 173 # StubIjectedViaCtor 174 # This is what I wrote because python is weak-type language 175 # so it can still work without using inheratance 176 def test_IsValidLogFileName_BadExtension_ReturnFalse_StubIjectedViaCtor_WithoutInhertingFrom_ExtensionMgr_AbstractedInterface(self): 177 ext = ExtendMgrStub_WithoutIngeritingFrom_ExtensionMgr_AbstractedInterface() 178 ext.mWillBeValid = False 179 180 logAnalyzer = LogAnalyzer_StubInjectedViaCtor(ext) 181 ret = logAnalyzer.IsValidLogFileName('fn1.sl') 182 183 self.assertFalse(ret) 184 185 # StubInjectedViaPropertySetter 186 def test_IsValidLogFileName_BadExtension_ReturnFalse_StubInjectedViaPropertySetter(self): 187 ext = ExtendMgrStub() 188 ext.mWillBeValid = False 189 190 logAnalyzer = LogAnalyzer_StubInjectedViaPropertySetter() 191 logAnalyzer.SetIExtensionMgr(ext) 192 ret = logAnalyzer.IsValidLogFileName('fn1.sl') 193 194 self.assertFalse(ret) 195 196 # StubIjectedViaFactory 197 def test_IsValidLogFileName_BadExtension_ReturnFalse_4_StubIjectedViaFactory(self): 198 ext = ExtendMgrStub() 199 ext.mWillBeValid = False 200 ExtensionMgrFactory.SetExtMgr(ext) 201 202 logAnalyzer = LogAnalyzer_StubInjectedViaFactory() 203 ret = logAnalyzer.IsValidLogFileName('fn1.sl') 204 205 self.assertFalse(ret) 206 207 # OverrideMethodReturnsResult 208 def test_IsValidLogFileName_BadExtension_ReturnFalse_4_OverrideMethodReturnsResult(self): 209 is_valid_extension = False 210 211 testableLogAnalyzer = TestableLogAnalyzer_OverrideMethodReturnsResult(is_valid_extension) 212 ret = testableLogAnalyzer.IsValidLogFileName('fnl.sl') 213 214 self.assertFalse(ret) 215 216 if __name__ == '__main__': 217 unittest.main()