隔离框架介绍
使用隔离框架的好处
- 可以把开发人员从编写重复代码,进行断言或模拟对象交互中解放出来
- 对于手工编写伪对象实现复杂接口时,不仅花费时间长,写起来还费劲
使用隔离框架的好处
- 动态伪对象就是在运行时创建的任何存根(桩)或者模拟对象。
- 使用动态伪对象,就不用手工编写代码实现接口或者派生类了。
用动态伪对象替换手工伪对象
被测类
public class LogAnalyzer
{
private ILogger _logger;
public LogAnalyzer(ILogger logger)
{
_logger = logger;
}
public int MinNameLength { get; set; }
public void Analyze(string filename)
{
if (filename.Length<MinNameLength)
{
_logger.LogError(string.Format("Filename too short: {0}",filename));
}
}
}
public interface IFileNameRules
{
bool IsValidLogFileName(string fileName);
}
public interface ILogger
{
void LogError(string message);
}
public interface IWebService
{
void Write(string message);
void Write(ErrorInfo message);
}
手工伪对象测试
//使用手工伪对象进行测试
[Test]
public void Analyze_TooShortFileName_CallLogger()
{
//下面这句后面会被隔离框架替换掉
FakeLogger logger=new FakeLogger();
LogAnalyzer analyzer=new LogAnalyzer(logger);
analyzer.MinNameLength = 6;
analyzer.Analyze("a.txt");
//下面这句后面会被隔离框架替换掉
StringAssert.Contains("too short", logger.LastError);
}
class FakeLogger : ILogger
{
public string LastError;
public void LogError(string message)
{
LastError = message;
}
}
使用NSubstitute伪造对象
//使用NSubstitute生成的动态伪对象来测试
[Test]
public void Analyze_TooShortFileName_UseNSub_CallLogger()
{
ILogger logger = Substitute.For<ILogger>();
LogAnalyzer analyzer=new LogAnalyzer(logger);
analyzer.MinNameLength = 6;
analyzer.Analyze("a.txt");
//使用NSub的API设置预期字符串,Received()这个方法在什么对象上调用,就会返回和这个对象同样类型的对象,但实际上是在声明断言对象。
//如果不加Received(),伪对象就会认为这个调用是产品代码发出的。
//使用Received(),就是在询问它后面的这个LogError是否调用过。
logger.Received().LogError("Filename too short: a.txt");
}
使用隔离框架也是符合“准备-执行-断言”这个结构要求
- 首先准备伪对象,然后执行要测试的功能,最后在测试结尾进行断言。
- 使用准备-执行-断言模式编写的测试代码具有良好的可读性。
使用隔离框架创建模拟对象和桩
从伪对象返回一个值(模拟值)
//模拟值:使用动态伪对象生成一个桩,提供被测试系统需要的值
[Test]
public void Returns_ByDefault_WorksForHardCodedArgument()
{
IFileNameRules fakeRules = Substitute.For<IFileNameRules>();
//强制方法调用时返回一个假值
fakeRules.IsValidLogFileName("strict.txt").Returns(true);
//因为调用了“fakeRules.IsValidLogFileName("strict.txt")”所以根据框架设置,返回了true
Assert.IsTrue(fakeRules.IsValidLogFileName("strict.txt"));
}
使用参数匹配器(argument matcher)
使用参数匹配器就可以无论参数是什么,方法总是返回一个假值,这样维护起来会非常容易。
//使用参数匹配器:不用管参数是什么,方法都返回一个假值
[Test]
public void Returns_ByDefault_WorksForHardCodedArgument2()
{
IFileNameRules fakeRules = Substitute.For<IFileNameRules>();
//这个Arg.Any<string>()就是参数匹配器,只要是string都会返回一个假值(例子里是true)
fakeRules.IsValidLogFileName(Arg.Any<string>()).Returns(true);
Assert.IsTrue(fakeRules.IsValidLogFileName("anything.txt"));
}
使用隔离框架模拟异常
//使用NSub模拟异常
[Test]
public void Returns_ArgAny_Throws()
{
IFileNameRules fakeRules = Substitute.For<IFileNameRules>();
//When方法必须使用Lambda表达式,其中x代表要改变行为的伪对象,context包含调用的参数值
fakeRules.When(x => x.IsValidLogFileName(Arg.Any<string>()))
.Do(context=>{throw new Exception("fake exception");});
Assert.Throws<Exception>(()=>fakeRules.IsValidLogFileName("anything"));
}