2015-08-24 55 views
2

我正在慢慢地開始單元測試和嘲弄,但這是一個緩慢的過程。我嘗試過使用這個Active Directory代碼進行單元測試。這個問題與AD並不嚴格相關。太多的接口和包裝?

class ActiveDirectoryQueryer {  
    DirectorySearcher mSearcher; 

    public ActiveDirectoryQueryer() { 
     var searcher = new DirectorySearcher(...); 
    } 

    public void GetAllMailEntries() { 
     MailEntries = 
     mSearcher 
     .FindAll() 
     .Select(result => result.GetDirectoryEntry()) 
     .Select(BuildNewADUser) 
     .ToList(); 
    } 

    static ActiveDirectoryUser BuildNewADUser(DirectoryEntry pDirectoryEntry) { 
     return ActiveDirectoryUser.Create(
     pDirectoryEntry.Guid, 
     (pDirectoryEntry.Properties["name"].Value ?? "").ToString(), 
     (pDirectoryEntry.Properties["mail"].Value ?? "").ToString() 
    ); 
    } 

所以,我想單元測試GetAllMailEntries的方法。爲了使用MOQ來做到這一點,我不得不手動生成各種.NET類型的接口和包裝器,並且改變了上面許多接口的引用(如IDirectoryEntry)。下面的每個IXxxx接口都有一個關聯的包裝類XxxxWrapper。總的來說,我爲這個測試添加了至少12個新的源文件。以下是單元測試的結果:

[TestMethod] 
public void TestGetAllMailEntries() { 
    var mockSearcher = new Mock<IDirectorySearcher>(); 
    var mockResultCollection = new Mock<ISearchResultCollection>(); 
    var mockSearchResult = new Mock<ISearchResult>(); 
    var mockDirectoryEntry = new Mock<IDirectoryEntry>(); 
    var mockPropertyCollection = new Mock<IPropertyCollection>(); 
    var nameMockPropertyValueCollection = new Mock<IPropertyValueCollection>(); 
    var mailMockPropertyValueCollection = new Mock<IPropertyValueCollection>(); 

    const string name = "SomeNameValue"; 
    const string mailAddress = "SomeMailAddress"; 

    nameMockPropertyValueCollection.SetupGet(pvc => pvc.Value).Returns(name); 
    mailMockPropertyValueCollection.SetupGet(pvc => pvc.Value).Returns(mailAddress); 
    mockPropertyCollection.SetupGet(pc => pc["name"]).Returns(nameMockPropertyValueCollection.Object); 
    mockPropertyCollection.SetupGet(pc => pc["mail"]).Returns(mailMockPropertyValueCollection.Object); 
    mockDirectoryEntry.SetupGet(de => de.Properties).Returns(mockPropertyCollection.Object); 
    mockSearchResult.Setup(sr => sr.GetDirectoryEntry()).Returns(mockDirectoryEntry.Object); 
    mockResultCollection.Setup(results => results.GetEnumerator()).Returns(new List<ISearchResult> { mockSearchResult.Object }.GetEnumerator()); 
    mockSearcher.Setup(searcher => searcher.FindAll()).Returns(mockResultCollection.Object); 

    var queryer = new ActiveDirectoryQueryer(mockSearcher.Object); 
    queryer.GetAllMailEntries(); 
    Assert.AreEqual(1, queryer.MailEntries.Count()); 
    var entry = queryer.MailEntries.Single(); 
    Assert.AreEqual(name, entry.Name); 
    Assert.AreEqual(mailAddress, entry.EmailAddress); 
} 

擁有這麼多接口和包裝類是否正常? (因爲.NET類型不能實現我的接口,所以包裝是必需的。)

+0

我簡要回顧你的設置和以下很快襲擊我。而不是靜態的「BuildNewADUser」功能。把它放到一個新的服務(一個接口)中,並給它它所需要的。例如,IActiveDirectoryUserFactory.Create(Guid id,字符串名稱,字符串電子郵件)返回一個ActiveDirectoryUser。現在,您可以更輕鬆地單元測試您的初始類,只需模擬一個界面,這是一種方法。 –

+0

@Atoms謝謝,我有點喜歡這個想法,但我沒有看到如何讓我不嘲笑'DirectorySearcher'。 –

回答

2

我認爲我的問題是太緊密地鏡像.NET結構。我不應該把每一種.NET類型都封裝在一起,直到我接觸到原始類型。相反,我應該抓住第一次機會,儘快刪除所有依賴項。在這種情況下,它是DirectorySearcher類和FindAll方法。

DirectorySearcher.FindAll返回一個SearchResultCollection,而不是將我的「包裝器」類看作是.NET類型的適配器,我應該更多地使用它。

忽略的IDisposable和其他不必要的代碼實現,我的包裝已經是這樣的:

public interface IDirectorySearcher : IDisposable { 
    ISearchResultCollection FindAll(); 
} 

class DirectorySearcherWrapper : IDirectorySearcher { 
    DirectorySearcher mDirectorySearcher; 

