Сделать приватный метод общедоступным для модульного тестирования… хорошая идея?

301

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


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

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

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

ОБНОВИТЬ

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

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

jcvandan
источник
2
«Изменение системы для« поддержки инструмента поддержки », если хотите, является вопиющей плохой практикой». Ну да, вроде как. Но OTOH, если ваша система не работает хорошо с установленными инструментами, возможно, что-то не так с системой.
Тило
1
Как это не дубликат stackoverflow.com/questions/105007/do-you-test-private-method ?
Санхьюн Ли
1
Не ответ, но вы всегда можете получить к ним доступ по рефлексии.
Зано
33
Нет, вы не должны их выставлять. Вместо этого вы должны проверить, что ваш класс ведет себя так, как должен, через его публичный API. И если раскрытие внутренних компонентов действительно является единственным вариантом (в чем я сомневаюсь), то вы должны по крайней мере сделать пакет средств доступа защищенным, чтобы к ним могли обращаться только классы в одном пакете (как и должно быть в вашем тесте).
JB Низет
4
Да, это так. Вполне приемлемо предоставить метод package-private (видимость по умолчанию), чтобы сделать его доступным из модульных тестов. Такие библиотеки, как Guava, даже предоставляют @VisibileForTestingаннотацию для создания таких методов - я рекомендую вам сделать это так, чтобы причина отсутствия метода была privateдолжным образом задокументирована.
Борис Паук

Ответы:

186

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

Как правило, я обычно за рефакторинг «производственного» кода, чтобы его было проще тестировать. Тем не менее, я не думаю, что это был бы хороший звонок здесь. Хороший модульный тест (обычно) не должен заботиться о деталях реализации класса, а только о его видимом поведении. Вместо того, чтобы тестировать внутренние стеки, вы можете проверить, что класс возвращает страницы в том порядке, в котором вы ожидаете их после вызова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());
    }
}
Mureinik
источник
8
Я полностью согласен с вашим ответом. Многие разработчики будут испытывать искушение использовать модификатор доступа по умолчанию и обосновывать, что хорошие платформы с открытым исходным кодом используют эту стратегию, поэтому она должна быть правильной. То, что ты можешь, не означает, что ты должен. +1
CKing
6
Хотя другие предоставили полезную информацию, я должен сказать, что это решение, которое я считаю наиболее подходящим для этой проблемы. Это делает тесты абсолютно независимыми от того, как намеченное поведение достигается внутренне. +10!
Гитахи Нг'анг'а
Кстати, это нормально, что в примере, представленном здесь @Mureinik, методы тестирования в конечном итоге охватывают больше, чем реальный тестируемый метод? Например, testFirst () вызывает next (), который на самом деле не является тестируемым методом, first (). Разве не будет чище и понятнее иметь только один метод испытаний, охватывающий все 4 метода навигации?
Гитахи Нганг'а
1
Хотя это идеальный вариант, бывают случаи, когда вам нужно проверить, что компонент сделал что-то правильно, а не только чтобы он был успешным. Это может быть сложнее для теста черного ящика. Иногда вам нужно протестировать компонент в белой коробке.
Питер Лори
1
Создание геттеров только для юнит-тестирования? Это определенно идет вразрез с OOP / Object Thinking в том, что интерфейс должен демонстрировать поведение, а не данные.
IntelliData
151

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

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

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

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

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

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

пустой
источник
5
+1 за Easymock / Powermock и никогда не делайте приватный метод публичным
Леонард Брюнингс
51
+1 За «Слушай тесты». Если вы чувствуете необходимость протестировать закрытый метод, велика вероятность, что логика этого метода настолько сложна, что он должен быть в отдельном вспомогательном классе. Затем вы можете проверить вспомогательный класс.
Фил
2
«Слушайте тесты», кажется, не работает. Вот кэшированная ссылка: webcache.googleusercontent.com/…
Джесс Телфорд,
1
Я согласен с @Phil (особенно ссылка на книгу), но я часто нахожусь в ситуации курица / яйцо с устаревшим кодом. Я знаю, что этот метод относится к другому классу, однако я не на 100% удобен рефакторинг без тестов.
Кевин
Я согласен с доктором. Если вам нужно протестировать закрытый метод, он, вероятно, должен быть в другом классе. Вы можете заметить, что другой класс / вспомогательный класс, который является закрытым методом, вероятно, станет общедоступным / защищенным.
rupweb
67

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

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

