前一篇文章 測試驅動開發實踐-入門篇 我們我們講了一些基本的測試驅動開發流程:
1。寫單元測試使他亮紅燈
2。寫代碼使測試變成綠燈
3。重構代碼
接下來我們需要開始重構了,大家有可能會問,為什麼需要重構,什麼時候開始重構。
對與為什麼需要重構,其實就是為了使代碼結構清晰,去除一些重復的代碼,比如我們執行sql語句操作,我們起初這樣寫
Code
1private connStr="server=.;database=TestDB;uid=sa;pwd=123"
2public int Add(string loginName)
3{
4 int count = 0;
5 using (SqlConnection conn = new SqlConnection(connStr))
6 {
7 conn.Open();
8 SqlCommand cmd = new SqlCommand("insert(loginName) value('" + loginName + "')", conn);
9 count = cmd.ExecuteNonQuery();
10 cmd.Dispose();
11 conn.Close();
12 }
13 return count;
14}
15
16public int Delete(string loginName)
17{
18 int count = 0;
19 using (SqlConnection conn = new SqlConnection(connStr))
20 {
21 conn.Open();
22 SqlCommand cmd = new SqlCommand("delete from LoginUsers where loginName='" + loginName + "'", conn);
23 count = cmd.ExecuteNonQuery();
24 cmd.Dispose();
25 conn.Close();
26 }
27 return count;
28}
我們發現這裡除了sql語句不一樣之外,其他都是一樣的,那我們就可以這樣重構
Code
1private int ExecuteSql(string sql)
2{
3 int count = 0;
4 using (SqlConnection conn = new SqlConnection(connStr))
5 {
6 conn.Open();
7 SqlCommand cmd = new SqlCommand(sql, conn);
8 count = cmd.ExecuteNonQuery();
9 cmd.Dispose();
10 conn.Close();
11 }
12 return count;
13}
14public int Add(string loginName)
15{
16 return ExecuteSql("insert(loginName) value('" + loginName + "')");
17}
18public int Delete(string loginName)
19{
20 return ExecuteSql("delete from LoginUsers where loginName='" + loginName + "'");
21}
這樣重構完之後,代碼是不是清晰了很多呢?
那什麼時候又開始重構呢,我們在覺得代碼重復性太大了,層次結構混亂了等等(就是傳說中的有壞味道的代碼)都可以重構,有測試在,還需要怕代碼改錯嗎?
接下來我們就接著前一篇文章的用例按照 設計模式原則進行重構(單一職責原則,接口隔離原則,依賴倒置原則等)
那麼我們回頭看一下這個登陸的方法裡,包含了驗證和數據操作的代碼,根據單一職責原則,我們需要把他們放在不同的類中,就有了如下代碼
1public interface IEmployeeService
2{
3 bool Login(string loginName, string password);
4 bool ValidateLoginName(string loginName);
5}
在上面代碼中我們定義一個服務層接口,又把驗證單獨拿出來做了一個方法
下面是對服務層的實現
Code
1public class EmployeeService : IEmployeeService
2{
3 private IEmployeeDataAccess _empDAO;
4
5 public EmployeeService(IEmployeeDataAccess empDAO)
6 {
7
8 _empDAO = empDAO;
9 }
10
11 public bool ValidateLoginName(string loginName)
12 {
13 return !string.IsNullOrEmpty(loginName);
14 }
15
16 /**//// <summary>
17 /// 員工登陸
18 /// </summary>
19 /// <param name="loginName">登陸名</param>
20 /// <param name="password">密碼</param>
21 /// <returns></returns>
22 public bool Login(string loginName, string password)
23 {
24 if (ValidateLoginName(loginName))
25 {
26 if (_empDAO.GetCount(loginName, password) > 0)
27 {
28 return true;
29 }
30 }
31
32 return false;
33 }
34}
上面服務層我們可以看出,我們是調用了數據層接口的實現,而不是直接調用數據層的操作,這樣的使服務層和數據層的關系進行解偶,服務層不是直接依賴於數據層,而是依賴於數據層接口,
這樣有什麼好處呢?
比如我們現在數據層的實現是和sqlserver進行交互的,下次要與xml進行交互了,那怎麼辦呢,那我們只要實現一個與xml數據交互的數據層就可以了,其他代碼也就不用修改了。
其實模式就是為了 變化 而准備的,假如項目真要交付了,什麼事都沒有了,我們還搞什麼模式呢,你覺得呢?
這裡有點跑題了,接下來我們再回到主題,數據接口的定義如下
1public interface IEmployeeDataAccess
2{
3 int GetCount(string loginName, string password);
4}
數據層我這裡就形式一下了
Code
1public class EmployeeAccess : IEmployeeDataAccess
2{
3 IEmployeeDataAccess 成員#region IEmployeeDataAccess 成員
4
5 public int GetCount(string loginName, string password)
6 {
7 throw new NotImplementedException();
8 }
9
10 #endregion
11}
最後我們還需要修改我們的測試用例,使他繼續運行通過,最後的測試用例如下:
Code
1[TestFixture]
2public class EmployeeServiceTest
3{
4 private IEmployeeService empService;
5
6 [TestFixtureSetUp]
7 public void Init()
8 {
9 var da = new Mock<IEmployeeDataAccess>();
10 da.Setup(dd => dd.GetCount(It.Is<string>(s => s == "admin"), It.Is<string>(s => s == "pwd"))).Returns(1);
11 empService = new EmployeeService(da.Object);
12 }
13
14 [Test]
15 public void LoginTest()
16 {
17 Assert.IsTrue(empService.Login("admin", "pwd"), "登陸失敗");
18 }
19
20 [Test]
21 public void ValidateNullLoginName()
22 {
23 Assert.IsTrue(!empService.ValidateLoginName(null), "用戶為null驗證測試失敗");
24 }
25
26 [Test]
27 public void ValidateEmptyLoginName()
28 {
29 Assert.IsTrue(!empService.ValidateLoginName(""), "用戶為Empty驗證測試失敗");
30 }
31}
這裡重構就到這裡了,這裡服務層測試使用到了moq的mock框架可以在 http://code.google.com/p/moq 下到,所以這裡用了mock模擬數據層進行了測試,這個框架對於分層開發測試非常好,
在數據層沒有寫完的時候,我們就可以模擬數據層提供數據,直接對服務層進行測試。