    DirectorySearcherWrapper(DirectorySearcher pDirectorySearcher) { 
     mDirectorySearcher = pDirectorySearcher; 
    } 

    public static IDirectorySearcher Wrap(DirectorySearcher pDirectorySearcher) { 
     return new DirectorySearcherWrapper(pDirectorySearcher); 
    } 

    public ISearchResultCollection FindAll() { 
     return SearchResultCollectionWrapper.Wrap(mDirectorySearcher.FindAll()); 
    } 
} 

相反,我要藉此機會就在這裏停止所有的依賴關係。我不必返回一個.NET類型,甚至只是一個.NET類型的包裝,我現在可以使用此接口返回任何我想要的。 IE:如果我想從FindAll方法得到的是一堆ActiveDirectoryUser s,那就返回。

然後我的代碼變成:

public interface IDirectorySearcher : IDisposable { 
    IEnumerable<ActiveDirectoryUser> FindAll(); 
} 

class DirectorySearcherWrapper : IDirectorySearcher { 
    DirectorySearcher mDirectorySearcher; 

    DirectorySearcherWrapper(DirectorySearcher pDirectorySearcher) { 
     mDirectorySearcher = pDirectorySearcher; 
    } 

    public static IDirectorySearcher Wrap(DirectorySearcher pDirectorySearcher) { 
     return new DirectorySearcherWrapper(pDirectorySearcher); 
    } 

    public IEnumerable<ActiveDirectoryUser> FindAll() { 
     return 
     mDirectorySearcher 
     .FindAll() 
     .Cast<SearchResult>() 
     .Select(result => result.GetDirectoryEntry()) 
     .Select(/*BuildNewADUser*/) 
     .ToList(); 
    } 
} 

而且GetAllMailEntries方法變得簡單:

public void GetAllMailEntries() { 
    MailEntries = mSearcher.FindAll(); 
} 

而且單元測試變爲:

[TestMethod] 
public void TestGetAllMailEntries2() { 
    var mockSearcher = new Mock<IDirectorySearcher>(); 

    mockSearcher 
    .Setup(s => s.FindAll()) 
    .Returns(new[] { 
     ActiveDirectoryUser.Create(new Guid(), "Name", "EmailAddress") 
    }); 

    var queryer = new ActiveDirectoryQueryer(mockSearcher.Object); 
    queryer.GetAllMailEntries(); 
    Assert.AreEqual(1, queryer.MailEntries.Count()); 
    var entry = queryer.MailEntries.Single(); 
    Assert.AreEqual("Name", entry.Name); 
    Assert.AreEqual("EmailAddress", entry.EmailAddress); 
} 
+0

這看起來好多了!我想你正在搞清楚。上面的我的建議,正如你自己學到的,很大程度上是關於去除外部依賴(特別是在討厭的靜態方法中)。依賴關係很好,但如果它們都是實現的內部組件,那通常是理想的。在接口中公開外部依賴關係的那一刻,它需要一個實現或模擬期。 –

+0

@天啊......想一想,我最終做了什麼?我將我想測試的方法提交給另一個班級,最終沒有真正進行測試......直到週三我都不會再對此進行測試,但我不完全確定這是更好的方法。 –

+0

我提出這個建議的原因是,有很多項目需要嘲諷一堂課,這是一個明顯的跡象,表明課堂做得太多。你說得對,最終在某個地方,你可能最終不得不直接使用這些活動目錄對象。但是如果你的設計被正確地分解了,創建單元測試變得更加容易。換一種方式;類更容易測試,但你有更多的類來測試。根據我的經驗,這一直是件好事。 –