Сделать приватный метод общедоступным для модульного тестирования ... хорошая идея? - PullRequest
279 голосов
/ 16 августа 2011

Примечание модератора: Здесь уже опубликовано 39 ответов (некоторые из них были удалены). Перед тем как опубликовать ваш ответ, подумайте, стоит ли илиВы не можете добавить что-то значимое к обсуждению.Скорее всего, вы просто повторяете то, что уже сказал кто-то еще.


Иногда мне приходится создавать закрытый метод в классе public просто для того, чтобы написать для него несколько модульных тестов.

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

Есть ли другие люди, которые делают это, потому что мне не очень нравится это делать ??Лично я считаю, что бонусы перевешивают проблемы, связанные с обнародованием метода, который на самом деле не предоставляет никаких услуг вне класса ...

ОБНОВЛЕНИЕ

Спасибо за ответыкаждый, кажется, заинтересовал людей.Я думаю, что общее согласие заключается в том, что тестирование должно происходить через общедоступный API, поскольку это единственный способ использовать класс, и я с этим согласен.Пара случаев, которые я упомянул выше, где я делал это выше, была необычной, и я подумал, что польза от этого того стоила.

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

Ответы [ 33 ]

182 голосов
/ 27 июля 2015

Примечание: Этот ответ был первоначально опубликован на вопрос Является ли когда-либо модульное тестирование хорошей причиной для раскрытия частных переменных экземпляра через геттеры?, который был объединен с этим, так что он может быть немного специфичен для представленного здесь сценария использования.

Как общее утверждение, я, как правило, полностью за рефакторинг "производственного" кода, чтобы сделатьэто проще проверить.Тем не менее, я не думаю, что это был бы хороший звонок здесь.Хороший модульный тест (обычно) не должен заботиться о деталях реализации класса, а только о его видимом поведении.Вместо того, чтобы подвергать тестирование внутренних стеков, вы можете проверить, что класс возвращает страницы в том порядке, в котором вы ожидаете, после вызова first() или last().

Например, рассмотрим этот псевдокод:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}
147 голосов
/ 16 августа 2011

Лично я бы предпочел модульное тестирование с использованием общедоступного API и, конечно же, никогда бы не сделал приватный метод общедоступным просто , чтобы его было проще тестировать.

Если вы действительно хотите протестировать приватный метод изолированно, в Java вы можете использовать Easymock / Powermock , чтобы позволить вам сделать это.

Вы должны быть прагматичными в этом, и вы должны также знать причины, по которым вещи трудно проверить.

' Слушайте тесты ' - если это сложно проверить, это говорит вам что-то о вашем дизайне? Не могли бы вы провести рефакторинг, чтобы тест для этого метода был тривиален и легко охватывался тестированием через общедоступный API?

Вот что сказал Майкл Фезерс в « Эффективная работа с устаревшим кодом» »

"Многие люди тратят много времени, пытаясь понять, как обойти эту проблему ... реальный ответ заключается в том, что если у вас есть желание протестировать закрытый метод, метод не должен быть закрытым; общественный метод беспокоит вас, скорее всего, потому, что он является частью отдельной ответственности; он должен быть в другом классе ». [ Эффективно работает с унаследованным кодексом (2005) М. Фезерса]

62 голосов
/ 16 августа 2011

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

Тем не менее, метод, который я использую, когда хочу выполнить модульное тестирование чего-то частного в C #, - понизить защиту доступности с частного на внутренний, а затем пометить сборку модульного тестирования как сборку друга, используя * 1003.* InternalsVisibleTo .Сборке модульного тестирования будет разрешено рассматривать внутренние объекты как общедоступные, но вам не придется беспокоиться о случайном добавлении в вашу область общего пользования.

40 голосов
/ 16 августа 2011

Множество ответов предлагают только тестирование общедоступного интерфейса, но ИМХО это нереально - если метод делает что-то, что занимает 5 шагов, вы захотите протестировать эти пять шагов отдельно, а не все вместе.Это требует тестирования всех пяти методов, которые (кроме тестирования) могут быть private.

Обычный способ тестирования «частных» методов - дать каждому классу свой собственный интерфейси сделать "приватные" методы public, но не включать их в интерфейс.Таким образом, они все еще могут быть протестированы, но они не раздувают интерфейс.

Да, это приведет к раздутию файлов и классов.

Да, это делает *Спецификаторы 1011 * и private избыточны.

