技术开发 频道

设计,由你掌握

  通过Category对测试方法进行分类(也可以对测试类进行分类)。这样,我们就可以在Nunit中,根据测试的情况,选择要测试的分类,或Exclude(排除)不测试的分类。我们运行一下,看NUnit的绿灯是否都亮了?测试通过后,就可以接着实现另外的日志类了。
  public class ErrorLogDecorator:LogDecorator,IFee
  {
   public ErrorLogDecorator(IFee decoratee):base(decoratee){}
   public double SettleFee(double money, int records)
   {  
    try
    {
     double result = Decoratee.SettleFee(money,records);
     Console.WriteLine(”Settling operation succeed!”);
     return result;
    }
    catch (Exception ex)
    {
     Console.WriteLine(”The error occured while settling the fee.”);
     Console.WriteLine(”The error is ” + ex.Message);
     return 0;
    }
   }
  }

  public class ImplLogDecorator:LogDecorator,IFee
  {
   public ImplLogDecorator(IFee decoratee):base(decoratee)
   {}
   public double SettleFee(double money, int records)
   {
    double result = Decoratee.SettleFee(money,records);
    Console.WriteLine(”The StoreProcedure whick was invoked is SpSettleFee.”);
    Console.WriteLine(”Data table is: UserFee, OnLineRecord.”);
    return result;
   }
  }
  当然每做一步改进后,都需要修改测试代码进行单元测试。最后的单元测试代码:
  using System;
  using NUnit.Framework;
  using FeeManagement;

  namespace UnitTest
  {
   [TestFixture]
  public class TestFee
   {
    private IFee fee;
 
    [SetUp]
    public void Init()
    {
     fee = new Fee();  
    }

    [Test]
    [Category(”SettleWithoutLog”)]
    public void Settle()
    { 
     Assert.IsNotNull(fee);
     Assert.AreEqual(6,fee.SettleFee(2.0,3));
    }
 
    [Test]
    [Category(”SettleWithBasicLog”)]
    public void SettleBasicLog()
    { 
     IFee basicLogFee = new BasicLogDecorator(fee);
     Assert.IsNotNull(basicLogFee);
     Assert.AreEqual(6,basicLogFee.SettleFee(2.0,3));
    }

    [Test]
    [Category(”SettleWithErrorLog”)]
    public void SettleErrorLog()
    {
     IFee errorLogFee = new ErrorLogDecorator(fee);
     Assert.IsNotNull(errorLogFee);
     Assert.AreEqual(6,errorLogFee.SettleFee(2.0,3));
    }

    [Test]
    [Category(”SettleWithImplLog”)]
    public void SettleImplLog()
    {
     IFee implLogFee = new ImplLogDecorator(fee);
     Assert.IsNotNull(implLogFee);
     Assert.AreEqual(6,implLogFee.SettleFee(2.0,3));
    }

    [Test]
    [Category(”SettleWithBasic&ErrorLog”)]
    public void SettleBasicErrorLog()
    {
     IFee basicLogFee = new BasicLogDecorator(fee);
     IFee errorLogFee = new ErrorLogDecorator(basicLogFee);
     Assert.IsNotNull(basicLogFee);
     Assert.IsNotNull(errorLogFee);
     Assert.AreEqual(6,errorLogFee.SettleFee(2.0,3));
    }

    [Test]
    [Category(”SettleWithBasic&ImplLog”)]
    public void SettleBasicImplLog()
    {
     IFee basicLogFee = new BasicLogDecorator(fee);
     IFee implLogFee = new ImplLogDecorator(basicLogFee);
    Assert.IsNotNull(basicLogFee);
     Assert.IsNotNull(implLogFee);
     Assert.AreEqual(6,implLogFee.SettleFee(2.0,3));
    }

    [Test]
    [Category(”SettleWithAllLog”)]
    public void SettleAllLog()
    {
     IFee basicLogFee = new BasicLogDecorator(fee);  
     IFee implLogFee = new ImplLogDecorator(basicLogFee);
     IFee errorLogFee = new ErrorLogDecorator(implLogFee);
     Assert.IsNotNull(basicLogFee);  
     Assert.IsNotNull(implLogFee);
     Assert.IsNotNull(errorLogFee);
     Assert.AreEqual(6, errorLogFee.SettleFee(2.0,3));
    }

    [TearDown]
    public void Dispose()
    {
     /*—*/
    }
   }
  }

  由于每一步都严格进行了单元测试,所以,我们对代码的正确性充满了信心。这也是单元测试的重要性及必要性所在。

  七、真的结束了吗

  从测试代码中看出,目前的解决方案还存在一个问题,就是日志对象的创建。由于日志对象可能会根据不同的情况,组合成不同的对象。如果不采取相应的方法来解决对象创建的问题,可能会造成对象管理的混乱。因此,我们还有必要引入工厂模式,专门负责日志对象的创建。

  我最初考虑在工厂方法中,将这些日志类型放到一个Type[]数组中,然后再通过反射的方式创建对象。然而,由于创建日志对象的组合会很麻烦,采用这样的设计,反而会有过度设计的嫌疑。(这也是我为什么在Decorator类中使用构造函数而非采用专门的方法来设置Decoratee对象的原因。)

  所以,只需要直接根据日志的情况为其分别创建相关的工厂方法就可以了。
  public class DecoratorFactory
  {
   private static IFee fee = new Fee();
   public static IFee CreateFee()
   {
    return fee;
   }
   public static IFee CreateBasicLogFee()
   {  
    IFee basicLog = new BasicLogDecorator(fee);
    return basicLog;
   }
   public static IFee CreateErrorLogFee()
   {
    IFee errorLog = new ErrorLogDecorator(fee);
    return errorLog;
   }
   public static IFee CreateImplLogFee()
   {
    IFee implLog = new ImplLogDecorator(fee);
    return implLog;
   }
   public static IFee CreateBasicErrorLogFee()
   {
    IFee basicLog = new BasicLogDecorator(fee);
    IFee errorLog = new ErrorLogDecorator(basicLog);
    return errorLog;
   }
   public static IFee CreateBasicImplLogFee()
   {
    IFee basicLog = new BasicLogDecorator(fee);
    IFee implLog = new ImplLogDecorator(basicLog);
    return implLog;
   }
   public static IFee CreateAllLogFee()
   {
    IFee basicLog = new BasicLogDecorator(fee);
    IFee implLog = new ImplLogDecorator(basicLog);
    IFee errorLog = new ErrorLogDecorator(implLog);
    return errorLog;
   }
  }

  然后再修改NUnit的测试代码:
  [TestFixture]
  public class TestFee
  {
   private IFee fee;
   [SetUp]
   public void Init()
   {
    fee = DecoratorFactory.CreateFee();  
   }
   [Test]
   [Category(”SettleWithoutLog”)]
   public void Settle()
   { 
    Assert.IsNotNull(fee);
    Assert.AreEqual(6,fee.SettleFee(2.0,3));
   }
   [Test]
   [Category(”SettleWithBasicLog”)]
   public void SettleBasicLog()
   { 
    IFee basicLogFee = DecoratorFactory.CreateBasicLogFee();
    Assert.IsNotNull(basicLogFee);
    Assert.AreEqual(6,basicLogFee.SettleFee(2.0,3));
   }
   [Test]
   [Category(”SettleWithErrorLog”)]
   public void SettleErrorLog()
   {
    IFee errorLogFee = DecoratorFactory.CreateErrorLogFee();
    Assert.IsNotNull(errorLogFee);
    Assert.AreEqual(6,errorLogFee.SettleFee(2.0,3));
   }
   [Test]
   [ Category(”SettleWithImplLog”)]
   public void SettleImplLog()
   {
    IFee implLogFee = DecoratorFactory.CreateImplLogFee();
    Assert.IsNotNull(implLogFee);
    Assert.AreEqual(6,implLogFee.SettleFee(2.0,3));
   }
   [Test]
   [Category(”SettleWithBasic&ErrorLog”)]
   public void SettleBasicErrorLog()
   {
    IFee log = DecoratorFactory.CreateBasicErrorLogFee();     
    Assert.IsNotNull(log);
    Assert.AreEqual(6,log.SettleFee(2.0,3));
   }
   [Test]
   [Category(”SettleWithBasic&ImplLog”)]
   public void SettleBasicImplLog()
   {
    IFee log = DecoratorFactory.CreateBasicImplLogFee();  
    Assert.IsNotNull(log);
    Assert.AreEqual(6,log.SettleFee(2.0,3));
   }
   [Test]
   [Category(”SettleWithAllLog”)]
   public void SettleAllLog()
   {
    IFee log = DecoratorFactory.CreateAllLogFee();
    Assert.IsNotNull(log);
    Assert.AreEqual(6,log.SettleFee(2.0,3));
   }
   [TearDown]
   public void Dispose()
   {
   /*—*/
   }
  } 

  经过这么多阶段的修改和完善,目前看来解决方案已经比较完善了。如果在Fee类中还有其他的方法,需要日志功能,方法仍然大同小异。因为在C#中可以同时实现多个接口,如果实现其他接口的类也要增加该日志功能,则日志的Decorator类同时还要去实现这个新的接口。好处是,你只需要修改这些实现,而调用的代码,却不用作大的修改了。因为要求提供日志功能的需求可能会不断增加,但只要日志的种类不变,用作装饰功能的日志对象个数就不会改变。

  自然,本文讨论日志功能是完全站在OOP的角度来考虑的。如果引入AOP的思想,将日志看作是一个方面(Aspect),那么对于客户而言,可能会更简单。但这已不是本文要讨论的问题了。

0
相关文章