Эрик Липперт
источник
Я тоже использую эту технику, но в качестве последнего средства для унаследованного кода обычно. Если вам нужно протестировать закрытый код, у вас отсутствует класс / тестируемый класс делает слишком много. Извлеките указанный код в новый класс, который вы можете протестировать отдельно. Уловки как это должны использоваться редко.
Финглас
Но извлечение класса и только тестирование означает, что ваши тесты теряют знание о том, что исходный класс фактически использует извлеченный код. Как вы рекомендуете заполнить этот пробел в ваших тестах? И если ответ заключается в том, чтобы просто протестировать открытый интерфейс исходного класса таким образом, который вызывает использование извлеченной логики, то зачем вообще его извлекать? (Между прочим, я не хочу звучать конфронтационно - мне просто всегда было интересно, как люди уравновешивают такие компромиссы.)
Мал Росс,
@mal Я бы прошел тест на сотрудничество. Другими словами, макет для проверки нового класса был вызван правильно. Смотрите мой ответ ниже.
Finglas
Можно ли написать тестовый класс, который наследует от класса с внутренней логикой, которую вы хотите протестировать, и предоставить открытый метод для использования для тестирования внутренней логики?
Шолзингер
1
@sholsinger: в C # открытый класс не может наследовать от внутреннего класса.
Эрик Липперт
45

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

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

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

Да, это делает излишним publicи privateспецификаторы.

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

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


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

BlueRaja - Дэнни Пфлугхофт
источник
2
хороший ответ для 1-й половины
cmcginty
7
+1: лучший ответ для меня. Если автопроизводитель не проверяет часть своего автомобиля по той же причине, я не уверен, что кто-нибудь купит его. Важная часть кода должна быть протестирована, даже если мы изменим ее на общедоступную.
Тэ Сон Шин
1
Очень хороший ответ. Вы мой теплый голос Я добавил ответ. Это может вас заинтересовать.
davidxxx
29

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

кан
источник
+1, и реальный ответ: stackoverflow.com/questions/4603847/…
Raedwald
Afair, редкая ситуация, которую я когда-либо решил использовать рядовые в тестовом примере, была, когда я проверял правильность объекта - если он закрыл все обработчики JNI. Очевидно, что он не предоставляется как публичный API, однако, если этого не произойдет, происходит утечка памяти. Но, забавно, позже я тоже избавился от этого после того, как представил детектор утечки памяти как часть публичного API.
Кан
11
Это ошибочная логика. Суть юнит-теста заключается в том, чтобы отлавливать ошибки раньше, чем позже. Не тестируя частные методы, вы оставляете пробелы в тестировании, которые могут быть обнаружены не намного позже. В случае, когда закрытый метод сложен и не может быть легко реализован публичным интерфейсом, он должен быть проверен модулем.
cmcginty
6
@Casey: если закрытый метод не используется публичным интерфейсом, почему он там?
Тило
2
@DaSilva_I Ireland: В будущем будет сложно поддерживать контрольные примеры. Единственный реальный вариант использования класса - через публичный метод. Т.е. весь другой код может использовать класс только через публичный API. Таким образом, если вы хотите изменить реализацию и тестовый пример закрытого метода, если это не так, это не означает, что вы делаете что-то не так, более того, если вы тестируете только приватный, но не тестируете публичный тест в полном объеме, это не покроет некоторые потенциальные ошибки. , В идеале тестовый пример должен охватывать все возможные случаи и только случаи реального использования класса.
кан
22

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

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

SimY4
источник
6
Я согласен, что модульные тесты, которые не нужно переписывать после рефакторинга кода, лучше. И не только потому, что вы потратили бы время на переписывание юнит-тестов. Гораздо более важной причиной является то, что наиболее важной целью модульных тестов я считаю то, что ваш код по-прежнему работает правильно после рефакторинга. Если вам придется переписать все свои юнит-тесты после рефакторинга, то вы потеряете эту выгоду от юнит-тестов.
касперд
20

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

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

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

Тило
источник
+1, если вам нужно проверить своих рядовых, вы, вероятно, пропускаете урок
jk.
+1 за отсутствующий класс. Рад видеть, что другие не сразу запрыгивают в «пометку изнутри».
Finglas
Мальчик, мне пришлось пройти долгий путь вниз, чтобы найти пакет частного ответа.
Билл К
17

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

Если мне когда-либо нужно сделать что-то публичное для тестирования, это обычно намекает на то, что тестируемая система не следует принципу единой ответственности . Следовательно, отсутствует класс, который следует ввести. После извлечения кода в новый класс сделайте его общедоступным. Теперь вы можете легко проверить, и вы следите за 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(..)
    }
}

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

