2011-04-04 86 views
2

我一直在为业务逻辑层编写单元测试时遇到了一些麻烦,请指点我正确的方向。任何意见,将不胜感激。重构单元测试

业务逻辑

public class TitleLogic 
{ 
    private readonly TitleDAL titleDAL = new TitleDAL(); 
    private readonly List<TitleEntity> titleEntities; 

    public TitleLogic() 
    { 
     titleEntities = titleDAL.GetAllTitles().ToList(); 
    } 

    public TitleEntity InsertTitle(TitleEntity titleEntity) 
    { 
     if (!titleEntity.IsValid) 
     { 
      throw new EntityException<TitleEntity>("Invalid Title.", titleEntity); 
     } 

     titleEntity.TitleName.TrimSize(TitleEntity.TitleName_Length); 

     var createdTitle = titleDAL.InsertTitle(titleEntity); 

     titleEntities.Add(createdTitle); 

     return createdTitle; 
    } 

    public TitleEntity FindTitle(string titleName) 
    { 
     return titleEntities.Find(p => p.TitleName == titleName); 
    } 
} 

数据层

public class TitleDAL 
{ 
    private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); 

    public TitleEntity InsertTitle(TitleEntity titleEntity) 
    { 
     XTime900Entities xTime900Entities = new XTime900Entities(); 

     //Find the next CodeId to use 
     var titleCodeId = xTime900Entities.TITLEs.Max(p => p.TITLE_CODEID) + 1; 

     TITLE title = new TITLE 
     { 
      TITLE_CODEID = (short) titleCodeId, 
      TITLE_ACTIVE = Convert.ToInt16(titleEntity.TitleActive), 
      TITLE_NAME = titleEntity.TitleName 
     }; 

     xTime900Entities.TITLEs.InsertOnSubmit(title); 
     xTime900Entities.SubmitChanges(); 
     logger.Debug("Inserted New Title CodeId: {0}", titleCodeId); 
     xTime900Entities.Dispose(); 

     return titleEntity.Clone((short)titleCodeId); 
    } 

    public ICollection<TitleEntity> GetAllTitles() 
    { 
     logger.Debug("Retrieving List all Titles from XTime900 database."); 
     List<TitleEntity> titleEntities = new List<TitleEntity>(); 

     using (XTime900Entities XTEntities = new XTime900Entities()) 
     { 
      var titlesInDB = from p in XTEntities.TITLEs 
            select p; 

      foreach (var titlesInDb in titlesInDB) 
      { 
       TitleEntity genderEntity = new TitleEntity(titlesInDb.TITLE_CODEID) 
       { 
        TitleActive = Convert.ToBoolean(titlesInDb.TITLE_ACTIVE), 
        TitleName = titlesInDb.TITLE_NAME 
       }; 

       titleEntities.Add(genderEntity); 
      } 
     } 

     logger.Debug("Found {0} Titles.", titleEntities.Count); 
     return titleEntities; 
    } 
} 

实体

public class TitleEntity 
{ 
    public const int TitleName_Length = 30; 

    public short TitleCodeId { get; private set; } 
    public bool TitleActive { get; set; } 
    public string TitleName { get; set; } 
    public bool IsValid 
    { 
     get 
     { 
      return !String.IsNullOrEmpty(TitleName); 
     } 
    } 

    public TitleEntity() 
    { 
     this.TitleCodeId = -1; 
    } 
    public TitleEntity(short titleCodeId) 
    { 
     this.TitleCodeId = titleCodeId; 
    } 

    public TitleEntity Clone(short titleCodeId) 
    { 
     TitleEntity genderEntity = new TitleEntity(titleCodeId) 
     { 
      TitleActive = this.TitleActive, 
      TitleName = this.TitleName 
     }; 

     return genderEntity; 
    } 

    public override string ToString() 
    { 
     StringBuilder sb = new StringBuilder(); 
     sb.AppendLine(String.Format("TitleCodeId : {0}", TitleCodeId)); 
     sb.AppendLine(String.Format("TitleActive : {0}", TitleActive)); 
     sb.AppendLine(String.Format("TitleName : {0}", TitleName)); 
     return sb.ToString(); 
    } 

    public static bool operator ==(TitleEntity x, TitleEntity y) 
    { 
     return (x.Equals(y)); 
    } 

    public static bool operator !=(TitleEntity x, TitleEntity y) 
    { 
     return !(x.Equals(y)); 
    } 

    public bool Equals(TitleEntity other) 
    { 
     if (ReferenceEquals(null, other)) return false; 
     if (ReferenceEquals(this, other)) return true; 
     return other.TitleCodeId == TitleCodeId && other.TitleActive.Equals(TitleActive) && Equals(other.TitleName, TitleName); 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) return false; 
     if (ReferenceEquals(this, obj)) return true; 
     return obj.GetType() == typeof(TitleEntity) && Equals((TitleEntity)obj); 
    } 

    public override int GetHashCode() 
    { 
     unchecked 
     { 
      var result = TitleCodeId.GetHashCode(); 
      result = (result * 397)^TitleActive.GetHashCode(); 
      result = (result * 397)^(TitleName != null ? TitleName.GetHashCode() : 0); 
      return result; 
     } 
    } 
} 

