from:http://www.javashuo.com/article/p-tdorrvhk-cq.htmlhtml
上一篇介紹了數據類型轉換的一些狀況,能夠看出,若是不進行封裝,有可能致使比較混亂的代碼。本文經過TDD方式把數據類型轉換公共操做類開發出來,並提供源碼下載。git
咱們在 應用程序框架實戰十一:建立VS解決方案與程序集 一文已經建立了解決方案,包含一個類庫項目和一個單元測試項目。單元測試將使用.Net自帶的 MsTest,另外經過Resharper工具來觀察測試結果。程序員
首先考慮咱們指望的API長成什麼樣子。基於TDD開發,其中一個做用是幫助程序員設計指望的API,這稱爲意圖導向編程。編程
由於數據類型轉換是Convert,因此咱們先在單元測試項目中建立一個ConvertTest的類文件。 框架
類建立好之後,我先隨便建立一個方法Test,以迅速展開工做。測試的方法名Test,我是隨便起的,由於如今還不清楚API是什麼樣,我一會再回過頭來改。ide
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的一套開發流程,主要爲了照顧尚未體驗過單元測試的人,後面直接粘貼代碼,以免這樣低效的敘述方式。
單元測試代碼以下。
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,主要是想使用起來方便一點,而不是隻支持字符串參數,這可能致使裝箱和拆箱,從而形成一些性能損失,不過個人大多數項目在性能方面尚未這麼高的要求,因此這個損失對我來說無關痛癢。
還有些數據類型轉換,我沒有放進來,主要是我平時不多用到,當我用到時再增長