Как сделать методы, основанные на методах расширения, тестируемыми? - PullRequest
4 голосов
/ 28 мая 2009

У меня есть метод расширения со следующей подписью (в классе BuildServerExtensions) ::

public static IEnumerable<BuildAgent> GetEnabledBuildAgents(
                                          this IBuildServer buildServer,
                                          string teamProjectName)
{
    // omitted agrument validation and irrelevant code
    var buildAgentSpec = buildServer.CreateBuildAgentSpec(teamProjectName);
}

И еще один метод, который вызывает первый (в классе BuildAgentSelector):

public BuildAgent Select(IBuildServer buildServer, string teamProjectName)
{
    // omitted argument validation
    IEnumerable<BuildAgent> serverBuildAgents = 
        buildServer.GetEnabledBuildAgents(teamProjectName);

    // omitted - test doesn't get this far
}

И я пытаюсь проверить это, используя MSTest и Rhino.Mocks (v3.4) с:

[TestMethod]
public void SelectReturnsNullOnNullBuildAgents()
{
    Mocks = new MockRepository();
    IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();

    BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
    using (Mocks.Record())
    {
        Expect.Call(buildServer.GetEnabledBuildAgents(TeamProjectName)).Return(null);
    }

    using (Mocks.Playback())
    {
        BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);

        Assert.IsNull(buildAgent);
    }
}

Когда я запускаю этот тест, я получаю:

System.InvalidOperationException:

Предыдущий метод IBuildServer.CreateBuildAgentSpec("TeamProjectName"); требует возвращаемого значения или исключения для выброса.

Это, очевидно, вызывает реальный метод расширения, а не тестовую реализацию. Моя следующая склонность была попробовать:

Expect.Call(BuildServerExtensions.GetEnabledBuildAgents(buildServer, TeamProjectName))
      .Return(null);

Потом я заметил, что мои ожидания от Rhino.Mocks перехватить это, вероятно, были неуместны.

Вопрос: как устранить эту зависимость и сделать метод Select тестируемым?

Обратите внимание, что метод расширения и классы BuildAgentSelector находятся в одной сборке, и я предпочел бы избегать изменения этого параметра или необходимости обращаться к чему-либо, кроме метода расширения, хотя я бы подумал о другой моделирующей среде, если бы знал, что она справится с этим ситуация.

Ответы [ 3 ]

4 голосов
/ 31 мая 2009

Ваш метод расширения написан довольно хорошо. Это свободный от побочных эффектов метод, расширяющий интерфейс, а не конкретный класс. Тебя почти нет, но тебе просто нужно пойти немного дальше. Вы пытаетесь смоделировать метод расширения .GetEnabledBuildAgents (...) ... однако это на самом деле не является поддельным (с помощью чего-либо, кроме TypeMock Isolator, который является единственной вещью, которая может фактически высмеивать статику на данный момент ... однако это довольно дорого .)

Вы на самом деле заинтересованы в том, чтобы высмеивать метод в IBuildAgent, который ваш метод расширения вызывает внутри: .CreateBuildAgentSpec (...). Если вы обдумаете это, насмешка над методом CreateBuildAgentSpec решит вашу проблему. Метод расширения является «чистым», и поэтому действительно не нуждается в издевательстве. Он не имеет состояния и не вызывает побочных эффектов. Он вызывает единственный метод в интерфейсе IBuildAgent ... который является первой подсказкой, которая направляет вас к тому, что действительно необходимо смоделировать.

Попробуйте следующее:

[TestMethod]
public void SelectReturnsNullOnNullBuildAgents()
{
    Mocks = new MockRepository();
    IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();

    BuildAgent agent = new BuildAgent { ... }; // Create an agent
    BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
    using (Mocks.Record())
    {
        Expect.Call(buildServer.CreateBuildAgentSpec(TeamProjectName)).Return(new List<BuildAgent> { agent });
    }

    using (Mocks.Playback())
    {
        BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);

        Assert.IsNull(buildAgent);
    }
}

Создавая экземпляр BuildAgent и возвращая его в список , вы фактически возвращаете IEnumerable , с которым может работать ваш метод Select. Это должно помочь вам. Возможно, вам придется выполнить некоторые дополнительные проверки, если простого возврата базового экземпляра BuildAgent недостаточно, или если вам нужно более одного. Когда дело доходит до насмешливых результатов, которые должны быть возвращены, Rhino.Mocks может быть РЕАЛЬНОЙ болью в тылу, чтобы работать с. Если у вас возникли проблемы (которые, учитывая мой опыт работы с вами, вполне вероятно), я рекомендую вам попробовать Moq , так как это гораздо более приятная и удобная для тестирования среда для работы с , Это не требует репозитория, и устраняет запись / Воспроизведение и использование () тяжелой нотации, которую требовал Rhino.Mocks. Moq также предоставляет дополнительные возможности, которые другие фреймворки пока не предлагают, что, как только вы попадаете в более тяжелые сценарии насмешек, вы влюбитесь (то есть в методы It. *).

Надеюсь, это поможет.

1 голос
/ 02 июня 2009

После перерыва и возвращения с новой головой я понял, что на самом деле немного смешиваю проблемы в классе BuildAgentSelector. Я получаю агентов и выбираю их. Разделяя эти две проблемы и передавая агенты для выбора непосредственно конструктору BuildAgentSelector (или делегату / интерфейсу для этого), я могу разделить проблемы, удалить зависимости как от параметров buildServer и teamProjectName, так и упростить интерфейс в процессе. Это также позволило получить результаты тестируемости, которые я искал в классе BuildAgentSelector. Я тоже могу отдельно протестировать метод расширения.

Однако, в конце концов, он просто перенес проблему тестирования в другое место. Это лучше, потому что проблема лучше, но ответ jrista решает проблему независимо от того, где она находится.

Все еще немного некрасиво издеваться над вторым слоем под тестируемым кодом. По сути, мне нужно взять макет успешного пути из моего тестирования метода расширения и повторно использовать этот код в других моих тестах - не сложно, но немного раздражает.

Я попробую MOQ и постараюсь быть слишком довольным написанием методов расширения.

0 голосов
/ 31 мая 2009

Тест вызывает метод реального расширения, потому что он единственный. При создании макета IBuildServer не создается тестовая реализация, поскольку метод не является членом IBuildServer.

Не существует чистого решения для этого, используя настройки, которые у вас есть сейчас.

Теоретически TypeMock будет имитировать статический класс, но рефакторинг методов расширения обеспечит большую тестируемость.

...