Finglas
источник
1
Я лично считаю, что SRP можно зайти слишком далеко, например, если я напишу службу учетных записей, которая занимается обновлением / созданием / удалением учетных записей в приложении, вы говорите мне, что вы должны создать отдельный класс для каждой операции? А как насчет валидации, например, стоит ли извлекать логику валидации в другой класс, когда она никогда не будет использоваться где-либо еще? Imo просто делает код менее читаемым, менее лаконичным и упаковывает ваше приложение лишними классами!
jcvandan
1
Все зависит от сценария. Определение одного человека «делать одно» может отличаться от другого. В этом случае для вас ясно, что закрытый код, который вы хотите протестировать, сложен / сложен в настройке. Тот факт, что вы хотите проверить это, доказывает, что он делает слишком много. Поэтому да, новый объект будет идеальным.
Finglas
1
Что касается того, чтобы сделать ваш код менее читабельным, я категорически не согласен. Наличие класса для проверки легче читать, чем встроенная проверка в другой объект. У вас все еще есть тот же объем кода, он просто не вложен в один большой класс. Я предпочел бы иметь несколько классов, которые делают одну вещь очень хорошо, чем один большой класс.
Finglas
4
@dormisher, относясь к вашей учетной записи, рассматривает SRP как «единственную причину для изменения». Если ваши операции CRUD естественно изменятся вместе, то они будут соответствовать SRP. Обычно вы меняете их, потому что схема базы данных может измениться, что естественно повлияет на вставку и обновление (хотя, возможно, не всегда удаляет).
Энтони Пеграм
@finglas что вы думаете об этом: stackoverflow.com/questions/29354729/… . Мне не хочется тестировать закрытый метод, поэтому, возможно, мне не следует разделять его на класс, но он используется в 2 других открытых методах, поэтому я бы в итоге дважды тестировал одну и ту же логику.
Родриго Руис
13

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

csano
источник
11

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

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

Альфред Армстронг
источник
10

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

Том Джефферис
источник
10

Частные методы обычно используются как «вспомогательные» методы. Поэтому они только возвращают базовые значения и никогда не работают с конкретными экземплярами объектов.

У вас есть несколько вариантов, если вы хотите проверить их.

  • Используйте отражение
  • Предоставьте доступ к пакету методов

В качестве альтернативы вы можете создать новый класс с помощью вспомогательного метода в качестве открытого метода, если он достаточно хороший кандидат для нового класса.

Здесь есть очень хорошая статья .

adamjmarkham
источник
1
Честный комментарий. Был просто вариант, возможно, плохой.
adamjmarkham
@Finglas Почему тестирование частного кода с использованием отражения - плохая идея?
Anshul
Закрытые методы @Anshul - это детали реализации, а не поведение. Другими словами, они меняются. Имена меняются. Параметры меняются. Содержание меняется. Это означает, что эти тесты будут ломаться все время. Публичные методы, с другой стороны, намного более стабильны с точки зрения API, поэтому они более устойчивы. Предоставляя свои тесты на общедоступных методах, проверяющих поведение, а не детали реализации, вы должны быть хороши.
Finglas
1
@Finglas Почему бы вам не захотеть проверить детали реализации? Это код, который должен работать, и если он будет меняться чаще, вы ожидаете, что он будет ломаться чаще, что является большей причиной для его тестирования, чем для общедоступного API. После того, как ваша кодовая база станет стабильной, скажем, когда-нибудь в будущем кто-то придет и изменит приватный метод, который нарушает внутреннюю работу вашего класса. Тест, который проверяет внутренности вашего класса, немедленно скажет им, что они что-то сломали. Да, это усложнит ведение ваших тестов, но это то, что вы ожидаете от полного охвата тестирования.
Аншул
1
@Finglas Тестирование деталей реализации не является неправильным. Это ваша позиция по этому вопросу, но это широко обсуждаемая тема. Тестирование внутренних устройств дает вам несколько преимуществ, даже если они не требуются для полного покрытия. Ваши тесты более сфокусированы, потому что вы тестируете частные методы напрямую, а не проходите через публичный API, который может смешивать ваш тест. Отрицательное тестирование проще, потому что вы можете вручную аннулировать закрытые члены и убедиться, что открытый API возвращает соответствующие ошибки в ответ на несовместимое внутреннее состояние. Есть еще примеры.
Anshul
9

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

