鼎鼎知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2020.10.27单元测试前奏--解耦.md 9.0KB

通常一个低内聚高耦合的类具有单一职责,但不可避免地会存在依赖。比如依赖于文件系统的某个管理类,依赖于时间、依赖于某个线程、依赖于对外的请求,等等。在单元测试的时候,如何解除依赖呢?

如下FileHelper存在强依赖:

public class FileHelper
{
    public bool IsValidLogFileName(string fileName)
    {
        //这里存在强依赖
        FileExtensionManager  mgr = new FileExtensionManager();
        return mgr.IsValid(fileName);
    }
}

public class FileExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

如果依赖于接口呢?在FileExtensionManager基础之上抽象出一个接口。

public interface IExtensionManager
{
    bool IsValid(string fileName);
}

//实现接口之一
public class FileExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

//实现接口之二,测试时使用
public class StubExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

//改造FileHelper
public class FileHelper
{
    //这里虽然依赖于接口,但实现是具体的
   IExtensionManager mgr = new FileExtensionManager();
   return mgr.IsValid(fileName);
}

现在需求变成:当在生产环境下希望FileHelper依赖于FileExtensionManager,在单元测试时,FileHelper依赖于StubExtesnionManager,并且希望StubExtensionManger提供一个属性让外界可以控制执行结果。

//首先接口
public interface IExtensionManager
{
    bool IsValid(string fileName);
}

//生产环境下的实现类
public class FileExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

//单元测试下的实现类
public class StubExtensionManager : IExtensionManager
{
    //通过这个属性,让外界可以控制执行结果
    public bool ShouldExtensionBeValid{get;set;}

    public bool IsValid(string fileName)
    {
        return ShouldExtensionBeValid;
    }
}

//在FileHelper中提供两个构造函数的重载
public class FileHelper
{
    private IExtenionManager manager;

    //生产环境使用的构造函数
    public FileHelper()
    {
        manager = new FileExtentionManager();
    }

    //单元测试使用的构造函数
    public FileHelper(IExtensionManager mgr)
    {
        manager = mgr;
    }

    //需要被测试的方法
    public bool IsValidLogFileName(string fileName)
    {
        return manager.IsValid(fileName);
    }
}

//开始单元测试
[TestFixture]
public class FileHelperTests
{
    public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
    {
        //1、Arrange

        // 单元测试用的实现类
        StubExtensionManager myFakeManager = new StubExtensionManager();
        // 控制设置单元测试的结果
        myFakeManager.ShouldExtensionBeValid = true;

        FileHelper fileHelper = new FileHelper(myFakeManager);

        //2、Act
        bool result = fileHelper.IsValidLogFileName("example.ext");

        //3、Assert
        Assert.IsFalse(result, "...");
    }
}

以上通过重载构造函数,把单元测试的实现类注入。是否还有其它注入方式呢?通过属性。

//首先接口
public interface IExtensionManager
{
    bool IsValid(string fileName);
}

//生产环境下的实现类
public class FileExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

//单元测试下的实现类
public class StubExtensionManager : IExtensionManager
{
    //通过这个属性,让外界可以控制执行结果
    public bool ShouldExtensionBeValid{get;set;}

    public bool IsValid(string fileName)
    {
        return ShouldExtensionBeValid;
    }
}

public class FileHelper
{
    private IExtensionManager manager;

    public FileHelper
    {
        manager = new FileExtensionManager();
    }

    //为外界提供注入的可能
    public IExtensionManager ExtensionManager
    {
        get {return manager;}
        set {manager = value;}
    }

    //需要被测试的方法
    public bool IsValidLogFileName(string fileName)
    {
        return manager.IsValid(fileName);
    }
}