回答

4

你不能轻易测试您的业务逻辑,因为您已在TitleLogic类中创建了DAL组件。

我会做的第一件事是让TitleDAL实施ITitleDAL接口,使TitleLogic类采取ITitleDAL接口的一个实例。

然后,当你正在测试的InsertTitle方法你可以测试其中:

  • 检查时提供一个无效TitleEntityEntityException抛出
  • 当实体是有效的是ITitleDAL.InsertTitle被调用。
  • 当一个标题正确插入,就可以使用FindTitle方法

在你的测试,你需要创建一个模拟ITitleDAL实现(或使用mocking library创建一个给你),以便找到你测试返回已知的预期数据不依赖于实际的DAL。

你也可能要考虑进行测试:

  • 发生什么情况,如果上ITitleDALInsertTitle方法失败?
+0

+1,与我写的相同,结构/解释比例更好。 – 2011-04-04 11:43:17

+0

太好了,这或多或少是我的目标。只是另一个q,我应该然后改变“私人只读TitleDAL titleDAL =新TitleDAL();”公开ITitleDAL titleDAL {get; set;}或在构造函数中传递它? – Jethro 2011-04-04 12:12:21

+0

我想你应该通过它的构造函数。而且我不认为你应该让这个物业公开。如果依赖关系是可选的,那么你可以有一个属性,它可能会或可能不会被设置。在这种情况下,依赖是强制性的,所以你应该通过在构造函数中询问来显示它。 – 2011-04-04 12:14:03

2

你需要做的第一件事就是考虑依赖注入。为此工作的最简单的方法是实现DAL的接口,沿线

interface ITitleDAL 
{ 
    TitleEntity InsertTitle(TitleEntity titleEntity); 
    ICollection<TitleEntity> GetAllTitles(); 
} 

然后让您的DAL层实现接口。

接下来,更改构造为您DAL接受实现该接口的对象...

public TitleLogic(ITitleDAL myDAL) 
{ 
    titleDAL = myDAL; 
    titleEntities = titleDAL.GetAllTitles().ToList(); 
} 

然后创建DAL的模拟版本,也实现了接口,但返回静态数据。

之后,你需要做2件事情。

1)让您的生产代码创建DAL实例并将其传递到业务层的构造函数中。
2)让您的单元测试在您的模拟类的实例中传递给构造函数,并根据已编码的已知数据进行测试。

+0

请注意,这个答案的目的是作为一个起点,提供一个快速参考的方式前进,故意不谈论嘲笑/存根/嘲讽框架或任何光荣的东西。简单的第一步... :) – ZombieSheep 2011-04-04 11:51:53

+0

+1的例子,并给出了最简单的东西,将工作的细分。 – 2011-04-04 11:58:00

+0

感谢您的回答。我有一个问题,我也有,并且具有TitleLogic,GenderLogic,OccupationLogic,DepartmentLogic,TeamLogic等的EmployeeLogic。这不是通过构造器传递的参数太多吗? – Jethro 2011-04-04 12:25:55

0

您想单独测试每种类型。也就是说,当您为业务对象编写测试时,您不希望被迫为其使用的DAL和辅助对象(如XTime900Entities等)执行代码。

现在,所有这些类型都紧密耦合到彼此,这是不必要的,从可测试性的角度来看是一个问题。也就是说,您的业务对象类的单元测试被迫与您的数据访问层和实现耦合。从长远来看,这是行不通的。它不会在一个庞大的代码基础上扩展,单元测试维护将随着变化而大幅攀升。

此外,你需要在这里采取例外的担忧。例如,xTime900Entities.Dispose();如果在该方法的中间存在异常,则不会被调用。这意味着如果InsertTitle期间发生意外事件,代码将泄漏资源。这是一个笼统的概念,但这样的事情会在这种情况下更好:

XTime900Entities xTime900Entities =新XTime900Entities(){ // 方法 的休息} //这里处理自动调用,如果不管的例外是扔在块或不

良好的做法是注入依赖关系,取决于抽象,孤立的担忧,它可以让你孤立地测试。

+0

糟糕,我忘了将XTime900Entities更改为using语句,我同意这一点。我在使用语句中没有使用它的原因最初是因为如果你在using语句中返回finally语句没有被调用,我会发现它总是被调用。我测试我的Entites和Logic,我的DAL并非真的需要测试。从某种意义上说,我不了解DAL与业务逻辑紧密结合,因此我对测试有疑问,可能实现ITilteDAL来解决问题,但除此之外,我认为这种分层是一种很好的设计。 – Jethro 2011-04-04 12:40:31