前一篇文章
测试驱动开发实践-入门篇 我们我们讲了一些基本的测试驱动开发流程:
1。写单元测试使他亮红灯
2。写代码使测试变成绿灯
3。重构代码
接下来我们需要开始重构了,大家有可能会问,为什么需要重构,什么时候开始重构。
对与为什么需要重构,其实就是为了使代码结构清晰,去除一些重复的代码,比如我们执行sql语句操作,我们起初这样写

Code
1
private connStr="server=.;database=TestDB;uid=sa;pwd=123"
2
public 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
16
public 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
1
private 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
}
14
public int Add(string loginName)
15

{
16
return ExecuteSql("insert(loginName) value('" + loginName + "')");
17
}
18
public int Delete(string loginName)
19

{
20
return ExecuteSql("delete from LoginUsers where loginName='" + loginName + "'");
21
}
这样重构完之后,代码是不是清晰了很多呢?
那什么时候又开始重构呢,我们在觉得代码重复性太大了,层次结构混乱了等等(就是传说中的有坏味道的代码)都可以重构,有测试在,还需要怕代码改错吗?
接下来我们就接着前一篇文章的用例按照 设计模式原则进行重构(单一职责原则,接口隔离原则,依赖倒置原则等)
那么我们回头看一下这个登陆的方法里,包含了验证和数据操作的代码,根据单一职责原则,我们需要把他们放在不同的类中,就有了如下代码
1
public interface IEmployeeService
2

{
3
bool Login(string loginName, string password);
4
bool ValidateLoginName(string loginName);
5
} 在上面代码中我们定义一个服务层接口,又把验证单独拿出来做了一个方法
下面是对服务层的实现

Code
1
public 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数据交互的数据层就可以了,其他代码也就不用修改了。
其实模式就是为了 变化 而准备的,假如项目真要交付了,什么事都没有了,我们还搞什么模式呢,你觉得呢?
这里有点跑题了,接下来我们再回到主题,数据接口的定义如下
1
public interface IEmployeeDataAccess
2

{
3
int GetCount(string loginName, string password);
4
} 数据层我这里就形式一下了

Code
1
public 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]
2
public 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模拟数据层进行了测试,这个框架对于分层开发测试非常好,
在数据层没有写完的时候,我们就可以模拟数据层提供数据,直接对服务层进行测试。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构