Да, это боль в заднице.

Это, к сожалению, одно из многих жертвоприношений , которые мы приносим, ​​чтобысделать код тестируемым .Возможно, будущий язык (или даже будущая версия C # / Java) будет иметь функции, которые сделают тестирование классов и модулей более удобным;но в то же время мы должны перепрыгнуть через эти обручи.


Некоторые считают, что каждый из этих шагов должен быть его собственным классом , но я не согласен - есливсе они имеют общее состояние, нет причин создавать пять отдельных классов, в которых было бы пять методов.Еще хуже, это приводит к раздутию файла и класса. Плюс , он заражает общедоступный API вашего модуля - все эти классы обязательно должны быть public, если вы хотите проверить их из другого модуля (либо того, либо включить тестовый код в тот жемодуль, который означает доставку тестового кода с вашим продуктом) .

26 голосов
/ 16 августа 2011

Модульное тестирование должно проверять публичный контракт, единственный способ использования класса в других частях кода. Закрытый метод - это детали реализации, его не следует тестировать, поскольку публичный API работает правильно, реализация не имеет значения и может быть изменена без изменений в тестовых примерах.

20 голосов
/ 27 июля 2015

IMO, вы должны писать свои тесты, не делая глубоких предположений о том, как ваш класс реализован внутри.Вы, вероятно, захотите провести рефакторинг позже, используя другую внутреннюю модель, но при этом сохраняя те же гарантии, что и предыдущая реализация.Ваш класс в настоящее время есть.Тестирование ваших общедоступных API на основе свойств.

18 голосов
/ 16 августа 2011

Как насчет сделать пакет закрытым?Тогда ваш тестовый код сможет увидеть его (и другие классы в вашем пакете), но он все еще скрыт от ваших пользователей.

Но на самом деле вам не следует тестировать закрытые методы.Это детали реализации, а не часть контракта.Все, что они делают, должно быть покрыто вызовом открытых методов (если у них есть код, который не выполняется публичными методами, то это должно продолжаться).Если закрытый код слишком сложен, класс, вероятно, делает слишком много вещей и нуждается в рефакторинге.

Создание открытого метода - большая задача.Как только вы это сделаете, люди смогут его использовать, и вы больше не сможете их просто поменять.

15 голосов
/ 17 августа 2011

Обновление : Я добавил более развернутый и более полный ответ на этот вопрос во многих других местах. Это можно найти в моем блоге .

Если мне когда-либо нужно сделать что-то публичное для тестирования, это обычно намекает на то, что тестируемая система не следует Принципу единой ответственности . Следовательно, отсутствует класс, который следует ввести. После извлечения кода в новый класс сделайте его общедоступным. Теперь вы можете легко проверить, и вы следите за SRP. Ваш другой класс просто должен вызвать этот новый класс с помощью композиции.

Публикация методов / использование трюков языка, таких как маркировка кода как видимого для тестовых сборок, всегда должно быть последним средством.

Например:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Измените это, введя объект валидатора.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Теперь все, что нам нужно сделать, это проверить, что валидатор вызывается правильно. Фактический процесс проверки (ранее частная логика) может быть проверен в чистой изоляции. Там не будет необходимости в сложных испытаний теста, чтобы убедиться, что эта проверка проходит успешно.

12 голосов
/ 16 августа 2011

Несколько замечательных ответов. Одна вещь, о которой я не упомянул, это то, что при разработке через тестирование (TDD) частные методы создаются на этапе рефакторинга (см. Extract Method для примера шаблона рефакторинга), и поэтому должны уже есть необходимое тестовое покрытие. Если все сделано правильно (и, конечно, вы получите смешанный пакет мнений, когда дело доходит до правильности), вам не нужно беспокоиться о том, чтобы сделать закрытый метод публичным, просто чтобы вы могли его протестировать.

10 голосов
/ 27 июля 2015

Почему бы не разделить алгоритм управления стеком на служебный класс? Утилита класса может управлять стеками и предоставлять публичные средства доступа. Его модульные тесты могут быть сосредоточены на деталях реализации. Глубокие тесты для алгоритмически хитрых классов очень полезны в устранении крайних случаев и обеспечении охвата.

Тогда ваш текущий класс может чисто делегировать классу утилит, не раскрывая никаких деталей реализации. Его тесты будут относиться к требованию нумерации страниц, как рекомендовали другие.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...