ソースを参照

单元测试解耦

master
qdjjx 4年前
コミット
5e44fb5f7f

+ 397
- 0
团队/2020.10.27单元测试前奏--解耦.md ファイルの表示

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

团队/个人篇.pptx → 团队/新入职/个人篇.pptx ファイルの表示


团队/公司篇.pptx → 团队/新入职/公司篇.pptx ファイルの表示


团队/共赢篇.pptx → 团队/新入职/共赢篇.pptx ファイルの表示


团队/应对篇.pptx → 团队/新入职/应对篇.pptx ファイルの表示


团队/挑战篇.pptx → 团队/新入职/挑战篇.pptx ファイルの表示


团队/文化.md → 团队/新入职/文化.md ファイルの表示


読み込み中…
キャンセル
保存