2011-04-04 88 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