鼎鼎知识库
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

2020.10.27单元测试前奏--解耦.md 9.0KB

pirms 4 gadiem
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. > 通常一个`低内聚高耦合`的类具有单一职责,但不可避免地会存在依赖。比如依赖于文件系统的某个管理类,依赖于时间、依赖于某个线程、依赖于对外的请求,等等。在单元测试的时候,如何解除依赖呢?
  2. 如下`FileHelper`存在强依赖:
  3. ```
  4. public class FileHelper
  5. {
  6. public bool IsValidLogFileName(string fileName)
  7. {
  8. //这里存在强依赖
  9. FileExtensionManager mgr = new FileExtensionManager();
  10. return mgr.IsValid(fileName);
  11. }
  12. }
  13. public class FileExtensionManager
  14. {
  15. public bool IsValid(string fileName)
  16. {
  17. return true;
  18. }
  19. }
  20. ```
  21. 如果依赖于接口呢?在`FileExtensionManager`基础之上抽象出一个接口。
  22. ```
  23. public interface IExtensionManager
  24. {
  25. bool IsValid(string fileName);
  26. }
  27. //实现接口之一
  28. public class FileExtensionManager : IExtensionManager
  29. {
  30. public bool IsValid(string fileName)
  31. {
  32. return true;
  33. }
  34. }
  35. //实现接口之二,测试时使用
  36. public class StubExtensionManager : IExtensionManager
  37. {
  38. public bool IsValid(string fileName)
  39. {
  40. return true;
  41. }
  42. }
  43. //改造FileHelper
  44. public class FileHelper
  45. {
  46. //这里虽然依赖于接口,但实现是具体的
  47. IExtensionManager mgr = new FileExtensionManager();
  48. return mgr.IsValid(fileName);
  49. }
  50. ```
  51. > 现在需求变成:当在生产环境下希望`FileHelper`依赖于`FileExtensionManager`,在单元测试时,`FileHelper`依赖于`StubExtesnionManager`,并且希望`StubExtensionManger`提供一个属性让外界可以控制执行结果。
  52. ```
  53. //首先接口
  54. public interface IExtensionManager
  55. {
  56. bool IsValid(string fileName);
  57. }
  58. //生产环境下的实现类
  59. public class FileExtensionManager : IExtensionManager
  60. {
  61. public bool IsValid(string fileName)
  62. {
  63. return true;
  64. }
  65. }
  66. //单元测试下的实现类
  67. public class StubExtensionManager : IExtensionManager
  68. {
  69. //通过这个属性,让外界可以控制执行结果
  70. public bool ShouldExtensionBeValid{get;set;}
  71. public bool IsValid(string fileName)
  72. {
  73. return ShouldExtensionBeValid;
  74. }
  75. }
  76. //在FileHelper中提供两个构造函数的重载
  77. public class FileHelper
  78. {
  79. private IExtenionManager manager;
  80. //生产环境使用的构造函数
  81. public FileHelper()
  82. {
  83. manager = new FileExtentionManager();
  84. }
  85. //单元测试使用的构造函数
  86. public FileHelper(IExtensionManager mgr)
  87. {
  88. manager = mgr;
  89. }
  90. //需要被测试的方法
  91. public bool IsValidLogFileName(string fileName)
  92. {
  93. return manager.IsValid(fileName);
  94. }
  95. }
  96. //开始单元测试
  97. [TestFixture]
  98. public class FileHelperTests
  99. {
  100. public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
  101. {
  102. //1、Arrange
  103. // 单元测试用的实现类
  104. StubExtensionManager myFakeManager = new StubExtensionManager();
  105. // 控制设置单元测试的结果
  106. myFakeManager.ShouldExtensionBeValid = true;
  107. FileHelper fileHelper = new FileHelper(myFakeManager);
  108. //2、Act
  109. bool result = fileHelper.IsValidLogFileName("example.ext");
  110. //3、Assert
  111. Assert.IsFalse(result, "...");
  112. }
  113. }
  114. ```
  115. > 以上通过重载构造函数,把单元测试的实现类注入。是否还有其它注入方式呢?通过属性。
  116. ```
  117. //首先接口
  118. public interface IExtensionManager
  119. {
  120. bool IsValid(string fileName);
  121. }
  122. //生产环境下的实现类
  123. public class FileExtensionManager : IExtensionManager
  124. {
  125. public bool IsValid(string fileName)
  126. {
  127. return true;
  128. }
  129. }
  130. //单元测试下的实现类
  131. public class StubExtensionManager : IExtensionManager
  132. {
  133. //通过这个属性,让外界可以控制执行结果
  134. public bool ShouldExtensionBeValid{get;set;}
  135. public bool IsValid(string fileName)
  136. {
  137. return ShouldExtensionBeValid;
  138. }
  139. }
  140. public class FileHelper
  141. {
  142. private IExtensionManager manager;
  143. public FileHelper
  144. {
  145. manager = new FileExtensionManager();
  146. }
  147. //为外界提供注入的可能
  148. public IExtensionManager ExtensionManager
  149. {
  150. get {return manager;}
  151. set {manager = value;}
  152. }
  153. //需要被测试的方法
  154. public bool IsValidLogFileName(string fileName)
  155. {
  156. return manager.IsValid(fileName);
  157. }
  158. }
  159. //开始单元测试
  160. [TestFixture]
  161. public class FileHelperTests
  162. {
  163. public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
  164. {
  165. //1、Arrange
  166. // 单元测试用的实现类
  167. StubExtensionManager myFakeManager = new StubExtensionManager();
  168. // 控制设置单元测试的结果
  169. myFakeManager.ShouldExtensionBeValid = true;
  170. FileHelper fileHelper = new FileHelper();
  171. fileHelper.ExtensionManager = myFakeManager;
  172. //2、Act
  173. bool result = fileHelper.IsValidLogFileName("example.ext");
  174. //3、Assert
  175. Assert.IsFalse(result, "...");
  176. }
  177. }
  178. ```
  179. > 以上通过构造函数或属性把单元测试的实现类注入到`FileHelper`中。如果用工厂方法呢?用工厂产生实例,并且工厂可以让外界决定具体生成哪个实例。
  180. ```
  181. //首先接口
  182. public interface IExtensionManager
  183. {
  184. bool IsValid(string fileName);
  185. }
  186. //生产环境下的实现类
  187. public class FileExtensionManager : IExtensionManager
  188. {
  189. public bool IsValid(string fileName)
  190. {
  191. return true;
  192. }
  193. }
  194. //单元测试下的实现类
  195. public class StubExtensionManager : IExtensionManager
  196. {
  197. //通过这个属性,让外界可以控制执行结果
  198. public bool ShouldExtensionBeValid{get;set;}
  199. public bool IsValid(string fileName)
  200. {
  201. return ShouldExtensionBeValid;
  202. }
  203. }
  204. //需要一个工厂类
  205. public class ExtensionManagerFactory
  206. {
  207. private IExtensionManager customeManager = null;
  208. public IExtensionManager Create()
  209. {
  210. if(customManager != null)
  211. {
  212. return customManager;
  213. }
  214. return new FileExtensionManager();
  215. }
  216. public void SetManager(IExtensionManager mgr)
  217. {
  218. customManager = mgr;
  219. }
  220. }
  221. public class FileHelper
  222. {
  223. private IExtensionManager manager;
  224. public FileHelper
  225. {
  226. manager = ExtensionManagerFactory.Create();
  227. }
  228. //需要被测试的方法
  229. public bool IsValidLogFileName(string fileName)
  230. {
  231. return manager.IsValid(fileName);
  232. }
  233. }
  234. //开始单元测试
  235. [TestFixture]
  236. public class FileHelperTests
  237. {
  238. public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
  239. {
  240. //1、Arrange
  241. // 单元测试用的实现类
  242. StubExtensionManager myFakeManager = new StubExtensionManager();
  243. // 控制设置单元测试的结果
  244. myFakeManager.ShouldExtensionBeValid = true;
  245. ExtensionManagerFactory.SetManager(myFakeManager);
  246. FileHelper fileHelper = new FileHelper();
  247. //2、Act
  248. bool result = fileHelper.IsValidLogFileName("example.ext");
  249. //3、Assert
  250. Assert.IsFalse(result, "...");
  251. }
  252. }
  253. ```
  254. > 以上通过构造函数、属性、工厂方法都可以把单元测试的实现类注入到`FileHelper`中。
  255. > 有时,被测试基类中会存在虚方法,这种情况可以测试基类的派生类。
  256. 有这样一个基类
  257. ```
  258. public class FileHelper
  259. {
  260. public bool IsValidLogFileName(string fileName)
  261. {
  262. return GetManager().IsValid(fileName);
  263. }
  264. //基类方法提供虚方法,等待派生类实现
  265. protected virtual IExtensionManager GetManager()
  266. {
  267. return new FileExtensionManager();
  268. }
  269. }
  270. //首先接口
  271. public interface IExtensionManager
  272. {
  273. bool IsValid(string fileName);
  274. }
  275. //生产环境下的实现类
  276. public class FileExtensionManager : IExtensionManager
  277. {
  278. public bool IsValid(string fileName)
  279. {
  280. return true;
  281. }
  282. }
  283. //单元测试下的实现类
  284. public class StubExtensionManager : IExtensionManager
  285. {
  286. //通过这个属性,让外界可以控制执行结果
  287. public bool ShouldExtensionBeValid{get;set;}
  288. public bool IsValid(string fileName)
  289. {
  290. return ShouldExtensionBeValid;
  291. }
  292. }
  293. ```
  294. 派生类
  295. ```
  296. public class TestableFileHelper : FileHelper
  297. {
  298. public IExtenionManager Manager{get;set;}
  299. protected override IExtensionManager GetManager()
  300. {
  301. return Mananger;
  302. }
  303. }
  304. ```
  305. 单元测试
  306. ```
  307. //开始单元测试
  308. [TestFixture]
  309. public class TestableFileHelperTests
  310. {
  311. public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
  312. {
  313. //1、Arrange
  314. // 单元测试用的实现类
  315. StubExtensionManager myFakeManager = new StubExtensionManager();
  316. // 控制设置单元测试的结果
  317. myFakeManager.ShouldExtensionBeValid = true;
  318. ExtensionManagerFactory.SetManager(myFakeManager);
  319. TestableFileHelper fileHelper = new TestableFileHelper();
  320. fileHelper.Manager = myFakeManager;
  321. //2、Act
  322. bool result = fileHelper.IsValidLogFileName("example.ext");
  323. //3、Assert
  324. Assert.IsFalse(result, "...");
  325. }
  326. }
  327. ```