Затем добавьте атрибут в dll

[сборка: InternalsVisibleTo ("MyTestAssembly")]

Теперь все методы видны в вашем проекте MyTestAssembly. Может быть, не идеально, но лучше, чем публичный метод публичного доступа, просто чтобы проверить его.

Петр Перак
источник
7

Используйте отражение для доступа к закрытым переменным, если вам нужно.

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

Даниил Алексюк
источник
Что бы вы сделали с пустым методом?
IntelliData
@IntelliData Используйте отражение для доступа к закрытым переменным, если вам нужно.
Даниэль Алексюк
6

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

Более того, объявив метод общедоступным, можно сказать, что через шесть месяцев (после того, как вы забыли, что единственная причина сделать метод общедоступным - для тестирования), что вы (или если вы передали проект) кто-то совершенно другой выиграл не используйте его, что может привести к непредвиденным последствиям и / или кошмару обслуживания.

beny23
источник
1
что если класс является реализацией контракта на обслуживание, и он когда-либо используется только через его интерфейс - таким образом, даже если закрытый метод стал общедоступным, он все равно не будет вызван, так как он невидим
jcvandan
Я все еще хотел бы протестировать класс с использованием открытого API (интерфейса), потому что в противном случае, если вы реорганизуете класс для разделения внутреннего метода, вам придется изменить тесты, что означает, что вам придется потратить некоторые усилия по обеспечению того, чтобы новые тесты работали так же, как старые тесты.
beny23
Кроме того, если единственный способ гарантировать, что тестовое покрытие включает каждый крайний случай в частном методе, - это вызвать его напрямую, это может указывать на то, что сложность класса требует рефакторинга, чтобы упростить его.
beny23
@dormisher - Если класс используется только когда-либо через интерфейс, вам нужно только проверить, правильно ли работают методы открытого интерфейса. Если публичные методы делают то, что они должны делать, почему вы заботитесь о частных?
Анджей Дойл
@Andrzej - я полагаю, что на самом деле не следует этого делать, но есть ситуации, когда я считаю, что он может быть действительным, например, закрытый метод, который проверяет состояние модели, возможно, стоит проверить такой метод, но такой метод должен быть проверен с помощью публичный интерфейс в любом случае
jcvandan
6

с точки зрения модульного тестирования, вы не должны добавлять больше методов; Я считаю, что вам лучше создать тестовый пример только для вашего first()метода, который будет вызываться перед каждым тестом; то вы можете позвонить в несколько раз больше - next(), previous()и last()чтобы увидеть , если результаты соответствовали вашим ожиданиям. Я думаю, что если вы не добавите больше методов в свой класс (только для целей тестирования), вы будете придерживаться принципа «черного ящика» тестирования;

Johny
источник
5

Сначала посмотрите, должен ли метод быть извлечен в другой класс и обнародован. Если это не так, сделайте его защищенным и в Java аннотируйте с помощью @VisibleForTesting .

Ноэль Яп
источник
5

В своем обновлении вы говорите, что хорошо просто протестировать с помощью публичного API. Здесь на самом деле две школы.

  1. Тестирование черного ящика

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

  2. тестирование белого ящика.

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

Я действительно не могу принять участие в обсуждении. Я просто подумал, что было бы интересно узнать, что есть два разных способа тестирования класса (или библиотеки, или чего-то еще).

bengtb
источник
4

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

Станислав Башкирцев
источник
4

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

Взгляните на это видео для действительно интересного взгляда на эту тему.

Jordão
источник
4

Вы никогда не должны позволять своим тестам диктовать ваш код. Я не говорю о TDD или других DD, я имею в виду именно то, что вы просите. Нужны ли вашему приложению эти методы, чтобы быть публичными? Если это так, то проверьте их. Если этого не произойдет, не публикуйте их только для тестирования. То же самое с переменными и другими. Пусть потребности вашего приложения диктуют код, и пусть ваши тесты проверяют, что потребность удовлетворена. (Опять же, я не имею в виду сначала тестирование или нет, я имею в виду изменение структуры классов для достижения цели тестирования).

Вместо этого вы должны «проверить выше». Проверьте метод, который вызывает закрытый метод. Но ваши тесты должны проверять потребности ваших приложений, а не ваши "решения по внедрению".

Например (псевдокод тела здесь);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

Нет смысла проверять «добавить», вместо этого вы можете проверить «книги».

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

