zoukankan      html  css  js  c++  java
  • 隔离框架

      前面说了手工创建模拟对象的示例,这一章主要说一下使用模拟框架创建示例。我使用的模拟框架是NSubstitute

    1:如何创建伪对象

    我们知道手动编写伪对象显得很笨拙(当然按照实际情况),那么用隔离框架创建伪对象就会很简单快速,那么我们先看一幅图

    那么首先我们创建一个个接口

    1  public interface IUser
    2     {
    3         bool Add(string userName, string realName);
    4     }
    IUser

    实现接口

    1 public class User:IUser {
    2         public bool Add(string userName, string realName)
    3         {
    4             return true;
    5         }
    6     }
    User

    业务中调用

     1 public class UserManager
     2     {
     3         public bool Add(string userName, string realName)
     4         {
     5             if (string.IsNullOrWhiteSpace(userName))throw new ArgumentNullException(userName);
     6             if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentNullException(realName);
     7 
     8             if (userName.Length>=0)
     9             {
    10                 return false;
    11             }
    12             return GetUser().Add(userName, realName);
    13         }
    14 }
    UserManager

    单元测试

    1                 [Test]
    2         public void Add_Default_CallAdd()
    3         {
    4             var fakeUser = Substitute.For<IUser>();
    5             fakeUser.Add("1", "1").Returns(true);//按照我们的意愿进行返回值            
    6             Assert.IsTrue(fakeUser.Add("1", "1"));                
    7         }
    Add_Default_CallAdd()

    以上是我们输入2个参数然后按我们意愿返回true;

    如果我们不考虑参数呢

    那么只需要把参数置为Arg.Any<T>()//T表示参数的类型可以string,int等看下测试结果

     当然返回值一定是按照你预定的类型。比喻bool Add() 返回值必须是一个bool类型的或者抛出异常。

    2:When...Do的用法

    when do在NSub的用处还是比较广的。比喻当什么的时候做什么。下面一个例子来看一下

    1 [Test]
    2         public void Add_Default_CallAdd()
    3         {
    4             var fakeUser = Substitute.For<IUser>();            
    5             fakeUser.When(x => x.Add("1", "2")).
    6                 Do(content => { throw new Exception("fack exception"); });//用when,do表示式,表示当操作这个函数的时候会出现什么后果        
    7             Assert.Throws<Exception>(() => fakeUser.Add("1","2"));        
    8         }
    When-Do

    上面的例子就是当参数是1和2的时候会抛出一个异常。下面看现实的结果

    刚刚参数是1和2会抛出一个异常如果是1和3会不会抛出异常呢,我们手动把参数进行修改在看下面效果

    我们一看测试通不过,此时说明只有当参数1和2的时候才会抛出异常这样一来模拟框架也会给我们预定的参数进行一个验证。

    3:很强大的Received()

    Received()方法工作很神奇,这个方法在什么对象被调用就会返回和这个对象同样类型的对象,但实际上就是申明断言的对象。什么意思呢我们看下面的代码
    测试中的接口和实现方法参考上一篇

     1 [Test]
     2         public void RecordLog_EmailServiceThrows_CallEmail()
     3         {
     4             var stub = Substitute.For<ILogService>();
     5             var mock = Substitute.For<IEmailService>();
     6             stub.When(p => p.ErrorLog(Arg.Any<String>())).Do(a => { throw new ArgumentNullException(); });
     7             var test = new TestUserManager(stub, mock);
     8             test.RecordLog("");//触发            
     9             mock.Received().SendEmail(Arg.Is("lp"), Arg.Is("subject"), Arg.Is("值不能为 null。"));//保证接受了方法并调用(不可以少一个句号)            
    10         }
    测试

    在以上的例子我们可以看出Received()就表示SendEmail被调用并且收获到SendEmail发送的信息然后进行断言是否和预定发送的信息保持一致。这个和上一篇说的模拟对象断言是一样的。(如果使用模拟对象不要忘记Received)

    4:事件应该怎么测试

    •  测试监听事件的一方
    •  测试触发事件的一方

    4.1:测试监听事件

    定义一个事件的接口

    1 public interface IView
    2     {
    3         event Action Loaded;
    4         void Render(string content);
    5     }
    IView

    事件相关的代码并如何触发事件的

     1 public class Presenter
     2     {
     3 
     4         private readonly IView _view;
     5 
     6         public Presenter(IView view)
     7         {
     8             this._view = view;
     9             this._view.Loaded += Onload;//注册事件
    10         }
    11 
    12         public void Onload()
    13         {
    14             _view.Render("Hello World");
    15         }
    16     }
    Presenter

    测试代码

     1 [Test]
     2         public void Ctor_WhenViewIsLoad_CallsViewRender()
     3         {
     4             var mockView = Substitute.For<IView>();
     5             var p =new Presenter(mockView);
     6             mockView.Loaded += Raise.Event<Action>();
     7 
     8             mockView.Received().Render(Arg.Is<string>(s=>s.Contains("Hello World")));
     9 
    10         }
    Ctor_WhenViewIsLoad_CallsViewRender

    Raise.Event表示触发事件。

    最后使用received来测试是否view中的Render是否触发,如果真的触发那么接受的参数是否是Hello World。

    我们可以模拟一个存根一个一个模拟对象

    修改与事件相关的代码

     1 public class Presenter
     2     {
     3 
     4         private readonly IView _view;
     5         private readonly ILogService _logService;
     6 
     7         public Presenter(IView view,ILogService logService)
     8         {
     9             this._view = view;
    10             this._logService = logService;
    11             this._view.Loaded += Onload;//注册事件
    12             this._view.ErrorOccured += WriteLog;
    13         }
    14 
    15         public void Onload()
    16         {
    17             _view.Render("Hello World");
    18         }
    19 
    20         public void WriteLog(string errorContent) {
    21             _logService.ErrorLog(errorContent);
    22         }
    23     }
    Presenter

    测试代码

     1     [Test]
     2         public void Ctor_WhenViewIsError_CallsLogs() {
     3 
     4             var stubView = Substitute.For<IView>();
     5             var mockLog = Substitute.For<ILogService>();
     6             var p = new Presenter(stubView,mockLog);
     7             stubView.ErrorOccured += Raise.Event<Action<string>>("fack error");
     8             mockLog.Received().ErrorLog(Arg.Is<string>(s => s.Contains("fack error")));
     9 
    10         }
    Ctor_WhenViewIsError_CallsLogs

    注意:使用存根stubView.ErrorOccured += Raise.Event<Action<string>>("fack error");来触发事件,然后在用模拟对象查看日志服务是否被正确的调用

    4.2:测试事件是否触发

    这个比较简单,在测试方法内部使用一个匿名委托,手动注册次方法即可

    定义一个变量,当事件触发改变变量的值即可。

    如:bool isTrigger=false;

    SomeView=new SomeView();

    SomeView.Load+=delegate{isTrigger=true;}

    SomeView.触发事件 然后比对isTrigger的值是否改变。

    5:模拟框架的优缺点

    • 优点
      • 容易验证参数
      • 容易创建伪对象
      • 使用模拟对象的时候比较简单不用手写大量代码
    • 缺点

             测试代码不可读

             验证错误的事情

            一个测试可能会有多个模拟对象

           过度指定

    NSubstitute的官方文档很详细有兴趣的可以自己查看动手写一写会有更好的效果http://nsubstitute.github.io/help/raising-events/

  • 相关阅读:
    控制台内容保存为文件
    SpringBoot
    JAVA基础
    jenkins的.gradle目录结构说明和清理
    macos 签名+公证app生成dmg后,安装使用过程中崩溃
    MacOS命令行打包+签名+公证+生成dmg文件
    jenkins构建调用tar报错:tar: Failed to set default locale
    jenkins构建报错:appdmg: command not found
    jenkins 构建xcode-select -s 切换xcode版本失败 (切换xcode路径无效)
    jenkins 执行shell编译go 代码报错:build cache is required, but could not be located: GOCACHE is not defined and neither $XDG_CACHE_HOME nor $HOME are defined
  • 原文地址:https://www.cnblogs.com/LipeiNet/p/5027610.html
Copyright © 2011-2022 走看看