2011-02-01 116 views
9

我想知道如何解決這個問題。我使用nhibernate和流利的。單元測試和nhibernate?

我有這樣

public class User 
{ 
    public virtual int UserId {get; private set;} 
} 

域類,這似乎NHibernate的時候做,因爲它使人們無法設置和ID,因爲它是自動生成成爲慣例。

現在問題出現在我單元測試的時候。

我有我所有的nhibernate代碼在回購,我嘲笑所以我只測試我的服務層。發生這種情況時會出現問題。

User user = repo.GetUser(email); 

這應該返回一個用戶對象。

所以我想用最小起訂量來做到這一點

repo.Setup(x => x.GetUser(It.IsAny<string>())).Return(/* UserObject here */) 

現在這裏的問題是

我需要做的是用戶對象,並把它的收益部分。

所以我會做類似

User user = new User() 
{ 
    UserId = 10, 
} 

但是,這是問題所在,我需要設置的ID,因爲實際上我用它後來做了一些收集一些LINQ(在服務層它沒有打我的數據庫,所以它不應該在我的回購)所以我需要設置它,但我不能設置它,因爲它是一個私人設置。

那我該怎麼辦?我應該刪除私人還是以其他方式?

回答

14

你可以有假Repository對象返回一個假User對象:

var stubUser = new Mock<User>(); 
stubUser.Setup(s => s.UserId).Returns(10); 

var stubRepo = new Mock<IUserRepository>(); 
stubRepo.Setup(s => s.GetUser(It.IsAny<string>())).Return(stubUser); 

有幾件事情來觀察這裏:

  1. 起訂量只能具體類的假會員如果他們被標記爲虛擬。這在某些情況下可能不適用,在這種情況下,通過Moq僞造對象的唯一方法是讓它實現一個接口。
    但是,在這種情況下,該解決方案很好地工作,因爲類的屬性上的NHibernate already imposes the same requirement爲了執行延遲加載。
  2. 假貨返回其他假貨有時會導致超過指定的單位測試。在這些情況下,由樁和模擬組成的豐富對象模型的構建會變得難以確定究竟究竟是什麼測試,從而使測試本身不可讀,難以維護。這是一個非常好的單元測試實踐,要清楚,但必須有意識地使用它。

相關資源:

+0

由於這個工程只是完美。我可能有2號的症狀。它只是模擬了很多事情。我只是試圖保留它,而不是我需要測試的東西。例如,我不嘲笑用戶的用戶名,因爲它沒有在方法中使用。我想嘗試自動混合moq,但我不知道如何真正使用,似乎沒有真正的教程,所以我不能確定這是否會幫助我。 – chobo2 2011-02-01 23:40:00

2

恩里科的答案是當場就進行單元測試。我提供了另一種解決方案,因爲這個問題在其他情況下也會出現,你可能不想使用Moq。我經常在生產代碼中使用這種技術,其中常見的使用模式是類成員是隻讀的,但某些其他類需要修改它。一個例子可能是狀態字段,它通常是隻讀的,只能由狀態機或業務邏輯類來設置。

基本上,您通過一個靜態嵌套類提供對私有成員的訪問,該類包含一個設置屬性的方法。一個例子是勝過千言萬語:

public class User { 
    public int Id { get; private set; } 

    public static class Reveal { 
     public static void SetId(User user, int id) { 
      user.Id = id; 
     } 
    } 
} 

你使用這樣的:

User user = new User(); 
User.Reveal.SetId(user, 43); 

當然,這則使任何人都幾乎一樣方便地設置屬性值就好像你提供的公共二傳手。但也有一些優勢,這種技術:

  • 沒有智能感知提示屬性setter或SETID()方法
  • 程序員必須明確使用奇怪的語法與Reveal類設置的屬性,從而促使他們他們可能不應該這樣做
  • 您可以輕鬆地在Reveal類的用途進行靜態分析,看看哪些代碼繞過標準的訪問模式

如果你只希望ŧ o爲單元測試目的修改私有財產,並且您可以將Moq作爲對象,那麼我仍然會推薦Enrico的建議;但您可能會不時發現此技術很有用。

+0

+1。很多類似的選項 - InternalsVisibleToAttribute,protected和subclass,用反射破解,用帶有ObsoleteAttribute的SetId和適當的警告文本,把Id傳遞給重載的構造函數(如果可能的話,我的首選項)等等。我喜歡這個,因爲它鼓勵用波蘇斯/ DTO的。就像我喜歡Moq一樣,@ Enrico對於過度規範的危險是正確的。 – TrueWill 2011-02-01 23:53:00

0

而不是試圖模擬你的倉庫,我建議你嘗試使用內存中的SQLite數據庫進行測試。它會給你你正在尋找的速度,它也會讓事情變得更容易。如果你想看到一個工作示例,你可以看看我的一個GitHub項目:https://github.com/dlidstrom/GridBook

1

如果您不想模擬實體類,另一種方法是使用反射來設置私有/受保護的ID。

是的,我知道這通常不被看好,經常被認爲是某個地方糟糕設計的標誌。但在這種情況下,在你的NHibernate實體上有一個受保護的ID是標準範例,所以它似乎是一個相當合理的解決方案。

我們可以嘗試至少實現它。在我的例子中,95%的實體都使用一個Guid作爲唯一標識符,只有少數人使用整數。因此,我們的實體類通常實現一個非常簡單的HasID接口:

public interface IHasID<T> 
{ 
    T ID { get; } 
} 

在實際的實體類,我們可以實現它是這樣的:

public class User : IHasID<Guid> 
{ 
    Guid ID { get; protected set; } 
} 

這個ID被映射到NHibernate的作爲主鍵通常的方式。

向這在我們的單元測試的設置中,我們可以用這個接口來提供一個方便的擴展方法:

public static T WithID<T, K>(this T o, K id) where T : class, IHasID<K> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { id }); 
    return o; 
} 

我們不必有HasID接口要做到這一點,但它意味着我們可以跳過一些額外的代碼 - 例如,我們不需要檢查ID是否實際得到支持。

擴展方法也返回原來的對象,因此在使用中我通常只是IT連鎖到構造函數的末尾:

var testUser = new User("Test User").WithID(new Guid("DC1BA89C-9DB2-48ac-8CE2-E61360970DF7")); 

或實際的,因爲對我的GUID不在乎ID居然什麼是的,我有另一種擴展方法:

public static T WithNewGuid<T>(this T o) where T : class, IHasID<Guid> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { Guid.NewGuid() }); 
    return o; 
} 

而且在用法:

var testUser = new User("Test User").WithNewGuid();