转载JGTM'2004 [MVP]的文章

Unit Testing with .NET Quiz: Adaptable Mock Objects?

前面用透明代理机制实现简单AOP的文章一发,很快收到很多朋友的反馈,其中就有一些朋友提出关于文中所述的透明代理等技术还能够用于什么应用场合的疑问。说实在的,本来我就是想写一些关于这些技术在实际项目中应用的随笔的,可是想到可能会有很多朋友对这些技术的基本原理还不熟悉,这才萌生了先写一系列文章作铺垫的念头(可惜由于工作比较忙的缘故,至今还欠一篇没有写完,还请大家见谅啦,我抓紧时间补上——这篇内容更精彩噢!呵呵)。

 

今天我们又一次把我前面这两篇文字中提到的技术应用在项目开发中,初步反馈还是非常positive的,这里我不妨把问题先说出来,请看过文章的朋友们好好想想,看看如何利用已经学到的知识来解决这个实际问题吧——稍后我会回来说说我的想法(当然如果大家看过偶的文章以后都能够想出这种解决方案的话那当然就最好了:),当然如果你有其他更好的办法,更是欢迎你通过发表评论和大家分享!

 

这个问题和单元测试(Unit Testing)有密切关系(虽然我们的项目还不能算是完全的TDD,但是充分、必要的单元测试还是不可或缺),这里我们还是以简单的例子来说明,比如这里有一个银行服务的接口:

 

interface IBankService
{
  void Withdraw(Account account, int amount);
  void Deposit(Account account, int amount);
  int QueryBalance(Account account);
}

 

这个接口的三个方法的作用很简单:取款、存款、查询余额。我们肯定要开发一个实现这个接口的具体业务操作类;与此同时,由于该服务接口对安全性的严格要求,项目组中专门负责安全规则的部门将以DECORATOR的方式针对该接口的每一个方法编写负责安全保障的修饰类(最终在运行时由组件工厂动态组合)。以这种设计方式,只要确定了服务接口,则两个类的开发就可以充分的并行进行了——真的是这样吗?我们写写看:

 

class BankServiceSecurityGuard: ComponentDecorator, IBankService
{
  void IBankService.Withdraw(Account account, int amount)
  {
    if ( … )
      throw new DailyAmountExceedsException( … );
    if ( … )
      throw new AccountBlockedException( … );
    if ( … )
      throw new PermissionNotGrantedException( … );
   

    ((IBankService)Decoratee).Withdraw(account, amount);   
  }
 
}

 

这个方法的实现就是一个把门儿的逻辑:结合方法调用参数使尽各种必要手段确保只有在符合所有安全规则的前提下才能够把调用转发到被修饰的(即decoratee)对象(也就是IBankService的核心实现类上)。也就是说,开发并测试这个安全保障修饰类的时候最终还是要依赖于一个能够工作的核心实现类,否则就算可以编译通过(还好这种依赖已经被接口隔离开了),你也无法让单元测试通过。我们能不能用什么办法来独立的测试这个修饰对象使之不依赖于一个正确工作的核心实现类呢?换句话说,有没有办法在没有可工作的核心实现类的情况下(这种情况常见于核心类开发周期较长的时候),让下面针对安全保障修饰类的单元测试代码正常起到单元测试的作用(一是驱动开发过程、二是形成软件规格说明、三是跟踪软件缺陷……)呢(我们先假设Account类是已经存在且经过充分测试的——其实对该类的隔离测试也是类似的道理)?

 

[TestFixture]
public class BankServiceSecurityTest
{
  private IBankService bankService;

  [SetUp]
  public void Initialization()
  {
    bankService = new BankServiceSecurityGuard(???);
  }

  [ExpectedException(typeof(DailyAmountExceedsException))]
  public void TestForExceedingDailyAmount()
  {
    AccountState state = AccountState.DailyAmountReached;
    Account account = new MockAccount(state);
    bankService.Withdraw(10);
  }

  public void TestForNormalWithdraw()
  {
    AccountState state = AccountState.Normal;
    int initialBalance = 1000;
    Account account = new MockAccount(state, initialBalance);
    try
    {
      bankService.Withdraw(10);
      Assert.IsTrue(coreBankService's been called for Withdraw(10));
    }
    catch(Exception ex)
    {
      Assert.Fail("this call should not throw exception");
    }   
  }
}

 

简单说,我们要测试两类情况:一类是应该抛出安全异常的情况(TestForExceedingDailyAmount),在这种情况下,我们需要确信在给定的前提下必将产生指定的后果(比如特定类型的安全异常);另一类则是不该抛出异常的情况喽,在这种情况下我们不光要确信没有安全异常抛出,还要进一步确保方法调用确实被正确的(包括传入参数的值)转发给内部的指定方法了(否则一是有可能放过人为的安全后门,也有可能漏过修饰类忘记或错误调用被修饰类的情况)!

 

我已经听见有人高呼“简单简单”啦……写个mock对象不就结了!好,现在请你来为系统中数以百计的安全修饰类以及其他功能修饰类编写一一对应的mock对象好啦(幸好我们的接口都不是很fat,每个里面平均也就不到10个方法吧:)!反正我可真是一个超懒的程序员啊……你能给个省事儿又简单的方案吗?

转自:http://blog.joycode.com/jgtm2000/posts/14549.aspx

posted @ 2004-03-03 18:51  dudu  阅读(693)  评论(1编辑  收藏  举报