Util应用程序框架公共操作类(二):数据类型转换公共操作类(源码篇)
上一篇介绍了数据类型转换的一些情况,可以看出,如果不进行封装,有可能导致比较混乱的代码。本文通过TDD方式把数据类型转换公共操作类开发出来,并提供源码下载。
我们在 应用程序框架实战十一:创建VS解决方案与程序集 一文已经创建了解决方案,包含一个类库项目和一个单元测试项目。单元测试将使用.Net自带的 MsTest,另外通过Resharper工具来观察测试结果。
首先考虑我们期望的API长成什么样子。基于TDD开发,其中一个作用是帮助程序员设计期望的API,这称为意图导向编程。
因为数据类型转换是Convert,所以我们先在单元测试项目中创建一个ConvertTest的类文件。
类创建好以后,我先随便创建一个方法Test,以迅速展开工作。测试的方法名Test,我是随便起的,因为现在还不清楚API是什么样,我一会再回过头来改。
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Util.Tests {
/// <summary>
/// 类型转换公共操作类测试
/// </summary>
[TestClass]
public class ConvertTest {
[TestMethod]
public void Test() {
}
}
}
为了照顾还没有使用单元测试的朋友,我在这里简单介绍一下MsTest。MsTest是.Net仿照JUnit打造的一个单元测试框架。在单元测试类上需要添加一个TestClass特性,在测试方法上添加TestMethod特性,用来识别哪些类的操作需要测试。还有一些其它特性,在用到的时候我再介绍。
现在先来实现一个最简单的功能,把字符串”1”转换为整数1。
[TestMethod]
public void Test() {
Assert.AreEqual( 1, Util.ConvertHelper.ToInt( "1" ) );
}
我把常用公共操作类尽量放到顶级命名空间Util,这样我就可以通过编写Util.来弹出代码提示,这样我连常用类也不用记了。
使用ConvertHelper是一个常规命名,大多数开发人员可以理解它是一个类型转换的公共操作类。我也这样用了多年,不过后面我发现Util.ConvertHelper有点啰嗦,所以我简化成Util.Convert,但Convert又和系统重名了,所以我现在使用Util.Conv,你不一定要按我的这个命名,你可以使用ConvertHelper这样的命名以提高代码清晰度。
System.Convert使用ToInt32来精确表示int是一个32位的数字,不过我们的公共操作类不用这样精确,ToInt就可以了,如果要封装ToInt64呢,我就用ToLong,这样比较符合我的习惯。
现在代码被简化成了下面的代码。
Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) );
Assert在测试中用来断言,断言就是比较实际计算出来的值是否和预期一致,Assert包含大量比较方法,AreEqual使用频率最高,用来比较预期值(左边)与实际值(右边)是否值相等,还有一个AreSame方法用来比较是否引用相等。
由于Conv类还未创建,所以显示一个红色警告。 现在在Util类库项目中创建一个Conv类。
创建了Conv类以后,单元测试代码检测到Conv,但ToInt方法未创建,所以红色警告转移到ToInt方法。
现在用鼠标左键单击红色ToInit方法,Resharper在左侧显示一个红色的灯泡。
单击红色灯泡提示,选择第一项”Create Method ‘Conv.ToInt’”。
Resharper会在Conv类中自动创建一个ToInt方法。
public class Conv {
public static int ToInt( string s ) {
throw new NotImplementedException();
}
}
方法体抛出一个未实现的异常,这正是我们想要的。TDD的口诀是“红、绿、重构”,第一步需要先保证方法执行失败,显示红色警告。至于未何需要测试先行,以及首先执行失败,牵扯TDD开发价值观,请大家参考相关资料。
准备工作已经就绪,现在可以运行测试了。安装了Resharper以后,在添加了TestClass特性的左侧,会看见两个重叠在一起的圆形图标。另外,在TestMethod特性左侧,有一个黑白相间的圆形图标。
单击Test方法左侧的图标,然后点击Run按钮。如果单击TestClass特性左侧的图标,会运行该类所有测试。
测试开始运行,并显示红色警告,提示未实现的异常,第一步完成。
为了实现功能,现在来添加ToInt方法的代码。
public static int ToInt( string s ) {
int result;
int.TryParse( s, out result );
return result;
}
再次运行测试,已经能够成功通过,第二步完成。
第三步是进行重构,现在看哪些地方可以重构。参数s看起来有点不爽,改成data,并添加XML注释。
/// <summary>
/// 转换为整型
/// </summary>
/// <param name="data">数据</param>
public static int ToInt( string data ) {
int result;
int.TryParse( data, out result );
return result;
}
另外重构一下测试,为了更容易找到相关测试,一般测试文件名使用类名+Test,现在测试文件名改成ConvTest.cs,测试类名改成ConvTest。把测试方法名改成TestToInt,并添加XML注释。
/// <summary>
/// 测试转换为整型
/// </summary>
[TestMethod]
public void TestToInt() {
Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) );
}
关于测试的命名,很多著作都提出了自己不同的方法。在《.Net单元测试艺术》中,作者建议使用三部分进行组合命名。还有一些著作建议将测试内容用下划线分隔单词,拼成一个长句子,以方便阅读和理解。这可能对英文水平好的人很有效,不过我的英文水平很烂,我拿一些单词拼成一个长句以后,发现更难理解了。所以我所采用的测试方法命名可能不一定好,你可以按你容易理解的方式来命名。
重构之后,需要重新测试代码,以观察是否导致失败。
上面简单介绍了TDD的一套开发流程,主要为了照顾还没有体验过单元测试的人,后面直接粘贴代码,以避免这样低效的叙述方式。
单元测试代码如下。
1 using System;
2 using Microsoft.VisualStudio.TestTools.UnitTesting;
3
4 namespace Util.Tests {
5 /// <summary>
6 /// 类型转换公共操作类测试
7 /// </summary>
8 [TestClass]
9 public class ConvTest {
10
11 #region ToInt(转换为整型)
12
13 /// <summary>
14 ///转换为整型,值为null
15 ///</summary>
16 [TestMethod]
17 public void TestToInt_Null() {
18 Assert.AreEqual( 0, Util.Conv.ToInt( null ) );
19 }
20
21 /// <summary>
22 ///转换为整型,值为空字符串
23 ///</summary>
24 [TestMethod]
25 public void TestToInt_Empty() {
26 Assert.AreEqual( 0, Util.Conv.ToInt( "" ) );
27 }
28
29 /// <summary>
30 ///转换为整型,无效值
31 ///</summary>
32 [TestMethod]
33 public void TestToInt_Invalid() {
34 Assert.AreEqual( 0, Util.Conv.ToInt( "1A" ) );
35 }
36
37 /// <summary>
38 ///转换为整型,有效值
39 ///</summary>
40 [TestMethod]
41 public void TestToInt() {
42 Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) );
43 Assert.AreEqual( 1778020, Util.Conv.ToInt( "1778019.7801684" ) );
44 }
45
46 #endregion
47
48 #region ToIntOrNull(转换为可空整型)
49
50 /// <summary>
51 ///转换为可空整型,值为null
52 ///</summary>
53 [TestMethod]
54 public void TestToIntOrNull_Null() {
55 Assert.IsNull( Util.Conv.ToIntOrNull( null ) );
56 }
57
58 /// <summary>
59 ///转换为可空整型,值为空字符串
60 ///</summary>
61 [TestMethod]
62 public void TestToIntOrNull_Empty() {
63 Assert.IsNull( Util.Conv.ToIntOrNull( "" ) );
64 }
65
66 /// <summary>
67 ///转换为可空整型,无效值
68 ///</summary>
69 [TestMethod]
70 public void TestToIntOrNull_Invalid() {
71 Assert.IsNull( Util.Conv.ToIntOrNull( "1A" ) );
72 }
73
74 /// <summary>
75 ///转换为可空整型,值为0
76 ///</summary>
77 [TestMethod]
78 public void TestToIntOrNull_0() {
79 Assert.AreEqual( 0, Util.Conv.ToIntOrNull( "0" ) );
80 }
81
82 /// <summary>
83 ///转换为可空整型,有效值
84 ///</summary>
85 [TestMethod]
86 public void TestToIntOrNull() {
87 Assert.AreEqual( 1, Util.Conv.ToIntOrNull( "1" ) );
88 }
89
90 #endregion
91
92 #region ToDouble(转换为双精度浮点数)
93
94 /// <summary>
95 ///转换为双精度浮点数,值为null
96 ///</summary>
97 [TestMethod]
98 public void TestToDouble_Null() {
99 Assert.AreEqual( 0, Util.Conv.ToDouble( null ) );
100 }
101
102 /// <summary>
103 ///转换为双精度浮点数,值为空字符串
104 ///</summary>
105 [TestMethod]
106 public void TestToDouble_Empty() {
107 Assert.AreEqual( 0, Util.Conv.ToDouble( "" ) );
108 }
109
110 /// <summary>
111 ///转换为双精度浮点数,无效值
112 ///</summary>
113 [TestMethod]
114 public void TestToDouble_Invalid() {
115 Assert.AreEqual( 0, Util.Conv.ToDouble( "1A" ) );
116 }
117
118 /// <summary>
119 ///转换为双精度浮点数,有效值
120 ///</summary>
121 [TestMethod]
122 public void TestToDouble() {
123 Assert.AreEqual( 1.2, Util.Conv.ToDouble( "1.2" ) );
124 }
125
126 /// <summary>
127 /// 转换为双精度浮点数,指定2位小数位数
128 ///</summary>
129 [TestMethod()]
130 public void TestToDouble_DigitsIs2() {
131 Assert.AreEqual( 12.36, Util.Conv.ToDouble( "12.355", 2 ) );
132 }
133
134 #endregion
135
136 #region ToDoubleOrNull(转换为可空双精度浮点数)
137
138 /// <summary>
139 ///转换为可空双精度浮点数,值为null
140 ///</summary>
141 [TestMethod]
142 public void TestToDoubleOrNull_Null() {
143 Assert.IsNull( Util.Conv.ToDoubleOrNull( null ) );
144 }
145
146 /// <summary>
147 ///转换为可空双精度浮点数,值为空字符串
148 ///</summary>
149 [TestMethod]
150 public void TestToDoubleOrNull_Empty() {
151 Assert.IsNull( Util.Conv.ToDoubleOrNull( "" ) );
152 }
153
154 /// <summary>
155 ///转换为可空双精度浮点数,无效值
156 ///</summary>
157 [TestMethod]
158 public void TestToDoubleOrNull_Invalid() {
159 Assert.IsNull( Util.Conv.ToDoubleOrNull( "1A" ) );
160 }
161
162 /// <summary>
163 ///转换为可空双精度浮点数,值为0
164 ///</summary>
165 [TestMethod]
166 public void TestToDoubleOrNull_0() {
167 Assert.AreEqual( 0, Util.Conv.ToDoubleOrNull( "0" ) );
168 }
169
170 /// <summary>
171 ///转换为可空双精度浮点数,有效值
172 ///</summary>
173 [TestMethod]
174 public void TestToDoubleOrNull() {
175 Assert.AreEqual( 1.2, Util.Conv.ToDoubleOrNull( "1.2" ) );
176 }
177
178 #endregion
179
180 #region ToDecimal(转换为高精度浮点数)
181
182 /// <summary>
183 ///转换为高精度浮点数,值为null
184 ///</summary>
185 [TestMethod]
186 public void TestToDecimal_Null() {
187 Assert.AreEqual( 0, Util.Conv.ToDecimal( null ) );
188 }
189
190 /// <summary>
191 ///转换为高精度浮点数,值为空字符串
192 ///</summary>
193 [TestMethod]
194 public void TestToDecimal_Empty() {
195 Assert.AreEqual( 0, Util.Conv.ToDecimal( "" ) );
196 }
197
198 /// <summary>
199 ///转换为高精度浮点数,无效值
200 ///</summary>
201 [TestMethod]
202 public void TestToDecimal_Invalid() {
203 Assert.AreEqual( 0, Util.Conv.ToDecimal( "1A" ) );
204 }
205
206 /// <summary>
207 ///转换为高精度浮点数,有效值
208 ///</summary>
209 [TestMethod]
210 public void TestToDecimal() {
211 Assert.AreEqual( 1.2M, Util.Conv.ToDecimal( "1.2" ) );
212 }
213
214 /// <summary>
215 /// 转换为高精度浮点数,指定2位小数位数
216 ///</summary>
217 [TestMethod()]
218 public void TestToDecimal_DigitsIs2() {
219 Assert.AreEqual( 12.24M, Util.Conv.ToDecimal( "12.235", 2 ) );
220 }
221
222 #endregion
223
224 #region ToDecimalOrNull(转换为可空高精度浮点数)
225
226 /// <summary>
227 ///转换为可空高精度浮点数,值为null
228 ///</summary>
229 [TestMethod]
230 public void TestToDecimalOrNull_Null() {
231 Assert.IsNull( Util.Conv.ToDecimalOrNull( null ) );
232 }
233
234 /// <summary>
235 ///转换为可空高精度浮点数,值为空字符串
236 ///</summary>
237 [TestMethod]
238 public void TestToDecimalOrNull_Empty() {
239 Assert.IsNull( Util.Conv.ToDecimalOrNull( "" ) );
240 }
241
242 /// <summary>
243 ///转换为可空高精度浮点数,无效值
244 ///</summary>
245 [TestMethod]
246 public void TestToDecimalOrNull_Invalid() {
247 Assert.IsNull( Util.Conv.ToDecimalOrNull( "1A" ) );
248 }
249
250 /// <summary>
251 ///转换为可空高精度浮点数,无效值,指定2位小数位数
252 ///</summary>
253 [TestMethod]
254 public void TestToDecimalOrNull_Invalid_DigitsIs2() {
255 Assert.IsNull( Util.Conv.ToDecimalOrNull( "1A", 2 ) );
256 }
257
258 /// <summary>
259 ///转换为可空高精度浮点数,值为0
260 ///</summary>
261 [TestMethod]
262 public void TestToDecimalOrNull_0() {
263 Assert.AreEqual( 0, Util.Conv.ToDecimalOrNull( "0" ) );
264 }
265
266 /// <summary>
267 ///转换为可空高精度浮点数,有效值
268 ///</summary>
269 [TestMethod]
270 public void TestToDecimalOrNull() {
271 Assert.AreEqual( 1.2M, Util.Conv.ToDecimalOrNull( "1.2" ) );
272 }
273
274 /// <summary>
275 /// 转换为可空高精度浮点数,指定2位小数位数
276 ///</summary>
277 [TestMethod()]
278 public void ToDecimalOrNull_DigitsIs2() {
279 Assert.AreEqual( 12.24M, Util.Conv.ToDecimalOrNull( "12.235", 2 ) );
280 }
281
282 #endregion
283
284 #region ToGuid(转换为Guid)
285
286 /// <summary>
287 ///转换为Guid,值为null
288 ///</summary>
289 [TestMethod]
290 public void TestToGuid_Null() {
291 Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( null ) );
292 }
293
294 /// <summary>
295 ///转换为Guid,值为空字符串
296 ///</summary>
297 [TestMethod]
298 public void TestToGuid_Empty() {
299 Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( "" ) );
300 }
301
302 /// <summary>
303 ///转换为Guid,无效值
304 ///</summary>
305 [TestMethod]
306 public void TestToGuid_Invalid() {
307 Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( "1A" ) );
308 }
309
310 /// <summary>
311 ///转换为Guid,有效值
312 ///</summary>
313 [TestMethod]
314 public void TestToGuid() {
315 Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ), Util.Conv.ToGuid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
316 }
317
318 #endregion
319
320 #region ToGuidOrNull(转换为可空Guid)
321
322 /// <summary>
323 ///转换为可空Guid,值为null
324 ///</summary>
325 [TestMethod]
326 public void TestToGuidOrNull_Null() {
327 Assert.IsNull( Util.Conv.ToGuidOrNull( null ) );
328 }
329
330 /// <summary>
331 ///转换为可空Guid,值为空字符串
332 ///</summary>
333 [TestMethod]
334 public void TestToGuidOrNull_Empty() {
335 Assert.IsNull( Util.Conv.ToGuidOrNull( "" ) );
336 }
337
338 /// <summary>
339 ///转换为可空Guid,无效值
340 ///</summary>
341 [TestMethod]
342 public void TestToGuidOrNull_Invalid() {
343 Assert.IsNull( Util.Conv.ToGuidOrNull( "1A" ) );
344 }
345
346 /// <summary>
347 ///转换为可空Guid,有效值
348 ///</summary>
349 [TestMethod]
350 public void TestToGuidOrNull() {
351 Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ), Util.Conv.ToGuidOrNull( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
352 }
353
354 #endregion
355
356 #region ToGuidList(转换为Guid集合)
357
358 /// <summary>
359 /// 转换为Guid集合,验证空字符串
360 /// </summary>
361 [TestMethod]
362 public void TestToGuidList_Empty() {
363 Assert.AreEqual( 0, Util.Conv.ToGuidList( "" ).Count );
364 }
365
366 /// <summary>
367 /// 转换为Guid集合,验证最后为逗号
368 /// </summary>
369 [TestMethod]
370 public void TestToGuidList_LastIsComma() {
371 Assert.AreEqual( 1, Util.Conv.ToGuidList( "83B0233C-A24F-49FD-8083-1337209EBC9A," ).Count );
372 }
373
374 /// <summary>
375 /// 转换为Guid集合,验证中间包含逗号
376 /// </summary>
377 [TestMethod]
378 public void TestToGuidList_MiddleIsComma() {
379 const string guid = "83B0233C-A24F-49FD-8083-1337209EBC9A,,EAB523C6-2FE7-47BE-89D5-C6D440C3033A,";
380 Assert.AreEqual( 2, Util.Conv.ToGuidList( guid ).Count );
381 Assert.AreEqual( new Guid( "83B0233C-A24F-49FD-8083-1337209EBC9A" ), Util.Conv.ToGuidList( guid )[0] );
382 Assert.AreEqual( new Guid( "EAB523C6-2FE7-47BE-89D5-C6D440C3033A" ), Util.Conv.ToGuidList( guid )[1] );
383 }
384
385 /// <summary>
386 /// 转换为Guid集合,仅1个Guid
387 /// </summary>
388 [TestMethod]
389 public void TestToGuidList_1Guid() {
390 const string guid = "83B0233C-A24F-49FD-8083-1337209EBC9A";
391 Assert.AreEqual( 1, Util.Conv.ToGuidList( guid ).Count );
392 Assert.AreEqual( new Guid( guid ), Util.Conv.ToGuidList( guid )[0] );
393 }
394
395 /// <summary>
396 /// 转换为Guid集合,2个Guid
397 /// </summary>
398 [TestMethod]
399 public void TestToGuidList_2Guid() {
400 const string guid = "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A";
401 Assert.AreEqual( 2, Util.Conv.ToGuidList( guid ).Count );
402 Assert.AreEqual( new Guid( "83B0233C-A24F-49FD-8083-1337209EBC9A" ), Util.Conv.ToGuidList( guid )[0] );
403 Assert.AreEqual( new Guid( "EAB523C6-2FE7-47BE-89D5-C6D440C3033A" ), Util.Conv.ToGuidList( guid )[1] );
404 }
405
406 #endregion
407
408 #region ToDate(转换为日期)
409
410 /// <summary>
411 ///转换为日期,值为null
412 ///</summary>
413 [TestMethod]
414 public void TestToDate_Null() {
415 Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( null ) );
416 }
417
418 /// <summary>
419 ///转换为日期,值为空字符串
420 ///</summary>
421 [TestMethod]
422 public void TestToDate_Empty() {
423 Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( "" ) );
424 }
425
426 /// <summary>
427 ///转换为日期,无效值
428 ///</summary>
429 [TestMethod]
430 public void TestToDate_Invalid() {
431 Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( "1A" ) );
432 }
433
434 /// <summary>
435 ///转换为日期,有效值
436 ///</summary>
437 [TestMethod]
438 public void TestToDate() {
439 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Util.Conv.ToDate( "2000-1-1" ) );
440 }
441
442 #endregion
443
444 #region ToDateOrNull(转换为可空日期)
445
446 /// <summary>
447 ///转换为可空日期,值为null
448 ///</summary>
449 [TestMethod]
450 public void TestToDateOrNull_Null() {
451 Assert.IsNull( Util.Conv.ToDateOrNull( null ) );
452 }
453
454 /// <summary>
455 ///转换为可空日期,值为空字符串
456 ///</summary>
457 [TestMethod]
458 public void TestToDateOrNull_Empty() {
459 Assert.IsNull( Util.Conv.ToDateOrNull( "" ) );
460 }
461
462 /// <summary>
463 ///转换为可空日期,无效值
464 ///</summary>
465 [TestMethod]
466 public void TestToDateOrNull_Invalid() {
467 Assert.IsNull( Util.Conv.ToDateOrNull( "1A" ) );
468 }
469
470 /// <summary>
471 ///转换为可空日期,有效值
472 ///</summary>
473 [TestMethod]
474 public void TestToDateOrNull() {
475 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Util.Conv.ToDateOrNull( "2000-1-1" ) );
476 }
477
478 #endregion
479
480 #region ToBool(转换为布尔值)
481
482 /// <summary>
483 ///转换为布尔值,值为null
484 ///</summary>
485 [TestMethod]
486 public void TestToBool_Null() {
487 Assert.AreEqual( false, Util.Conv.ToBool( null ) );
488 }
489
490 /// <summary>
491 ///转换为布尔值,值为空字符串
492 ///</summary>
493 [TestMethod]
494 public void TestToBool_Empty() {
495 Assert.AreEqual( false, Util.Conv.ToBool( "" ) );
496 }
497
498 /// <summary>
499 ///转换为布尔值,无效值
500 ///</summary>
501 [TestMethod]
502 public void TestToBool_Invalid() {
503 Assert.AreEqual( false, Util.Conv.ToBool( "1A" ) );
504 }
505
506 /// <summary>
507 ///转换为布尔值,值为False
508 ///</summary>
509 [TestMethod]
510 public void TestToBool_False() {
511 Assert.AreEqual( false, Util.Conv.ToBool( "0" ) );
512 Assert.AreEqual( false, Util.Conv.ToBool( "否" ) );
513 Assert.AreEqual( false, Util.Conv.ToBool( "no" ) );
514 Assert.AreEqual( false, Util.Conv.ToBool( "No" ) );
515 Assert.AreEqual( false, Util.Conv.ToBool( "false" ) );
516 Assert.AreEqual( false, Util.Conv.ToBool( "False" ) );
517 }
518
519 /// <summary>
520 ///转换为布尔值,值为True
521 ///</summary>
522 [TestMethod]
523 public void TestToBool_True() {
524 Assert.AreEqual( true, Util.Conv.ToBool( "1" ) );
525 Assert.AreEqual( true, Util.Conv.ToBool( "是" ) );
526 Assert.AreEqual( true, Util.Conv.ToBool( "yes" ) );
527 Assert.AreEqual( true, Util.Conv.ToBool( "Yes" ) );
528 Assert.AreEqual( true, Util.Conv.ToBool( "true" ) );
529 Assert.AreEqual( true, Util.Conv.ToBool( "True" ) );
530 }
531
532 #endregion
533
534 #region ToBoolOrNull(转换为可空布尔值)
535
536 /// <summary>
537 ///转换为可空布尔值,值为null
538 ///</summary>
539 [TestMethod]
540 public void TestToBoolOrNull_Null() {
541 Assert.IsNull( Util.Conv.ToBoolOrNull( null ) );
542 }
543
544 /// <summary>
545 ///转换为可空布尔值,值为空字符串
546 ///</summary>
547 [TestMethod]
548 public void TestToBoolOrNull_Empty() {
549 Assert.IsNull( Util.Conv.ToBoolOrNull( "" ) );
550 }
551
552 /// <summary>
553 ///转换为可空布尔值,无效值
554 ///</summary>
555 [TestMethod]
556 public void TestToBoolOrNull_Invalid() {
557 Assert.IsNull( Util.Conv.ToBoolOrNull( "1A" ) );
558 }
559
560 /// <summary>
561 ///转换为布尔值,值为False
562 ///</summary>
563 [TestMethod]
564 public void TestToBoolOrNull_False() {
565 Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "0" ) );
566 Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "否" ) );
567 Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "no" ) );
568 Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "No" ) );
569 Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "false" ) );
570 Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "False" ) );
571 }
572
573 /// <summary>
574 ///转换为布尔值,值为True
575 ///</summary>
576 [TestMethod]
577 public void TestToBoolOrNull_True() {
578 Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "1" ) );
579 Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "是" ) );
580 Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "yes" ) );
581 Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "Yes" ) );
582 Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "true" ) );
583 Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "True" ) );
584 }
585
586 #endregion
587
588 #region ToString(转换为字符串)
589
590 /// <summary>
591 ///转换为字符串,值为null
592 ///</summary>
593 [TestMethod]
594 public void TestToString_Null() {
595 Assert.AreEqual( string.Empty, Util.Conv.ToString( null ) );
596 }
597
598 /// <summary>
599 ///转换为字符串,值为空字符串
600 ///</summary>
601 [TestMethod]
602 public void TestToString_Empty() {
603 Assert.AreEqual( string.Empty, Util.Conv.ToString( " " ) );
604 }
605
606 /// <summary>
607 ///转换为字符串,有效值
608 ///</summary>
609 [TestMethod]
610 public void TestToString() {
611 Assert.AreEqual( "1", Util.Conv.ToString( 1 ) );
612 }
613
614 #endregion
615
616 #region To(通用泛型转换)
617
618 #region 目标为int
619
620 /// <summary>
621 ///通用泛型转换,目标为整数,值为null
622 ///</summary>
623 [TestMethod]
624 public void TestTo_Int_Null() {
625 Assert.AreEqual( 0, Conv.To<int>( null ) );
626 }
627
628 /// <summary>
629 ///通用泛型转换,目标为整数,值为空字符串
630 ///</summary>
631 [TestMethod]
632 public void TestTo_Int_Empty() {
633 Assert.AreEqual( 0, Conv.To<int>( "" ) );
634 }
635
636 /// <summary>
637 ///通用泛型转换,目标为整数,无效值
638 ///</summary>
639 [TestMethod]
640 public void TestTo_Int_Invalid() {
641 Assert.AreEqual( 0, Conv.To<int>( "1A" ) );
642 }
643
644 /// <summary>
645 ///通用泛型转换,目标为整数,有效值
646 ///</summary>
647 [TestMethod]
648 public void TestTo_Int() {
649 Assert.AreEqual( 1, Conv.To<int>( "1" ) );
650 }
651
652 /// <summary>
653 ///通用泛型转换,目标为可空整数,无效值
654 ///</summary>
655 [TestMethod]
656 public void TestTo_IntOrNull_Invalid() {
657 Assert.IsNull( Conv.To<int?>( "1A" ) );
658 }
659
660 /// <summary>
661 ///通用泛型转换,目标为可空整数,有效值
662 ///</summary>
663 [TestMethod]
664 public void TestTo_IntOrNull() {
665 Assert.AreEqual( 1, Conv.To<int?>( "1" ) );
666 }
667
668 #endregion
669
670 #region 目标为Guid
671
672 /// <summary>
673 ///通用泛型转换,目标为Guid,无效值
674 ///</summary>
675 [TestMethod]
676 public void TestTo_Guid_Invalid() {
677 Assert.AreEqual( Guid.Empty, Conv.To<Guid>( "1A" ) );
678 }
679
680 /// <summary>
681 ///通用泛型转换,目标为Guid,有效值
682 ///</summary>
683 [TestMethod]
684 public void TestTo_Guid() {
685 Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ),
686 Conv.To<Guid>( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
687 }
688
689 /// <summary>
690 ///通用泛型转换,目标为可空Guid,无效值
691 ///</summary>
692 [TestMethod]
693 public void TestTo_GuidOrNull_Invalid() {
694 Assert.IsNull( Conv.To<Guid?>( "1A" ) );
695 }
696
697 /// <summary>
698 ///通用泛型转换,目标为可空Guid,有效值
699 ///</summary>
700 [TestMethod]
701 public void TestTo_GuidOrNull() {
702 Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ),
703 Conv.To<Guid?>( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
704 }
705
706 #endregion
707
708 #region 目标为string
709
710 /// <summary>
711 ///通用泛型转换,目标为string,有效值
712 ///</summary>
713 [TestMethod]
714 public void TestTo_String() {
715 Assert.AreEqual( "123", Conv.To<string>( 123 ) );
716 }
717
718 #endregion
719
720 #region 目标为double
721
722 /// <summary>
723 ///通用泛型转换,目标为double,无效值
724 ///</summary>
725 [TestMethod]
726 public void TestTo_Double_Invalid() {
727 Assert.AreEqual( 0, Conv.To<double>( "1A" ) );
728 }
729
730 /// <summary>
731 ///通用泛型转换,目标为double,有效值
732 ///</summary>
733 [TestMethod]
734 public void TestTo_Double() {
735 Assert.AreEqual( 12.5, Conv.To<double>( "12.5" ) );
736 }
737
738 /// <summary>
739 ///通用泛型转换,目标为可空double,无效值
740 ///</summary>
741 [TestMethod]
742 public void TestTo_DoubleOrNull_Invalid() {
743 Assert.IsNull( Conv.To<double?>( "1A" ) );
744 }
745
746 /// <summary>
747 ///通用泛型转换,目标为可空double,有效值
748 ///</summary>
749 [TestMethod]
750 public void TestTo_DoubleOrNull() {
751 Assert.AreEqual( 12.5, Conv.To<double?>( "12.5" ) );
752 }
753
754 #endregion
755
756 #region 目标为decimal
757
758 /// <summary>
759 ///通用泛型转换,目标为decimal,无效值
760 ///</summary>
761 [TestMethod]
762 public void TestTo_Decimal_Invalid() {
763 Assert.AreEqual( 0, Conv.To<decimal>( "1A" ) );
764 }
765
766 /// <summary>
767 ///通用泛型转换,目标为decimal,有效值
768 ///</summary>
769 [TestMethod]
770 public void TestTo_Decimal() {
771 Assert.AreEqual( 12.5M, Conv.To<decimal>( "12.5" ) );
772 }
773
774 /// <summary>
775 ///通用泛型转换,目标为可空decimal,无效值
776 ///</summary>
777 [TestMethod]
778 public void TestTo_DecimalOrNull_Invalid() {
779 Assert.IsNull( Conv.To<decimal?>( "1A" ) );
780 }
781
782 /// <summary>
783 ///通用泛型转换,目标为可空decimal,有效值
784 ///</summary>
785 [TestMethod]
786 public void TestTo_DecimalOrNull() {
787 Assert.AreEqual( 12.5M, Conv.To<decimal?>( "12.5" ) );
788 }
789
790 #endregion
791
792 #region 目标为bool
793
794 /// <summary>
795 ///通用泛型转换,目标为bool,无效值
796 ///</summary>
797 [TestMethod]
798 public void TestTo_Bool_Invalid() {
799 Assert.AreEqual( false, Conv.To<bool>( "1A" ) );
800 }
801
802 /// <summary>
803 ///通用泛型转换,目标为bool,有效值
804 ///</summary>
805 [TestMethod]
806 public void TestTo_Bool() {
807 Assert.AreEqual( true, Conv.To<bool>( 1 ) );
808 }
809
810 /// <summary>
811 ///通用泛型转换,目标为可空bool,无效值
812 ///</summary>
813 [TestMethod]
814 public void TestTo_BoolOrNull_Invalid() {
815 Assert.IsNull( Conv.To<bool?>( "1A" ) );
816 }
817
818 /// <summary>
819 ///通用泛型转换,目标为可空bool,有效值
820 ///</summary>
821 [TestMethod]
822 public void TestTo_BoolOrNull() {
823 Assert.AreEqual( true, Conv.To<bool?>( "true" ) );
824 }
825
826 #endregion
827
828 #region 目标为DateTime
829
830 /// <summary>
831 ///通用泛型转换,目标为DateTime,无效值
832 ///</summary>
833 [TestMethod]
834 public void TestTo_DateTime_Invalid() {
835 Assert.AreEqual( DateTime.MinValue, Conv.To<DateTime>( "1A" ) );
836 }
837
838 /// <summary>
839 ///通用泛型转换,目标为DateTime,有效值
840 ///</summary>
841 [TestMethod]
842 public void TestTo_DateTime() {
843 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Conv.To<DateTime>( "2000-1-1" ) );
844 }
845
846 /// <summary>
847 ///通用泛型转换,目标为可空DateTime,无效值
848 ///</summary>
849 [TestMethod]
850 public void TestTo_DateTimeOrNull_Invalid() {
851 Assert.IsNull( Conv.To<DateTime?>( "1A" ) );
852 }
853
854 /// <summary>
855 ///通用泛型转换,目标为可空DateTime,有效值
856 ///</summary>
857 [TestMethod]
858 public void TestTo_DateTimeOrNull() {
859 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Conv.To<DateTime?>( "2000-1-1" ) );
860 }
861
862 #endregion
863
864 #endregion
865 }
866 }
Conv类代码如下。
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4
5 namespace Util {
6 /// <summary>
7 /// 类型转换
8 /// </summary>
9 public static class Conv {
10
11 #region 数值转换
12
13 /// <summary>
14 /// 转换为整型
15 /// </summary>
16 /// <param name="data">数据</param>
17 public static int ToInt( object data ) {
18 if ( data == null )
19 return 0;
20 int result;
21 var success = int.TryParse( data.ToString(), out result );
22 if ( success == true )
23 return result;
24 try {
25 return Convert.ToInt32( ToDouble( data, 0 ) );
26 }
27 catch ( Exception ) {
28 return 0;
29 }
30 }
31
32 /// <summary>
33 /// 转换为可空整型
34 /// </summary>
35 /// <param name="data">数据</param>
36 public static int? ToIntOrNull( object data ) {
37 if ( data == null )
38 return null;
39 int result;
40 bool isValid = int.TryParse( data.ToString(), out result );
41 if ( isValid )
42 return result;
43 return null;
44 }
45
46 /// <summary>
47 /// 转换为双精度浮点数
48 /// </summary>
49 /// <param name="data">数据</param>
50 public static double ToDouble( object data ) {
51 if ( data == null )
52 return 0;
53 double result;
54 return double.TryParse( data.ToString(), out result ) ? result : 0;
55 }
56
57 /// <summary>
58 /// 转换为双精度浮点数,并按指定的小数位4舍5入
59 /// </summary>
60 /// <param name="data">数据</param>
61 /// <param name="digits">小数位数</param>
62 public static double ToDouble( object data, int digits ) {
63 return Math.Round( ToDouble( data ), digits );
64 }
65
66 /// <summary>
67 /// 转换为可空双精度浮点数
68 /// </summary>
69 /// <param name="data">数据</param>
70 public static double? ToDoubleOrNull( object data ) {
71 if ( data == null )
72 return null;
73 double result;
74 bool isValid = double.TryParse( data.ToString(), out result );
75 if ( isValid )
76 return result;
77 return null;
78 }
79
80 /// <summary>
81 /// 转换为高精度浮点数
82 /// </summary>
83 /// <param name="data">数据</param>
84 public static decimal ToDecimal( object data ) {
85 if ( data == null )
86 return 0;
87 decimal result;
88 return decimal.TryParse( data.ToString(), out result ) ? result : 0;
89 }
90
91 /// <summary>
92 /// 转换为高精度浮点数,并按指定的小数位4舍5入
93 /// </summary>
94 /// <param name="data">数据</param>
95 /// <param name="digits">小数位数</param>
96 public static decimal ToDecimal( object data, int digits ) {
97 return Math.Round( ToDecimal( data ), digits );
98 }
99
100 /// <summary>
101 /// 转换为可空高精度浮点数
102 /// </summary>
103 /// <param name="data">数据</param>
104 public static decimal? ToDecimalOrNull( object data ) {
105 if ( data == null )
106 return null;
107 decimal result;
108 bool isValid = decimal.TryParse( data.ToString(), out result );
109 if ( isValid )
110 return result;
111 return null;
112 }
113
114 /// <summary>
115 /// 转换为可空高精度浮点数,并按指定的小数位4舍5入
116 /// </summary>
117 /// <param name="data">数据</param>
118 /// <param name="digits">小数位数</param>
119 public static decimal? ToDecimalOrNull( object data, int digits ) {
120 var result = ToDecimalOrNull( data );
121 if ( result == null )
122 return null;
123 return Math.Round( result.Value, digits );
124 }
125
126 #endregion
127
128 #region Guid转换
129
130 /// <summary>
131 /// 转换为Guid
132 /// </summary>
133 /// <param name="data">数据</param>
134 public static Guid ToGuid( object data ) {
135 if ( data == null )
136 return Guid.Empty;
137 Guid result;
138 return Guid.TryParse( data.ToString(), out result ) ? result : Guid.Empty;
139 }
140
141 /// <summary>
142 /// 转换为可空Guid
143 /// </summary>
144 /// <param name="data">数据</param>
145 public static Guid? ToGuidOrNull( object data ) {
146 if ( data == null )
147 return null;
148 Guid result;
149 bool isValid = Guid.TryParse( data.ToString(), out result );
150 if ( isValid )
151 return result;
152 return null;
153 }
154
155 /// <summary>
156 /// 转换为Guid集合
157 /// </summary>
158 /// <param name="guid">guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A</param>
159 public static List<Guid> ToGuidList( string guid ) {
160 var listGuid = new List<Guid>();
161 if ( string.IsNullOrWhiteSpace( guid ) )
162 return listGuid;
163 var arrayGuid = guid.Split( ',' );
164 listGuid.AddRange( from each in arrayGuid where !string.IsNullOrWhiteSpace( each ) select new Guid( each ) );
165 return listGuid;
166 }
167
168 #endregion
169
170 #region 日期转换
171
172 /// <summary>
173 /// 转换为日期
174 /// </summary>
175 /// <param name="data">数据</param>
176 public static DateTime ToDate( object data ) {
177 if ( data == null )
178 return DateTime.MinValue;
179 DateTime result;
180 return DateTime.TryParse( data.ToString(), out result ) ? result : DateTime.MinValue;
181 }
182
183 /// <summary>
184 /// 转换为可空日期
185 /// </summary>
186 /// <param name="data">数据</param>
187 public static DateTime? ToDateOrNull( object data ) {
188 if ( data == null )
189 return null;
190 DateTime result;
191 bool isValid = DateTime.TryParse( data.ToString(), out result );
192 if ( isValid )
193 return result;
194 return null;
195 }
196
197 #endregion
198
199 #region 布尔转换
200
201 /// <summary>
202 /// 转换为布尔值
203 /// </summary>
204 /// <param name="data">数据</param>
205 public static bool ToBool( object data ) {
206 if ( data == null )
207 return false;
208 bool? value = GetBool( data );
209 if ( value != null )
210 return value.Value;
211 bool result;
212 return bool.TryParse( data.ToString(), out result ) && result;
213 }
214
215 /// <summary>
216 /// 获取布尔值
217 /// </summary>
218 private static bool? GetBool( object data ) {
219 switch ( data.ToString().Trim().ToLower() ) {
220 case "0":
221 return false;
222 case "1":
223 return true;
224 case "是":
225 return true;
226 case "否":
227 return false;
228 case "yes":
229 return true;
230 case "no":
231 return false;
232 default:
233 return null;
234 }
235 }
236
237 /// <summary>
238 /// 转换为可空布尔值
239 /// </summary>
240 /// <param name="data">数据</param>
241 public static bool? ToBoolOrNull( object data ) {
242 if ( data == null )
243 return null;
244 bool? value = GetBool( data );
245 if ( value != null )
246 return value.Value;
247 bool result;
248 bool isValid = bool.TryParse( data.ToString(), out result );
249 if ( isValid )
250 return result;
251 return null;
252 }
253
254 #endregion
255
256 #region 字符串转换
257
258 /// <summary>
259 /// 转换为字符串
260 /// </summary>
261 /// <param name="data">数据</param>
262 public static string ToString( object data ) {
263 return data == null ? string.Empty : data.ToString().Trim();
264 }
265
266 #endregion
267
268 #region 通用转换
269
270 /// <summary>
271 /// 泛型转换
272 /// </summary>
273 /// <typeparam name="T">目标类型</typeparam>
274 /// <param name="data">数据</param>
275 public static T To<T>( object data ) {
276 if ( data == null || string.IsNullOrWhiteSpace( data.ToString() ) )
277 return default( T );
278 Type type = Nullable.GetUnderlyingType( typeof( T ) ) ?? typeof( T );
279 try {
280 if ( type.Name.ToLower() == "guid" )
281 return (T)(object)new Guid( data.ToString() );
282 if ( data is IConvertible )
283 return (T)Convert.ChangeType( data, type );
284 return (T)data;
285 }
286 catch {
287 return default( T );
288 }
289 }
290
291 #endregion
292 }
293 }
Conv公共操作类的用法,在单元测试中已经说得很清楚了,这也是单元测试的一个用途,即作为API说明文档。
单元测试最强大的地方,可能是能够帮助你回归测试,如果你发现我的代码有BUG,请通知我一声,我只需要在单元测试中增加一个测试来捕获这个BUG,就可以永久修复它,并且由于采用TDD方式可以获得很高的测试覆盖率,所以我花上几秒钟运行一下全部测试,就可以知道这次修改有没有影响其它代码。这也是你创建自己的应用程序框架所必须要做的,它可以给你提供信心。
可以看到,我在单元测试中进行了很多边界测试,比如参数为null或空字符串等。但不可能穷举所有可能出错的情况,因为可能想不到,另外时间有限,也不可能做到。当在项目上发现BUG后,再通过添加单元测试的方式修复BUG就可以了。由于你的项目代码调用的是应用程序框架API,所以你只需要在框架内修复一次,项目代码完全不动。
像数据类型转换这样简单的操作,你发现写单元测试非常容易,因为它有明确的返回值,但如果没有返回值呢,甚至有外部依赖呢,那就没有这么简单了,需要很多技巧,所以你多看几本TDD和单元测试方面的著作有很多好处。
另外,再补充一下,Conv这个类里面有几个法宝。一个是ToGuidList这个方法,当你需要把字符串转换为List<Guid>的时候就用它。还有一个泛型转换的方法To<T>,很多时候可以用它进行泛型转换。
最后,我把所有方法参数类型都改成了object,主要是想使用起来方便一点,而不是只支持字符串参数,这可能导致装箱和拆箱,从而造成一些性能损失,不过我的大多数项目在性能方面还没有这么高的要求,所以这个损失对我来讲无关痛痒。
还有些数据类型转换,我没有放进来,主要是我平时很少用到,当我用到时再增加。
.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。
谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/
下载地址: https://files.cnblogs.com/xiadao521/Util.2014.11.12.1.rar