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