前一篇文章
测试驱动开发实践-入门篇 我们我们讲了一些基本的测试驱动开发流程:
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模拟数据层进行了测试,这个框架对于分层开发测试非常好,
在数据层没有写完的时候,我们就可以模拟数据层提供数据,直接对服务层进行测试。