//开始单元测试
[TestFixture]
public class FileHelperTests
{
    public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
    {
        //1、Arrange

        // 单元测试用的实现类
        StubExtensionManager myFakeManager = new StubExtensionManager();
        // 控制设置单元测试的结果
        myFakeManager.ShouldExtensionBeValid = true;

        FileHelper fileHelper = new FileHelper();
        fileHelper.ExtensionManager = myFakeManager;

        //2、Act
        bool result = fileHelper.IsValidLogFileName("example.ext");

        //3、Assert
        Assert.IsFalse(result, "...");
    }
}

以上通过构造函数或属性把单元测试的实现类注入到FileHelper中。如果用工厂方法呢?用工厂产生实例,并且工厂可以让外界决定具体生成哪个实例。

//首先接口
public interface IExtensionManager
{
    bool IsValid(string fileName);
}

//生产环境下的实现类
public class FileExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

//单元测试下的实现类
public class StubExtensionManager : IExtensionManager
{
    //通过这个属性,让外界可以控制执行结果
    public bool ShouldExtensionBeValid{get;set;}

    public bool IsValid(string fileName)
    {
        return ShouldExtensionBeValid;
    }
}

//需要一个工厂类
public class ExtensionManagerFactory
{
    private IExtensionManager customeManager = null;

    public IExtensionManager Create()
    {
        if(customManager != null)
        {
            return customManager;
        }

        return new FileExtensionManager();
    }

    public void SetManager(IExtensionManager mgr)
    {
        customManager = mgr;
    }
}

public class FileHelper
{
    private IExtensionManager manager;

    public FileHelper
    {
        manager = ExtensionManagerFactory.Create();
    }

    //需要被测试的方法
    public bool IsValidLogFileName(string fileName)
    {
        return manager.IsValid(fileName);
    }
}

//开始单元测试
[TestFixture]
public class FileHelperTests
{
    public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
    {
        //1、Arrange

        // 单元测试用的实现类
        StubExtensionManager myFakeManager = new StubExtensionManager();
        // 控制设置单元测试的结果
        myFakeManager.ShouldExtensionBeValid = true;

        ExtensionManagerFactory.SetManager(myFakeManager);

        FileHelper fileHelper = new FileHelper();


        //2、Act
        bool result = fileHelper.IsValidLogFileName("example.ext");

        //3、Assert
        Assert.IsFalse(result, "...");
    }
}

以上通过构造函数、属性、工厂方法都可以把单元测试的实现类注入到FileHelper中。

有时,被测试基类中会存在虚方法,这种情况可以测试基类的派生类。

有这样一个基类

public class FileHelper
{
    public bool IsValidLogFileName(string fileName)
    {
        return GetManager().IsValid(fileName);
    }

    //基类方法提供虚方法,等待派生类实现
    protected virtual IExtensionManager GetManager()
    {
        return new FileExtensionManager();
    }
}

//首先接口
public interface IExtensionManager
{
    bool IsValid(string fileName);
}

//生产环境下的实现类
public class FileExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

//单元测试下的实现类
public class StubExtensionManager : IExtensionManager
{
    //通过这个属性,让外界可以控制执行结果
    public bool ShouldExtensionBeValid{get;set;}

    public bool IsValid(string fileName)
    {
        return ShouldExtensionBeValid;
    }
}

派生类

public class TestableFileHelper : FileHelper
{
    public IExtenionManager Manager{get;set;}

    protected override IExtensionManager GetManager()
    {
        return Mananger;
    }
}

单元测试

//开始单元测试
[TestFixture]
public class TestableFileHelperTests
{
    public void IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnFale()
    {
        //1、Arrange

        // 单元测试用的实现类
        StubExtensionManager myFakeManager = new StubExtensionManager();
        // 控制设置单元测试的结果
        myFakeManager.ShouldExtensionBeValid = true;

        ExtensionManagerFactory.SetManager(myFakeManager);

        TestableFileHelper fileHelper = new TestableFileHelper();
        fileHelper.Manager = myFakeManager;


        //2、Act
        bool result = fileHelper.IsValidLogFileName("example.ext");

        //3、Assert
        Assert.IsFalse(result, "...");
    }
}