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




unit test c# примеры (20)

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

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

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

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

ОБНОВИТЬ

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

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


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

Как правило, я обычно за рефакторинг «производственного» кода, чтобы его было проще тестировать. Тем не менее, я не думаю, что это был бы хороший звонок здесь. Хороший модульный тест (обычно) не должен заботиться о деталях реализации класса, а только о его видимом поведении. Вместо того, чтобы выставлять внутренние стеки тесту, вы можете проверить, что класс возвращает страницы в том порядке, в котором вы ожидаете это после вызова 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());
    }
}

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

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

Когда:

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

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


Как широко отмечается в комментариях других, модульные тесты должны быть ориентированы на общедоступный 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. Если вам это действительно нужно, отражение может быть использовано в модульных тестах для доступа к частным методам экземпляра.


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


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


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


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

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

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

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

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

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


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

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

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

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


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

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


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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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


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


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


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


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

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

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

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

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


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

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


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

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

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


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

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


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








unit-testing