zoukankan      html  css  js  c++  java
  • .net core 单元测试之 JustMock第二篇

    JustMock标记方法

    上篇文章在举例子的时候使用了returns的标记方法,JustMock还有很多标记方法:

    • CallOriginal
      跟Behaviors里的CallOriginal差不多意思,被调用时执行原始的方法和属性的实现。
    • DoNothing
      忽略对方法或者属性的调用。
    • DoInstead
      替换原来方法的调用,或者属性的设置。
    • MustBeCalled
      被标记的方法必须调用,否则会抛出异常。
    • Raise
      当我们需要测试Event是否执行时,我们可以使用Raise来引发Events。
      例子:
        public delegate void CustomEvent(string value);
    
        public interface IFoo
        {
            event CustomEvent CustomEvent;
        }
    
            //测试方法:
            [Test]
            public void ShouldInvokeMethodForACustomEventWhenRaised()
            {
                string expected = "ping";
                string actual = string.Empty;
    
                var foo = Mock.Create<IFoo>();
    
                foo.CustomEvent += delegate (string s)
                {
                    actual = s;
                };
    
                //引发事件,并传递参数
                Mock.Raise(() => foo.CustomEvent += null, expected);
    
                Assert.AreEqual(expected, actual);
            }
    
    • Raises
      Raises用在调用方法后出发事件。
      例子:
        //使用的接口
        public delegate void CustomEvent(string value, bool called);
    
        public delegate void EchoEvent(bool echoed);
    
        public delegate void ExecuteEvent();
    
        public interface IFoo
        {
            event CustomEvent CustomEvent;
            event EchoEvent EchoEvent;
            event ExecuteEvent ExecuteEvent;
    
            void RaiseMethod();
            string Echo(string arg);
            void Execute();
            void Execute(string arg);
        }
    

    当调用方法时触发:

        //测试方法
            [Test]
            public void ShouldRaiseCustomEventOnMethodCall()
            {
                string actual = string.Empty;
                bool isCalled = false;
    
                var foo = Mock.Create<IFoo>();
    
                foo.CustomEvent += (s, c) => { actual = s; isCalled = c; };
                Mock.Arrange(() => foo.RaiseMethod()).Raises(() => foo.CustomEvent += null, "ping", true);
    
                foo.RaiseMethod();//调用方法,触发事件
    
                Assert.AreEqual("ping", actual);
                Assert.IsTrue(isCalled);
            }
    

    另外还可以设置参数满足条件时调用的方法触发,设置延时触发。

    • Returns
      设置返回结果。
    • Throws
      当一个方法调用时,抛出异常。
      例子:
            [Test]
            public void ShouldThrowExceptionOnMethodCall()
            {
                // Arrange 
                var foo = Mock.Create<IFoo>();
    
                Mock.Arrange(() => foo.Execute(string.Empty)).Throws<ArgumentException>();
    
                Assert.Throws<ArgumentException>(() => foo.Execute(string.Empty));
            }
    
    • ThrowsAsync
      调用异步方法时抛出异常。(官方文档有这个标记,貌似使用21912071版本时没有这个标记了)
    • When
      当满足条件时执行某操作。
      例子:
        public interface IFoo
        {
            bool IsCalled();
            string Prop { get; set; }
        }
    
            [Test]
            public void IsCalled_ShouldReturnTrue_WithMockedDependencies()
            {
                var foo = Mock.Create<IFoo>();
    
                Mock.Arrange(() => foo.Prop).Returns("test");//设置Prop返回"Test"
                Mock.Arrange(() => foo.IsCalled()).When(() => foo.Prop == "test").Returns(true);
    
                Assert.IsTrue(foo.IsCalled());
            }
    

    JustMock模拟属性

    上面的例子中已经用到Returns来模拟属性的值了,这里再看看还有其它的用法:

    • 根据索引模拟值
      假设一个类的属性是数组或者链表等,我们需要模拟其中某个索引下的值:
    //indexedFoo是一个数组
    Mock.Arrange(() => indexedFoo[1]).Returns("pong"); //Mock indexedFoo 下标1的值为“pong”
    
    • 设置属性
      我们需要验证一个属性是否被正确赋值时,可以用 ArrangeSet 来模拟设置属性并验证。
    [Test] 
        public void ShouldAssertPropertySet() 
        { 
            var foo = Mock.Create<IFoo>(); 
     
            Mock.ArrangeSet(() => foo.Value = 1); 
     
            foo.Value = 1; //执行赋值
     
            Mock.AssertSet(() => foo.Value = 1); 
        } 
    

    Matchers 匹配参数

    我们模拟有参数的方法时,要根据不同的参数设置不同的返回值,Matchers可以做到这些。

    1. Arg.AnyBool,Arg.AnyDouble,Arg.AnyFloat, Arg.AnyGuid, Arg.AnyInt, Arg.AnyLong, Arg.AnyObject, Arg.AnyShort, Arg.AnyString, Arg.NullOrEmpty
    2. Arg.IsAny()
    3. Arg.IsInRange([FromValue : int], [ToValue : int], [RangeKind])
    4. Arg.Matches(Expression<Predicate> expression)
    5. Arg.Ref()
    6. Args.Ignore()

    我们可以利用Arg.Matches<T>(Expression<Predicate<T>> expression) 生成我们想要的数据条件。

    Arg.Ref() 是C#里的ref 参数。

    Args.Ignore() 可以忽略参数,同标记方法IgnoreArguments()

    Matchers 不仅在构造参数中使用,还可以在Assert中使用:

        [Test] 
        public void ShouldUseMatchersInAssert() 
        { 
            var paymentService = Mock.Create<IPaymentService>(); 
     
            paymentService.ProcessPayment(DateTime.Now, 54.44M); 
     
            // 断言:不管DateTime什么时间,Payment amount都是54.44.
            Mock.Assert(() => paymentService.ProcessPayment( 
                Arg.IsAny<DateTime>(), 
                Arg.Matches<decimal>(paymentAmount => paymentAmount == 54.44M))); 
        } 
    

    Sequential Mocking 连续模拟

    Sequential mocking 允许我们多次执行代码返回不一样的值,具体代码理解:

    [TestMethod] 
        public void ShouldArrangeAndAssertInASequence() 
        { 
            var foo = Mock.Create<IFoo>(); 
            //只需要在Arrange()后面加上InSequence()
            Mock.Arrange(() => foo.GetIntValue()).Returns(0).InSequence(); 
            Mock.Arrange(() => foo.GetIntValue()).Returns(1).InSequence(); 
            Mock.Arrange(() => foo.GetIntValue()).Returns(2).InSequence(); 
     
            int actualFirstCall = foo.GetIntValue(); //结果是:0
            int actualSecondCall = foo.GetIntValue(); //结果是:1
            int actualThirdCall = foo.GetIntValue(); //结果是:2
            int actualFourthCall = foo.GetIntValue(); // 注意这是第四次调用,因为没有设置结果,实际上他应当是上次调用的结果:2
        }
    

    配合Matcher使用:

     Mock.Arrange(() => foo.Echo(Arg.Matches<int>(x => x > 10))).Returns(10).InSequence(); 
     Mock.Arrange(() => foo.Echo(Arg.Matches<int>(x => x > 20))).Returns(20).InSequence(); 
     
     int actualFirstCall = foo.Echo(11); //结果10
     int actualSecondCall = foo.Echo(21); //结果20
    

    还可以多次返回:

    Mock.Arrange(() => foo.Echo(Arg.AnyInt)).Returns(10).Returns(11).MustBeCalled(); 
     
    Assert.AreEqual(10, foo.Echo(1)); 
    Assert.AreEqual(11, foo.Echo(2)); 
    

    或者以数组方式返回:

    int[] returnValues = new int[3] { 1, 2, 3 }; 
     
    Mock.Arrange(() => foo.Echo(Arg.AnyInt)).ReturnsMany(returnValues); 
     
    var actualFirstCall = foo.Echo(10); // 结果:1
    var actualSecondCall = foo.Echo(10); // 结果:2
    var actualThirdCall = foo.Echo(10); // 结果:3
    

    Recursive Mocking 递归模拟

    递归Mock。有时候我们需要取值像这样的:foo.Bar.Baz.Do("x"),我们可以不用一个一个对象去模拟,我们只需要Mock 第一个,然后设置值就可以了。
    像这样:

     var foo = Mock.Create<IFoo>(); // mock 第一个对象
     
     Mock.Arrange(() => foo.Bar.Do("x")).Returns("xit"); //设置某次调用的值
     Mock.Arrange(() => foo.Bar.Baz.Do("y")).Returns("yit"); 
    

    最后

    这里涉及到的都是是JustMock的免费版功能。在工作中遇到一些问题,我们需要测试的目标方法中混有静态类提供的逻辑,免费的框架都不支持Mock静态类,需要用到付费版的高级功能。更好的时候,我们应该避免这些依赖,以方便我们测试。

  • 相关阅读:
    另一种遍历Map的方式: Map.Entry 和 Map.entrySet()
    mycat 插入语句导致的一个Dobbo问题
    Json数据处理
    List与字符串转换
    MySQL中四舍五入的实现
    java连接mysql :No Suitable Driver Found For Jdbc 解决方法
    Linux中printf格式化输出
    bat隐藏文件夹
    Python 3.5.2建立与DB2的连接
    Python 爬虫实例
  • 原文地址:https://www.cnblogs.com/jimizhou/p/11424009.html
Copyright © 2011-2022 走看看