1.标题是什么意思?
1.1什么是单元测试?
单元测试,目的是为了保证代码的质量;
1.2什么是解耦?
解耦,目的是为了方便单元测试。当然,另一个目的是为了保持程序的扩展性。
思想工具:为了同时达到单元测试与代码解耦(或者称为设计优良的OO代码),那么依赖注入的思想是必不可少的工具。
- 之所以说是思想,从设计的角度来说,这确实是需要思想上的超越;
- 之所以说是工具,是因为有许多工具可以实现这一思想,如Ninject,Unity。
简要如下图所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuUTN3QTM1gjNyMTMwIzNy8CX2ADNxAjMvwlM0cTO0IzLcd2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
2.解除外部依赖及实践
外部依赖:配置文件、WS、数据库、IO等,可控性较差,是集成测试的接缝点。
为什么要解除外部依赖,对于一个函数来说,只关注某一功能(即SRP,除非你想把所有的事情在一个方法内做完,但这不是OO,也没有讨论的价值)。
2.1.耦合的代码
Eg:调用一个Web服务,最后发送邮件,但如果邮件服务挂了,剩余的逻辑就无法判断了,所以,邮件服务是一个外部依赖,要模拟,或者打桩。
代码清单1:——常规耦合的代码
public class WebService
{
public void KaoQinSign(string userName,string from, string to)
{
//check the argument
//validate
// do something logistic
MailHelper.SendMail("some content", from, to);
}
}
public class MailHelper
{
public static void SendMail(string content, string from, string to)
{
//...
}
}
对于上述的KaoQinSign方法的写法,常见,但不易测试,严格来说,在TDD开发中是不容出现的,根本原因是静态方法的存在,阻止了可测试性,当然,对于Wrapper模式就另当别论。
考虑一个问题:如果KaoQinSign执行到MailHelper.SendMail方法,但运行时邮件系统不知道出了什么问题,异常了。
我通常的做法:将焦点转移到了MailHelper.SendMail方法,修复之后,然后再回到KaoQinSign进行调试,如果MailHelper.SendMail有问题,继续往前。——随着方法调用的层次越来越深,焦点转移的次数越来越远,Bug率会很高。一般来说,调试的成功率和工作经验成反比。
2.2 接口注入
代码清单2:使用接口注入来解耦
public class WebService
{
public void KaoQinSign(IMail mail,string userName, string from, string to)
{
//check the argument
//validate
// do something logistic
mail.SendMail("some content", from, to);
}
}
public interface IMail
{
void SendMail(string content, string from, string to);
}
public class MailStub : IMail
{
public void SendMail(string content, string from, string to) { }
}
上述代码,解除了对外部邮件系统的依赖,使KaoQinSign具有可测试性,如果对模拟框架有所了解,那么使用Moq就可以轻松地模拟一个IMail接口,从而使代码开发和测试能够一路向前。
2.3 模拟与测试
代码清单3:使用模拟框架进行方法的测试
[TestFixture]
public class WebServiceTests
{
[Test]
public void Method1_When_Exception_Will_SendMail()
{
WebService ws = new WebService();
//模拟邮件服务
Moq.Mock<IMail> mockMail = new Moq.Mock<IMail>();
//Verifiable表示:将要验证SendMail是否被调用
mockMail.Setup(zw => zw.SendMail(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Verifiable();
ws.KaoQinSign(mockMail.Object,,"5299530", "One", "Other");
//验证是否被调用
mockMail.Verify();
}
}
代码清单3,模拟邮件服务意思是这个服务是假的,用来确保这个方法通过的。
总结
单元测试的目的——确保(专业点来说称为断言)某一分支能够正确地执行。
耦合的常见:
- 静态方法;
- 一个函数干了几百件事情;
- 一个函数内容有几百个流程。
而解除外部依赖是常用的OO编码方法。而上述的静态方法就是典型的,符合二八定律。
使用
- SRP确保函数功能的唯一性;
- 针对接口编程(IOC);
- 使编码具有可测试性(提取接口以及依赖注入)
才是TDD的最佳实践,同时,TDD是开发能够有效地横向覆盖(BFS式前进),而不需要使用DFS式地向前开发、调试,从而避免了DFS带来的大脑爆栈。~~~ come from hp.