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

不羁岁月 提交于 2020-04-19 04:27:06

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

  首先上环境

  • 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框架。

  处于笔者的水平限制,笔者先后尝试了 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 类型的。

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

  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?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!