在教程开始之前,先回顾下单元测试应该有哪些特点?
运行快,自动化,结果稳定,隔离等等。
但是并不是所有的方法都能写出这样的单元测试。比如说有如下类:
public class Foo
{
private Log _log;
public Foo(Log log)
{
_log = log;
}
public void DoA()
{
//do something
_log.Write("Finish A");
}
}
这个类依赖于一个日志对象Log
public class Log
{
private readonly string _uri;
public Log(string uri)
{
_uri = uri;
}
public void Write(string text)
{
using (var stream = File.AppendText(_uri))
{
stream.Write(text);
}
}
public string Read()
{
if (!File.Exists(_uri))
{
return string.Empty;
}
using (var stream = File.OpenRead(_uri))
{
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
现在我们期望能够写一个单元测试,验证运行DoA
方法时,是否向日志写入了Finish A
那么问题来了,我们需要在每次运行单元测试时,要真正的读写
文件。那么这个单元测试能够做到运行快,结果稳定,隔离等等要求么?如果我们的例子中的日志系统换成数据库,网络请求会怎样呢?
如果这个时候我们能够伪造
一个日志系统,是否问题就能够解决了呢?
那么首先我们需要引入一个接口ILog
public interface ILog
{
void Write(string text);
string Read();
}
public class Log:ILog
{
//some code
}
相应的,待测类也要做一些改动
public class Foo
{
private ILog _log;
public Foo(ILog log)
{
_log = log;
}
public void DoA()
{
//do something
_log.Write("Finish A");
}
}
这样我们就可以制造一个FakeLog
将测试环境同真实的文件系统进行隔离。
好,我们看下FakeLog
的代码
public class FakeLog : ILog
{
public string Log { get; private set; }
public void Write(string text)
{
Log = text;
}
public string Read()
{
throw new NotImplementedException();
}
}
我们要测试的只有日志写入,因此Read
方法不需要实现,另外我们还需要一个简单的方式能够把写入的内容暴露
出来。所以这里还定义了一个属性Log
用于展示写入的日志内容。
下面是测试代码
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var fakeLog = new FakeLog();
var foo = new Foo(fakeLog);
foo.DoA();
Assert.AreEqual("Finish A",fakeLog.Log);
}
}
运行一下,绿色,很好。
那么就结束了么?不是的,单元测试还有一个特点是易于编写。我们这样子手工编写一个单元测试成本还是太高了。这么简单的东西是不是应该有一个隔离框架来做呢?
于是就到了我们的主角,Moq
。
先让大家体验一下使用Moq
的自动创建代码来替换我们手动创建的FakeLog
。
[TestMethod]
public void TestMethod2()
{
var fakeLog = new Mock<ILog>();
var foo = new Foo(fakeLog.Object);
foo.DoA();
fakeLog.Verify(log => log.Write("Finish A"));
}
就是这么简单,完全不用自己实现FakeLog
,并且用一个lambda表达式就完成了我们期望的验证。
运行测试,绿色,通过。
很棒是不是,这就是自动化的隔离框架的作用。
接下去就会带你进入Moq
的世界
本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/Moq%E5%9F%BA%E7%A1%80-%E4%B8%80.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 腾霄 (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。