单元测试与解耦
1.标题是什么意思?
1.1什么是单元测试?
单元测试,目的是为了保证代码的质量;
1.2什么是解耦?
解耦,目的是为了方便单元测试。当然,另一个目的是为了保持程序的扩展性。
思想工具:为了同时达到单元测试与代码解耦(或者称为设计优良的OO代码),那么依赖注入的思想是必不可少的工具。
- 之所以说是思想,从设计的角度来说,这确实是需要思想上的超越;
- 之所以说是工具,是因为有许多工具可以实现这一思想,如Ninject,Unity。
简要如下图所示:
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.