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