在教程开始之前,先回顾下单元测试应该有哪些特点?


运行快,自动化,结果稳定,隔离等等。

但是并不是所有的方法都能写出这样的单元测试。比如说有如下类:

    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将测试环境同真实的文件系统进行隔离。

52334346609

好,我们看下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);
        }
    }

运行一下,绿色,很好。

52334493763

那么就结束了么?不是的,单元测试还有一个特点是易于编写。我们这样子手工编写一个单元测试成本还是太高了。这么简单的东西是不是应该有一个隔离框架来做呢?

于是就到了我们的主角,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表达式就完成了我们期望的验证。

运行测试,绿色,通过。

52334582447

很棒是不是,这就是自动化的隔离框架的作用。

接下去就会带你进入Moq的世界


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/Moq%E5%9F%BA%E7%A1%80-%E4%B8%80.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系