测试驱动开发实践——重构篇

前一篇文章测试驱动开发实践-入门篇》我们我们讲了一些基本的测试驱动开发流程:

  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语句不一样之外,其他都是一样的,那我们就可以这样重构:

 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}

  在上面代码中我们定义一个服务层接口,又把验证单独拿出来做了一个方法。

  下面是对服务层的实现:

 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    }

上面服务层我们可以看出,我们是调用了数据层接口的实现,而不是直接调用数据层的操作,这样的使服务层和数据层的关系进行解偶,服务层不是直接依赖于数据层,而是依赖于数据层接口,这样有什么好处呢?

  比如我们现在数据层的实现是和sqlserver进行交互的,下次要与xml进行交互了,那怎么办呢,那我们只要实现一个与xml数据交互的数据层就可以了,其他代码也就不用修改了。

  其实模式就是为了 变化 而准备的,假如项目真要交付了,什么事都没有了,我们还搞什么模式呢,你觉得呢?

  这里有点跑题了,接下来我们再回到主题,数据接口的定义如下:

1public interface IEmployeeDataAccess
2{
3    int GetCount(string loginName, string password);
4}

  数据层我这里就形式一下了:

 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}

  最后我们还需要修改我们的测试用例,使他继续运行通过,最后的测试用例如下:

 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模拟数据层进行了测试,这个框架对于分层开发测试非常好,在数据层没有写完的时候,我们就可以模拟数据层提供数据,直接对服务层进行测试。

 

posted @ 2009-06-22 11:47  广陵散仙(www.cnblogs.com/junzhongxu/)  阅读(235)  评论(0编辑  收藏  举报