1.什么是外部依赖
外部依赖是指在系统中代码与其交互的对象,而且无法对其做人为控制。
最常见的例子是文件系统、线程、内存和时间等,我们使用桩对象来处理外部依赖问题。
2.什么是桩对象
桩对象是对系统中现有依赖的一个替代品,可人为控制。
通过使用桩对象,无需涉及依赖项,即可直接对代码进行测试。
3.什么是重构
重构是指不影响已有功能而改变代码设计的一种行为
4.什么是接缝
接缝是指代码中可以插入不同功能(如桩对象类)的地方。
二、解除依赖抽象一个接口
namespaceLogAn.Interface { public interfaceIExtensionManager { bool IsValid(stringfileName); } }
实现接口的具体类
namespaceLogAn.Implement { public classFileExtensionManager:IExtensionManager { public bool IsValid(stringfileName) { if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException("No filename provided!"); } if (!fileName.EndsWith(".SLF")) { return false; } else{ return true; } } } }
编写一个实现该接口的桩对象类
无论文件的扩展类是什么,这个桩对象类永远返回true
public classStubExtensionManager:IExtensionManager { public bool IsValid(stringfileName) { return true; } }
编写被测方法
现有一个接口和两个实现该接口的类,但被测类还是直接调用“真对象”;
这个时候我们需要在代码中引入接缝,以便可以使用桩对象
在被测试类中注入桩对象的实现;
在构造函数级别上接收一个接口;
namespaceLogAn { public classLogAnalyzer { public bool IsValidLogFileName(stringfileName) { IExtensionManager mgr =newFileExtensionManager(); returnmgr.IsValid(fileName); } } }
1.重写LogAnalyzer.cs
namespaceLogAn { public classLogAnalyzer { privateIExtensionManager manager; /// <summary> ///在生产代码中新建对象 /// </summary> publicLogAnalyzer() { manager = newFileExtensionManager(); } /// <summary> ///定义可供测试调用的构造函数 /// </summary> /// <param name="mgr"></param> publicLogAnalyzer(IExtensionManager mgr) { manager =mgr; } public bool IsValidLogFileName(stringfileName) { returnmanager.IsValid(fileName); } } }
public classStubExtensionManager : IExtensionManager { public boolShouldExtensionBeValid; public bool IsValid(stringfileName) { returnShouldExtensionBeValid; } }
[TestFixture] public classLogAnalyzerTest { [Test] public voidIsValidFileName_validFileLowerCased_ReturnTrue() { StubExtensionManager myFakeManager = newStubExtensionManager(); myFakeManager.ShouldExtensionBeValid = true; LogAnalyzer analyzer = newLogAnalyzer(myFakeManager); bool result = analyzer.IsValidLogFileName("haha.slf"); Assert.IsTrue(result, "filename shoud be valid!"); } }
4.构造函数注入方式存在的问题
如果被测代码需要多个桩对象才能正常工作,就需要增加更多的构造函数,而造成很大的困扰,甚至降低代码的可读性和可维护性
5.何时使用构造函数注入方式
使用构造函数的方式,可以很好的告知API使用者:“这些参数是必须的,新建这个对象时必须传入所有参数”
如果想要这些依赖变成可选的,可以使用属性注入
四、在被测类中注入桩对象-属性注入1.重写LogAnalyzer.cs
namespaceLogAn { public classLogAnalyzer { privateIExtensionManager manager; /// <summary> ///在生产代码中新建对象 /// </summary> publicLogAnalyzer() { manager = newFileExtensionManager(); } /// <summary> ///允许通过属性设置依赖 /// </summary> /// <param name="mgr"></param> publicIExtensionManager ExtensionManager { get { returnmanager; } set { manager =value; } } public bool IsValidLogFileName(stringfileName) { returnmanager.IsValid(fileName); } } }
public classStubExtensionManager : IExtensionManager { public boolShouldExtensionBeValid; public bool IsValid(stringfileName) { returnShouldExtensionBeValid; } }
[TestFixture] public classLogAnalyzerTest { [Test] public voidIsValidFileName_validFileLowerCased_ReturnTrue() { StubExtensionManager myFakeManager = newStubExtensionManager(); myFakeManager.ShouldExtensionBeValid = true; LogAnalyzer analyzer = newLogAnalyzer(); analyzer.ExtensionManager =myFakeManager; bool result = analyzer.IsValidLogFileName("haha.slf"); Assert.IsTrue(result, "filename shoud be valid!"); } }
1.编写LogAnalyzer.cs
namespaceLogAn { public classLogAnalyzer { privateIExtensionManager manager; /// <summary> ///在生产代码中使用工厂 /// </summary> /// <param name="mgr"></param> publicLogAnalyzer() { manager =ExtensionManagerFactory.Create(); } public bool IsValidLogFileName(stringfileName) { returnmanager.IsValid(fileName); } } }
2.编写测试方法
[TestFixture] public classLogAnalyzerTest { [Test] public voidIsValidFileName_validFileLowerCased_ReturnTrue() { StubExtensionManager myFakeManager = newStubExtensionManager(); myFakeManager.ShouldExtensionBeValid = true; //把桩对象赋给工厂类 ExtensionManagerFactory.SetManager(myFakeManager); LogAnalyzer analyzer = newLogAnalyzer(); bool result = analyzer.IsValidLogFileName("haha.slf"); Assert.IsTrue(result, "filename shoud be valid!"); } }