[UWP] 如何作 简单的UnitTest with Stub(基于微软原生的MSTest + SimpleStub)

  近期应项目须要,笔者疯狂学习UWP项目测试的知识,确实学习到了很多关于测试方面的知识,等有时间会好好地记录下测试的学习成果。OK,回到主题,在笔者作项目测试的过程当中把本身遇到的坑都简单的记录了下来,而且写了个关于测试的Demo,传送门。下面简单地笔者的初入测试门槛的经验!ios

  首先上环境git

  • Windows 10 OS
  • Windows 10 SDK 10.0.15860
  • Visual Studio 2017
  • MSTest Framework 1.2.0
  • Etg.SimpleStubs 2.4.6

  使用到的测试框架:MSTest 提供的 UnitTestFramework + 微软 Microsoft BigPark Studios团队开发的 SimpleStub Mock框架。github

  处于笔者的水平限制,笔者前后尝试了 NUnitXUnit 等多款测试框架可是效果并非太好,其中 NUnit 彷佛对 UnitTestProject(单元测试应用)并不太友好(多半是笔者水平的问题,若是有Dalao能教下我NUnit与UWP的集成请私信),网上的dalao们使用 XUnit 多数是基于 ClassLibrary 的项目,而笔者尝试的时候发现系统没法直接调用 ClassLibrary 所书写的测试 ,须要加载 Runner ,有些小麻烦,不过应该也是能用的。最终,多番尝试后笔者敲定了使用 MSTest + SimpleStub这两款框架(都是来自微软,相信与UWP兼容性不错)。框架

  MSTest比较简单,上述的MSDN传送门对应的网页有详细的指导,而 SampleStub 则须要注意挺多的,笔者将本身在使用过程当中踩的一些可能比较常见的坑记录了下来,下面上笔者记录的小坑:函数

摘自笔者Demo项目的README:单元测试

  做为一个刚入测试的小菜鸡,做者在结合使用这两个框架的时候也遇到了不少坑,下面简单列举一些萌新们可能会踩的坑:学习

  1. UWP 新建一个UnitTest项目?

答:UWP 的单元测试项目优选 单元测试应用 类型的项目,即新建一个单元测试应用的项目,而且在其中书写本身的测试测试

  1. 为何在新建的UnitTestProject内新建测试文件不会在测试列表中显示?

答:测试文件目前使用的是注解机制,其中对做为TestFixture的测试类以及测试方法都是有访问权限要求的,即必须都为 public 的 ,而且测试方法 Method 必须为 public void 类型的。spa

  1. 测试经常使用的东西: Assert 类的诸多方法,Such as: AreEqual AreSame IsTruecode

  2. 如何在目前的Unit测试引入Mock框架?

答:目前的状况,做者尝试过许多框架,出于做者水平,这些框架(NUnit Template 不支持、XUnit 换成Library没法识别测试)在做者的UWP上表现都不太好。目前发现表现最好的是微软去年年底推出的 SimpleStub 框架,而后做者也推荐使用这个,由于做者我的认为这个是目前与 MSTest 兼容最好的Mock框架。

  1. SimpleStub 如何使用?

