上一篇介绍了数据类型转换的一些情况,可以看出,如果不进行封装,有可能导致比较混乱的代码。本文通过TDD方式把数据类型转换公共操作类开发出来,并提供源码下载。
我们在 应用程序框架实战十一:创建VS解决方案与程序集 一文已经创建了解决方案,包含一个类库项目和一个单元测试项目。单元测试将使用.Net自带的 MsTest,另外通过Resharper工具来观察测试结果。
首先考虑我们期望的API长成什么样子。基于TDD开发,其中一个作用是帮助程序员设计期望的API,这称为意图导向编程。
因为数据类型转换是Convert,所以我们先在单元测试项目中创建一个ConvertTest的类文件。
类创建好以后,我先随便创建一个方法Test,以迅速展开工作。测试的方法名Test,我是随便起的,因为现在还不清楚API是什么样,我一会再回过头来改。
using Microsoft.VisualStudio.TestTools.UnitTesting;namespace Util.Tests { ////// 类型转换公共操作类测试 /// [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注释。
////// 转换为整型 /// /// 数据 public static int ToInt( string data ) { int result; int.TryParse( data, out result ); return result; }
另外重构一下测试,为了更容易找到相关测试,一般测试文件名使用类名+Test,现在测试文件名改成ConvTest.cs,测试类名改成ConvTest。把测试方法名改成TestToInt,并添加XML注释。
////// 测试转换为整型 /// [TestMethod] public void TestToInt() { Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) ); }
关于测试的命名,很多著作都提出了自己不同的方法。在《.Net单元测试艺术》中,作者建议使用三部分进行组合命名。还有一些著作建议将测试内容用下划线分隔单词,拼成一个长句子,以方便阅读和理解。这可能对英文水平好的人很有效,不过我的英文水平很烂,我拿一些单词拼成一个长句以后,发现更难理解了。所以我所采用的测试方法命名可能不一定好,你可以按你容易理解的方式来命名。
重构之后,需要重新测试代码,以观察是否导致失败。
上面简单介绍了TDD的一套开发流程,主要为了照顾还没有体验过单元测试的人,后面直接粘贴代码,以避免这样低效的叙述方式。
单元测试代码如下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 using System; 2 using Microsoft.VisualStudio.TestTools.UnitTesting; 3 4 namespace Util.Tests { 5 ///6 /// 类型转换公共操作类测试 7 /// 8 [TestClass] 9 public class ConvTest { 10 11 #region ToInt(转换为整型) 12 13 ///14 ///转换为整型,值为null 15 /// 16 [TestMethod] 17 public void TestToInt_Null() { 18 Assert.AreEqual( 0, Util.Conv.ToInt( null ) ); 19 } 20 21 ///22 ///转换为整型,值为空字符串 23 /// 24 [TestMethod] 25 public void TestToInt_Empty() { 26 Assert.AreEqual( 0, Util.Conv.ToInt( "" ) ); 27 } 28 29 ///30 ///转换为整型,无效值 31 /// 32 [TestMethod] 33 public void TestToInt_Invalid() { 34 Assert.AreEqual( 0, Util.Conv.ToInt( "1A" ) ); 35 } 36 37 ///38 ///转换为整型,有效值 39 /// 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 ///51 ///转换为可空整型,值为null 52 /// 53 [TestMethod] 54 public void TestToIntOrNull_Null() { 55 Assert.IsNull( Util.Conv.ToIntOrNull( null ) ); 56 } 57 58 ///59 ///转换为可空整型,值为空字符串 60 /// 61 [TestMethod] 62 public void TestToIntOrNull_Empty() { 63 Assert.IsNull( Util.Conv.ToIntOrNull( "" ) ); 64 } 65 66 ///67 ///转换为可空整型,无效值 68 /// 69 [TestMethod] 70 public void TestToIntOrNull_Invalid() { 71 Assert.IsNull( Util.Conv.ToIntOrNull( "1A" ) ); 72 } 73 74 ///75 ///转换为可空整型,值为0 76 /// 77 [TestMethod] 78 public void TestToIntOrNull_0() { 79 Assert.AreEqual( 0, Util.Conv.ToIntOrNull( "0" ) ); 80 } 81 82 ///83 ///转换为可空整型,有效值 84 /// 85 [TestMethod] 86 public void TestToIntOrNull() { 87 Assert.AreEqual( 1, Util.Conv.ToIntOrNull( "1" ) ); 88 } 89 90 #endregion 91 92 #region ToDouble(转换为双精度浮点数) 93 94 ///95 ///转换为双精度浮点数,值为null 96 /// 97 [TestMethod] 98 public void TestToDouble_Null() { 99 Assert.AreEqual( 0, Util.Conv.ToDouble( null ) );100 }101 102 ///103 ///转换为双精度浮点数,值为空字符串104 /// 105 [TestMethod]106 public void TestToDouble_Empty() {107 Assert.AreEqual( 0, Util.Conv.ToDouble( "" ) );108 }109 110 ///111 ///转换为双精度浮点数,无效值112 /// 113 [TestMethod]114 public void TestToDouble_Invalid() {115 Assert.AreEqual( 0, Util.Conv.ToDouble( "1A" ) );116 }117 118 ///119 ///转换为双精度浮点数,有效值120 /// 121 [TestMethod]122 public void TestToDouble() {123 Assert.AreEqual( 1.2, Util.Conv.ToDouble( "1.2" ) );124 }125 126 ///127 /// 转换为双精度浮点数,指定2位小数位数128 /// 129 [TestMethod()]130 public void TestToDouble_DigitsIs2() {131 Assert.AreEqual( 12.36, Util.Conv.ToDouble( "12.355", 2 ) );132 }133 134 #endregion135 136 #region ToDoubleOrNull(转换为可空双精度浮点数)137 138 ///139 ///转换为可空双精度浮点数,值为null140 /// 141 [TestMethod]142 public void TestToDoubleOrNull_Null() {143 Assert.IsNull( Util.Conv.ToDoubleOrNull( null ) );144 }145 146 ///147 ///转换为可空双精度浮点数,值为空字符串148 /// 149 [TestMethod]150 public void TestToDoubleOrNull_Empty() {151 Assert.IsNull( Util.Conv.ToDoubleOrNull( "" ) );152 }153 154 ///155 ///转换为可空双精度浮点数,无效值156 /// 157 [TestMethod]158 public void TestToDoubleOrNull_Invalid() {159 Assert.IsNull( Util.Conv.ToDoubleOrNull( "1A" ) );160 }161 162 ///163 ///转换为可空双精度浮点数,值为0164 /// 165 [TestMethod]166 public void TestToDoubleOrNull_0() {167 Assert.AreEqual( 0, Util.Conv.ToDoubleOrNull( "0" ) );168 }169 170 ///171 ///转换为可空双精度浮点数,有效值172 /// 173 [TestMethod]174 public void TestToDoubleOrNull() {175 Assert.AreEqual( 1.2, Util.Conv.ToDoubleOrNull( "1.2" ) );176 }177 178 #endregion179 180 #region ToDecimal(转换为高精度浮点数)181 182 ///183 ///转换为高精度浮点数,值为null184 /// 185 [TestMethod]186 public void TestToDecimal_Null() {187 Assert.AreEqual( 0, Util.Conv.ToDecimal( null ) );188 }189 190 ///191 ///转换为高精度浮点数,值为空字符串192 /// 193 [TestMethod]194 public void TestToDecimal_Empty() {195 Assert.AreEqual( 0, Util.Conv.ToDecimal( "" ) );196 }197 198 ///199 ///转换为高精度浮点数,无效值200 /// 201 [TestMethod]202 public void TestToDecimal_Invalid() {203 Assert.AreEqual( 0, Util.Conv.ToDecimal( "1A" ) );204 }205 206 ///207 ///转换为高精度浮点数,有效值208 /// 209 [TestMethod]210 public void TestToDecimal() {211 Assert.AreEqual( 1.2M, Util.Conv.ToDecimal( "1.2" ) );212 }213 214 ///215 /// 转换为高精度浮点数,指定2位小数位数216 /// 217 [TestMethod()]218 public void TestToDecimal_DigitsIs2() {219 Assert.AreEqual( 12.24M, Util.Conv.ToDecimal( "12.235", 2 ) );220 }221 222 #endregion223 224 #region ToDecimalOrNull(转换为可空高精度浮点数)225 226 ///227 ///转换为可空高精度浮点数,值为null228 /// 229 [TestMethod]230 public void TestToDecimalOrNull_Null() {231 Assert.IsNull( Util.Conv.ToDecimalOrNull( null ) );232 }233 234 ///235 ///转换为可空高精度浮点数,值为空字符串236 /// 237 [TestMethod]238 public void TestToDecimalOrNull_Empty() {239 Assert.IsNull( Util.Conv.ToDecimalOrNull( "" ) );240 }241 242 ///243 ///转换为可空高精度浮点数,无效值244 /// 245 [TestMethod]246 public void TestToDecimalOrNull_Invalid() {247 Assert.IsNull( Util.Conv.ToDecimalOrNull( "1A" ) );248 }249 250 ///251 ///转换为可空高精度浮点数,无效值,指定2位小数位数252 /// 253 [TestMethod]254 public void TestToDecimalOrNull_Invalid_DigitsIs2() {255 Assert.IsNull( Util.Conv.ToDecimalOrNull( "1A", 2 ) );256 }257 258 ///259 ///转换为可空高精度浮点数,值为0260 /// 261 [TestMethod]262 public void TestToDecimalOrNull_0() {263 Assert.AreEqual( 0, Util.Conv.ToDecimalOrNull( "0" ) );264 }265 266 ///267 ///转换为可空高精度浮点数,有效值268 /// 269 [TestMethod]270 public void TestToDecimalOrNull() {271 Assert.AreEqual( 1.2M, Util.Conv.ToDecimalOrNull( "1.2" ) );272 }273 274 ///275 /// 转换为可空高精度浮点数,指定2位小数位数276 /// 277 [TestMethod()]278 public void ToDecimalOrNull_DigitsIs2() {279 Assert.AreEqual( 12.24M, Util.Conv.ToDecimalOrNull( "12.235", 2 ) );280 }281 282 #endregion283 284 #region ToGuid(转换为Guid)285 286 ///287 ///转换为Guid,值为null288 /// 289 [TestMethod]290 public void TestToGuid_Null() {291 Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( null ) );292 }293 294 ///295 ///转换为Guid,值为空字符串296 /// 297 [TestMethod]298 public void TestToGuid_Empty() {299 Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( "" ) );300 }301 302 ///303 ///转换为Guid,无效值304 /// 305 [TestMethod]306 public void TestToGuid_Invalid() {307 Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( "1A" ) );308 }309 310 ///311 ///转换为Guid,有效值312 /// 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 #endregion319 320 #region ToGuidOrNull(转换为可空Guid)321 322 ///323 ///转换为可空Guid,值为null324 /// 325 [TestMethod]326 public void TestToGuidOrNull_Null() {327 Assert.IsNull( Util.Conv.ToGuidOrNull( null ) );328 }329 330 ///331 ///转换为可空Guid,值为空字符串332 /// 333 [TestMethod]334 public void TestToGuidOrNull_Empty() {335 Assert.IsNull( Util.Conv.ToGuidOrNull( "" ) );336 }337 338 ///339 ///转换为可空Guid,无效值340 /// 341 [TestMethod]342 public void TestToGuidOrNull_Invalid() {343 Assert.IsNull( Util.Conv.ToGuidOrNull( "1A" ) );344 }345 346 ///347 ///转换为可空Guid,有效值348 /// 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 #endregion355 356 #region ToGuidList(转换为Guid集合)357 358 ///359 /// 转换为Guid集合,验证空字符串360 /// 361 [TestMethod]362 public void TestToGuidList_Empty() {363 Assert.AreEqual( 0, Util.Conv.ToGuidList( "" ).Count );364 }365 366 ///367 /// 转换为Guid集合,验证最后为逗号368 /// 369 [TestMethod]370 public void TestToGuidList_LastIsComma() {371 Assert.AreEqual( 1, Util.Conv.ToGuidList( "83B0233C-A24F-49FD-8083-1337209EBC9A," ).Count );372 }373 374 ///375 /// 转换为Guid集合,验证中间包含逗号376 /// 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 ///386 /// 转换为Guid集合,仅1个Guid387 /// 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 ///396 /// 转换为Guid集合,2个Guid397 /// 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 #endregion407 408 #region ToDate(转换为日期)409 410 ///411 ///转换为日期,值为null412 /// 413 [TestMethod]414 public void TestToDate_Null() {415 Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( null ) );416 }417 418 ///419 ///转换为日期,值为空字符串420 /// 421 [TestMethod]422 public void TestToDate_Empty() {423 Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( "" ) );424 }425 426 ///427 ///转换为日期,无效值428 /// 429 [TestMethod]430 public void TestToDate_Invalid() {431 Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( "1A" ) );432 }433 434 ///435 ///转换为日期,有效值436 /// 437 [TestMethod]438 public void TestToDate() {439 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Util.Conv.ToDate( "2000-1-1" ) );440 }441 442 #endregion443 444 #region ToDateOrNull(转换为可空日期)445 446 ///447 ///转换为可空日期,值为null448 /// 449 [TestMethod]450 public void TestToDateOrNull_Null() {451 Assert.IsNull( Util.Conv.ToDateOrNull( null ) );452 }453 454 ///455 ///转换为可空日期,值为空字符串456 /// 457 [TestMethod]458 public void TestToDateOrNull_Empty() {459 Assert.IsNull( Util.Conv.ToDateOrNull( "" ) );460 }461 462 ///463 ///转换为可空日期,无效值464 /// 465 [TestMethod]466 public void TestToDateOrNull_Invalid() {467 Assert.IsNull( Util.Conv.ToDateOrNull( "1A" ) );468 }469 470 ///471 ///转换为可空日期,有效值472 /// 473 [TestMethod]474 public void TestToDateOrNull() {475 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Util.Conv.ToDateOrNull( "2000-1-1" ) );476 }477 478 #endregion479 480 #region ToBool(转换为布尔值)481 482 ///483 ///转换为布尔值,值为null484 /// 485 [TestMethod]486 public void TestToBool_Null() {487 Assert.AreEqual( false, Util.Conv.ToBool( null ) );488 }489 490 ///491 ///转换为布尔值,值为空字符串492 /// 493 [TestMethod]494 public void TestToBool_Empty() {495 Assert.AreEqual( false, Util.Conv.ToBool( "" ) );496 }497 498 ///499 ///转换为布尔值,无效值500 /// 501 [TestMethod]502 public void TestToBool_Invalid() {503 Assert.AreEqual( false, Util.Conv.ToBool( "1A" ) );504 }505 506 ///507 ///转换为布尔值,值为False508 /// 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 ///520 ///转换为布尔值,值为True521 /// 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 #endregion533 534 #region ToBoolOrNull(转换为可空布尔值)535 536 ///537 ///转换为可空布尔值,值为null538 /// 539 [TestMethod]540 public void TestToBoolOrNull_Null() {541 Assert.IsNull( Util.Conv.ToBoolOrNull( null ) );542 }543 544 ///545 ///转换为可空布尔值,值为空字符串546 /// 547 [TestMethod]548 public void TestToBoolOrNull_Empty() {549 Assert.IsNull( Util.Conv.ToBoolOrNull( "" ) );550 }551 552 ///553 ///转换为可空布尔值,无效值554 /// 555 [TestMethod]556 public void TestToBoolOrNull_Invalid() {557 Assert.IsNull( Util.Conv.ToBoolOrNull( "1A" ) );558 }559 560 ///561 ///转换为布尔值,值为False562 /// 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 ///574 ///转换为布尔值,值为True575 /// 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 #endregion587 588 #region ToString(转换为字符串)589 590 ///591 ///转换为字符串,值为null592 /// 593 [TestMethod]594 public void TestToString_Null() {595 Assert.AreEqual( string.Empty, Util.Conv.ToString( null ) );596 }597 598 ///599 ///转换为字符串,值为空字符串600 /// 601 [TestMethod]602 public void TestToString_Empty() {603 Assert.AreEqual( string.Empty, Util.Conv.ToString( " " ) );604 }605 606 ///607 ///转换为字符串,有效值608 /// 609 [TestMethod]610 public void TestToString() {611 Assert.AreEqual( "1", Util.Conv.ToString( 1 ) );612 }613 614 #endregion615 616 #region To(通用泛型转换)617 618 #region 目标为int619 620 ///621 ///通用泛型转换,目标为整数,值为null622 /// 623 [TestMethod]624 public void TestTo_Int_Null() {625 Assert.AreEqual( 0, Conv.To ( null ) );626 }627 628 ///629 ///通用泛型转换,目标为整数,值为空字符串630 /// 631 [TestMethod]632 public void TestTo_Int_Empty() {633 Assert.AreEqual( 0, Conv.To ( "" ) );634 }635 636 ///637 ///通用泛型转换,目标为整数,无效值638 /// 639 [TestMethod]640 public void TestTo_Int_Invalid() {641 Assert.AreEqual( 0, Conv.To ( "1A" ) );642 }643 644 ///645 ///通用泛型转换,目标为整数,有效值646 /// 647 [TestMethod]648 public void TestTo_Int() {649 Assert.AreEqual( 1, Conv.To ( "1" ) );650 }651 652 ///653 ///通用泛型转换,目标为可空整数,无效值654 /// 655 [TestMethod]656 public void TestTo_IntOrNull_Invalid() {657 Assert.IsNull( Conv.To( "1A" ) );658 }659 660 /// 661 ///通用泛型转换,目标为可空整数,有效值662 /// 663 [TestMethod]664 public void TestTo_IntOrNull() {665 Assert.AreEqual( 1, Conv.To( "1" ) );666 }667 668 #endregion669 670 #region 目标为Guid671 672 /// 673 ///通用泛型转换,目标为Guid,无效值674 /// 675 [TestMethod]676 public void TestTo_Guid_Invalid() {677 Assert.AreEqual( Guid.Empty, Conv.To( "1A" ) );678 }679 680 /// 681 ///通用泛型转换,目标为Guid,有效值682 /// 683 [TestMethod]684 public void TestTo_Guid() {685 Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ),686 Conv.To( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );687 }688 689 /// 690 ///通用泛型转换,目标为可空Guid,无效值691 /// 692 [TestMethod]693 public void TestTo_GuidOrNull_Invalid() {694 Assert.IsNull( Conv.To( "1A" ) );695 }696 697 /// 698 ///通用泛型转换,目标为可空Guid,有效值699 /// 700 [TestMethod]701 public void TestTo_GuidOrNull() {702 Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ),703 Conv.To( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );704 }705 706 #endregion707 708 #region 目标为string709 710 /// 711 ///通用泛型转换,目标为string,有效值712 /// 713 [TestMethod]714 public void TestTo_String() {715 Assert.AreEqual( "123", Conv.To( 123 ) );716 }717 718 #endregion719 720 #region 目标为double721 722 /// 723 ///通用泛型转换,目标为double,无效值724 /// 725 [TestMethod]726 public void TestTo_Double_Invalid() {727 Assert.AreEqual( 0, Conv.To( "1A" ) );728 }729 730 /// 731 ///通用泛型转换,目标为double,有效值732 /// 733 [TestMethod]734 public void TestTo_Double() {735 Assert.AreEqual( 12.5, Conv.To( "12.5" ) );736 }737 738 /// 739 ///通用泛型转换,目标为可空double,无效值740 /// 741 [TestMethod]742 public void TestTo_DoubleOrNull_Invalid() {743 Assert.IsNull( Conv.To( "1A" ) );744 }745 746 /// 747 ///通用泛型转换,目标为可空double,有效值748 /// 749 [TestMethod]750 public void TestTo_DoubleOrNull() {751 Assert.AreEqual( 12.5, Conv.To( "12.5" ) );752 }753 754 #endregion755 756 #region 目标为decimal757 758 /// 759 ///通用泛型转换,目标为decimal,无效值760 /// 761 [TestMethod]762 public void TestTo_Decimal_Invalid() {763 Assert.AreEqual( 0, Conv.To( "1A" ) );764 }765 766 /// 767 ///通用泛型转换,目标为decimal,有效值768 /// 769 [TestMethod]770 public void TestTo_Decimal() {771 Assert.AreEqual( 12.5M, Conv.To( "12.5" ) );772 }773 774 /// 775 ///通用泛型转换,目标为可空decimal,无效值776 /// 777 [TestMethod]778 public void TestTo_DecimalOrNull_Invalid() {779 Assert.IsNull( Conv.To( "1A" ) );780 }781 782 /// 783 ///通用泛型转换,目标为可空decimal,有效值784 /// 785 [TestMethod]786 public void TestTo_DecimalOrNull() {787 Assert.AreEqual( 12.5M, Conv.To( "12.5" ) );788 }789 790 #endregion791 792 #region 目标为bool793 794 /// 795 ///通用泛型转换,目标为bool,无效值796 /// 797 [TestMethod]798 public void TestTo_Bool_Invalid() {799 Assert.AreEqual( false, Conv.To( "1A" ) );800 }801 802 /// 803 ///通用泛型转换,目标为bool,有效值804 /// 805 [TestMethod]806 public void TestTo_Bool() {807 Assert.AreEqual( true, Conv.To( 1 ) );808 }809 810 /// 811 ///通用泛型转换,目标为可空bool,无效值812 /// 813 [TestMethod]814 public void TestTo_BoolOrNull_Invalid() {815 Assert.IsNull( Conv.To( "1A" ) );816 }817 818 /// 819 ///通用泛型转换,目标为可空bool,有效值820 /// 821 [TestMethod]822 public void TestTo_BoolOrNull() {823 Assert.AreEqual( true, Conv.To( "true" ) );824 }825 826 #endregion827 828 #region 目标为DateTime829 830 /// 831 ///通用泛型转换,目标为DateTime,无效值832 /// 833 [TestMethod]834 public void TestTo_DateTime_Invalid() {835 Assert.AreEqual( DateTime.MinValue, Conv.To( "1A" ) );836 }837 838 /// 839 ///通用泛型转换,目标为DateTime,有效值840 /// 841 [TestMethod]842 public void TestTo_DateTime() {843 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Conv.To( "2000-1-1" ) );844 }845 846 /// 847 ///通用泛型转换,目标为可空DateTime,无效值848 /// 849 [TestMethod]850 public void TestTo_DateTimeOrNull_Invalid() {851 Assert.IsNull( Conv.To( "1A" ) );852 }853 854 /// 855 ///通用泛型转换,目标为可空DateTime,有效值856 /// 857 [TestMethod]858 public void TestTo_DateTimeOrNull() {859 Assert.AreEqual( new DateTime( 2000, 1, 1 ), Conv.To( "2000-1-1" ) );860 }861 862 #endregion863 864 #endregion865 }866 }
Conv类代码如下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 5 namespace Util { 6 ///7 /// 类型转换 8 /// 9 public static class Conv { 10 11 #region 数值转换 12 13 ///14 /// 转换为整型 15 /// 16 /// 数据 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 ///33 /// 转换为可空整型 34 /// 35 /// 数据 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 ///47 /// 转换为双精度浮点数 48 /// 49 /// 数据 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 ///58 /// 转换为双精度浮点数,并按指定的小数位4舍5入 59 /// 60 /// 数据 61 /// 小数位数 62 public static double ToDouble( object data, int digits ) { 63 return Math.Round( ToDouble( data ), digits ); 64 } 65 66 ///67 /// 转换为可空双精度浮点数 68 /// 69 /// 数据 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 ///81 /// 转换为高精度浮点数 82 /// 83 /// 数据 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 ///92 /// 转换为高精度浮点数,并按指定的小数位4舍5入 93 /// 94 /// 数据 95 /// 小数位数 96 public static decimal ToDecimal( object data, int digits ) { 97 return Math.Round( ToDecimal( data ), digits ); 98 } 99 100 ///101 /// 转换为可空高精度浮点数102 /// 103 /// 数据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 ///115 /// 转换为可空高精度浮点数,并按指定的小数位4舍5入116 /// 117 /// 数据118 /// 小数位数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 #endregion127 128 #region Guid转换129 130 ///131 /// 转换为Guid132 /// 133 /// 数据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 ///142 /// 转换为可空Guid143 /// 144 /// 数据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 ///156 /// 转换为Guid集合157 /// 158 /// guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A159 public static ListToGuidList( string guid ) {160 var listGuid = new List ();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 #endregion169 170 #region 日期转换171 172 /// 173 /// 转换为日期174 /// 175 /// 数据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 ///184 /// 转换为可空日期185 /// 186 /// 数据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 #endregion198 199 #region 布尔转换200 201 ///202 /// 转换为布尔值203 /// 204 /// 数据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 ///216 /// 获取布尔值217 /// 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 ///238 /// 转换为可空布尔值239 /// 240 /// 数据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 #endregion255 256 #region 字符串转换257 258 ///259 /// 转换为字符串260 /// 261 /// 数据262 public static string ToString( object data ) {263 return data == null ? string.Empty : data.ToString().Trim();264 }265 266 #endregion267 268 #region 通用转换269 270 ///271 /// 泛型转换272 /// 273 ///目标类型 274 /// 数据275 public static T To( 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 #endregion292 }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/
下载地址: http://files.cnblogs.com/xiadao521/Util.2014.11.12.1.rar