单元测试与解耦

1.标题是什么意思?

1.1什么是单元测试?

单元测试,目的是为了保证代码的质量;

1.2什么是解耦?

解耦,目的是为了方便单元测试。当然,另一个目的是为了保持程序的扩展性

思想工具:为了同时达到单元测试与代码解耦(或者称为设计优良的OO代码),那么依赖注入的思想是必不可少的工具。

  • 之所以说是思想,从设计的角度来说,这确实是需要思想上的超越;
  • 之所以说是工具,是因为有许多工具可以实现这一思想,如Ninject,Unity。

简要如下图所示:

clip_image002

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.

原文地址:https://www.cnblogs.com/pengzhen/p/3812654.html