答:Nuget -> download SimpleStub nuget package 。而后环境就已经搭建好了,选择你的测试项目进行生成,此时框架会自动在编译期间在项目obj目录下生成一个 SimpleStub.generate.cs 的文件。利用这个文件生成一个 StubXxxx 的Mock对象进而实现测试,详细用法见样例代码( UnitTestProject1 内的 TestUserMagr.cs

Tips:

  • 这里补充说明下,若是项目生成后在obj目录下没找到 SimpleStub.generate.cs 文件,说明生成 SimpleStub.generate.cs 文件失败了,此时应该去查看Console(输出控制台)的输出内容,挖掘出错误的缘由,解决异常后从新生成下便可!
  • 同时须要多留意 SimpleStub 框架为咱们生成的 StubXxx Mock对象的一些方法构建,由于这与构建的Mock对象的 MockBehavior 息息相关,详细请见示例代码

  最后,上笔者Demo中测试的部分代码:

namespace UnitTestProject1
{
    [TestClass]
    public class TestUserMagr
    {
        // 待测试对象-> uMagr  Mock对象->uMagr.UDao(IUserDao)
        private UserMagr uMagr;        

        [TestInitialize]
        public void BeforeTestInitialize()
        {
            uMagr = new UserMagr();
        }

        
        [TestMethod]
        public void TestCallSequence()
        {

            // 新建mock对象
            var stub = new StubIUserDao(MockBehavior.Strict)
                .CreateUser((p2) => { return null == p2 ? 0 : 1; }, Times.Once)// 限定最多调用一次
                .CreateUser((p2) => { return null == p2 ? 0 : 1; }, Times.Twice)// 限定最多调用两次
                .CreateUser((p2) => { return null == p2 ? 0 : 1; }, Times.Forever);// 解除调用次数上限设置
                                                                                   // 若是不执行上述最后一行,那么stub对象只能执行CreateUser 两次(取决于最后一条的限定),执行超出次数会抛异常

            // 将Mock对象赋值给依赖对象,从而解除依赖对象对测试的干预
            uMagr.UDao = stub;
            // 从这开始执行测试 相似 EasyMock->replayAll(); 
            Assert.AreEqual(1, uMagr.CreateUser(new User() { Username = "123", Password = "123" }));
            Assert.AreEqual(1, uMagr.CreateUser(new User() { Username = "123", Password = "123" }));
            Assert.AreEqual(1, uMagr.CreateUser(new User() { Username = "123", Password = "123" }));
        }

        [TestMethod]
        public void TestMethod_WithReturnType_WithParameters()// 测试Mock对象的函数带参数的调用
        {
            // 设置一些初始变量
            int Expect = 1;
            string Username = null;
            string Password = null;
            // 用于判断是否修改的对象
            User user = new User() { Username = Username, Password = Password };
            // 开始构建Mock
            var stub = new StubIUserDao()
                .CreateUser((u) => { user = u; return Expect; });
            uMagr.UDao = stub;
            // replayAll()
            Assert.AreEqual(Expect, uMagr.CreateUser(new User() { Username = "test" }));
            Assert.AreEqual("test", user.Username);
        }

        [TestMethod]
        public void TestThatMethodStubCanBeOverwritten()// 测试Mock对象的方法是否支持重写
        {
            // 构建一个Mock对象
            var stub = new StubIUserDao()
                .CreateUser(u => 1);
            // 重写其调用的方法,若是须要重写则须要加上overwrite:true,不然将不会重写
            stub.CreateUser(u => 2, overwrite: true);
            uMagr.UDao = stub;
            // replayAll()
            Assert.AreEqual(2, uMagr.CreateUser(new User()));
        }

        [TestMethod]
        [ExpectedException(typeof(SimpleStubsException))]
        public void TestMethod_WithReturnType_WithParameters_DefaultBehavior_Strict()// 展现 MockBehavior为Strict下的不一样之处,于下面的method一块儿比对观察
        {
            // 构建一个不带函数定义的Mock对象(存根)
            var stub = new StubIUserDao(MockBehavior.Strict);
            uMagr.UDao = stub;
            // 调用方法的时候,Strict模式下会抛出异常,由于没有定义调用函数
            Assert.AreEqual(0, uMagr.CreateUser(null));
        }

        [TestMethod]
        public void TestMethod_Void_WithNoParameters_DefaultBehavior_Loose()
        {
            // 构建一个不带函数定义的Mock对象(存根),默认下MockBehavior为Loose
            var stub = new StubIUserDao();
            uMagr.UDao = stub;
            // 调用方法的时候,Loose模式下不会抛出异常,而且返回函数返回值的默认值 null | 默认基本类型值 | 
            Assert.AreEqual(0, uMagr.CreateUser(null));
        }       
    }
}

OK,就先记录到这!

附:感谢多位dalao超棒的回答:

  • In Github:

What should I do if I want to mock an object ? Order of Execution ?

  • In StackOverFlow:

What is the difference between MockBehavior.Loose and MockBehavior.Strict in SimpleStub?

相关文章
相关标签/搜索