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 }
ConvTest

  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

  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/

  下载地址: http://files.cnblogs.com/xiadao521/Util.2014.11.12.1.rar

原文地址:https://www.cnblogs.com/xiadao521/p/4092846.html