2015-08-24 7 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. Теперь вы можете MUCH легче тестировать свой начальный класс, только имея в виду один интерфейс, и это один из методов. –

+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

Причина, по которой я сделал предложение, состоит в том, что наличие множества элементов, требующих насмешки над одним классом, является явным признаком того, что класс делает слишком много. Вы правы, что, в конце концов, где-то вам может понадобиться работать непосредственно с этими активными объектами каталога. Но если ваш дизайн правильно разбит, становится легче создавать модульные тесты. Перефразируй; классы легче тестировать, но у вас есть больше тестов для тестирования. По моему опыту это всегда было хорошо. –

Смежные вопросы