coteyr
источник
1
«Никогда не позволяйте своим тестам принимать решения для разработки кода», - должен согласиться я. Трудно проверить - это запах кода. Если вы не можете проверить это, как вы узнаете, что он не сломан?
Альфред Армстронг
Для пояснения, я согласен, что вы не должны изменять внешний дизайн API библиотеки, например. Но вам может понадобиться рефакторинг, чтобы сделать его более тестируемым.
Альфред Армстронг,
Я обнаружил, что если вам нужен рефакторинг для тестирования, вам не стоит беспокоиться о запахе кода. У тебя что-то не так. Конечно, это личный опыт, и мы оба могли бы поспорить с достоверными данными в обоих направлениях. У меня просто никогда не было «трудно проверить», который изначально не был «плохим дизайном».
Котейр
Это решение подходит для такого метода, как books, который возвращает значение - вы можете проверить тип возвращаемого значения в assert ; но как бы вы проверили метод void ?
IntelliData
Пустые методы что-то делают, или им не нужно существовать. Проверьте то, что они делают,
coteyr
2

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

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

IanR
источник
2

Я обычно держу тестовые классы в том же проекте / сборке, что и тестируемые классы.
Так мне нужно толькоinternal видимость, чтобы сделать функции / классы тестируемыми.

Это несколько усложняет процесс сборки, который требует фильтрации тестовых классов. Я достигаю этого, называя все мои тестовые классыTestedClassTest и используя регулярные выражения для фильтрации этих классов.

Это, конечно, относится только к C # / .NET-части вашего вопроса

yas4891
источник
2

Я часто добавить метод , называемый что - то вроде validate, verify,check и т.д., к классу так , что его можно назвать , чтобы проверить внутреннее состояние объекта.

Иногда этот метод помещается в блок ifdef (я пишу в основном на C ++), чтобы он не компилировался для выпуска. Но в выпуске часто полезно предоставлять методы валидации, которые обходят деревья объектов программы, проверяя вещи.

Зан Рысь
источник
2

У Guava есть аннотация @VisibleForTesting для маркировки методов, которые увеличили область действия (пакет или общедоступный), что они иначе. Я использую аннотацию @Private для того же.

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

Когда:

  • класс становится значительно менее читаемым, в целом, разбивая его на несколько классов,
  • просто чтобы сделать его более тестируемым,
  • и предоставление некоторого тестового доступа к внутренностям сделает свое дело

похоже, что религия превосходит инженерию.

Эд Стауб
источник
2

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

hiergiltdiestfu
источник
2

Нет, потому что есть лучшие способы снятия шкуры с этой кошки.

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

Более простая идиома OO состоит в том, чтобы сделать все, что вы хотите проверить, «защищенным», а не «приватным». Испытательный комплект наследует от тестируемого класса и может затем получить доступ ко всем защищенным членам

Или вы выбираете вариант «друг». Лично это особенность C ++, которая мне нравится меньше всего, потому что она нарушает правила инкапсуляции, но это необходимо для того, как C ++ реализует некоторые функции, так что, привет.

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

Грэхем
источник
2

В .Net есть специальный класс под названием PrivateObject deigned, специально предназначенный для доступа к закрытым методам класса.

Узнайте больше об этом на MSDN или здесь на переполнение стека

(Мне интересно, что никто до сих пор не упомянул об этом.)

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

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

Йоэль Хэлб
источник
1

Как широко отмечается в комментариях других, модульные тесты должны быть ориентированы на общедоступный API. Тем не менее, за и против и в сторону оправдания, вы можете вызывать частные методы в модульном тесте, используя рефлексию. Вы, конечно, должны убедиться, что ваша безопасность JRE позволяет это. Вызов закрытых методов - это то, что Spring Framework использует со своим ReflectionUtils (см.makeAccessible(Method) Метод).

Вот небольшой пример класса с закрытым методом экземпляра.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

И пример класса, который выполняет метод частного экземпляра.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Выполнение B выведет на печать Doing something private. Если вам это действительно нужно, отражение может использоваться в модульных тестах для доступа к частным методам экземпляра.

Дэн Круз
источник
0

Лично у меня те же проблемы при тестировании частных методов, потому что некоторые инструменты тестирования ограничены. Нехорошо, когда ваш дизайн управляется ограниченными инструментами, если они не отвечают на ваши потребности, меняйте инструмент, а не дизайн. Поскольку вы просите C #, я не могу предложить хорошие инструменты тестирования, но для Java есть два мощных инструмента: TestNG и PowerMock, и вы можете найти соответствующие инструменты тестирования для платформы .NET

Xoke
источник