Как вы тестируете частные методы с NUnit?

104

Мне интересно, как правильно использовать NUnit. Во-первых, я создал отдельный тестовый проект, который использует мой основной проект в качестве ссылки. Но в этом случае я не могу тестировать частные методы. Я предполагал, что мне нужно включить свой тестовый код в свой основной код ?! - Кажется, это неправильный способ сделать это. (Мне не нравится идея поставки кода с тестами.)

Как вы тестируете частные методы с NUnit?

MrFox
источник

Ответы:

74

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

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

гарпо
источник
1
+1 Я только что столкнулся с этой проблемой, и в моем случае есть алгоритм «сопоставления», который происходит между частным и публичным смыслом, если бы я был общедоступным модульным тестом, это был бы фактически интеграционный тест. В этом сценарии я думаю, что он пытается записать тестовые точки для проблемы дизайна в коде. в этом случае мне, вероятно, следует создать другой класс, который выполняет этот «частный» метод.
Энди
140
Я полностью не согласен с этим аргументом. Модульное тестирование - это не класс, а код . Если бы у меня был класс с одним общедоступным методом и десятью частными, используемыми для создания результата общедоступного, у меня не было бы возможности гарантировать, что каждый частный метод работает должным образом. Я мог бы попытаться создать тестовые примеры для общедоступного метода, которые правильно попадут в правильный частный метод. Но тогда я понятия не имею, действительно ли был вызван частный метод, если я не доверяю общедоступному методу никогда не изменяться. Частные методы предназначены для использования в рабочей среде пользователей кода, а не для автора.
Quango
3
@Quango, в стандартной настройке тестирования «автор» является производственным пользователем кода. Тем не менее, если вы не чувствуете проблемы с дизайном, у вас есть варианты для тестирования закрытых методов без изменения класса. System.Reflectionпозволяет получать доступ и вызывать закрытые методы с использованием флагов привязки, чтобы вы могли взломать NUnit или настроить свою собственную структуру. Или (я думаю, проще) вы можете установить флаг времени компиляции (#if TESTING) для изменения модификаторов доступа, что позволит вам использовать существующие фреймворки.
harpo
23
Частный метод - это деталь реализации. Во многом смысл модульных тестов состоит в том, чтобы разрешить рефакторинг реализации без изменения поведения . Если частные методы становятся настолько сложными, что их хочется покрывать тестами индивидуально, тогда можно использовать следующую лемму: «Частный метод всегда может быть публичным (или внутренним) методом отдельного класса». То есть, если часть логики достаточно продвинута для тестирования, она, вероятно, является кандидатом на роль своего собственного класса со своими собственными тестами.
Андерс Форсгрен,
6
@AndersForsgren Но суть модульного тестирования, в отличие от интеграции или функционального тестирования, как раз в том, чтобы проверить такие детали. А покрытие кода считается важной метрикой модульного тестирования, поэтому теоретически каждая часть логики «достаточно продвинута, чтобы подвергаться тестированию».
Кайл Стрэнд
57

Хотя я согласен с тем, что основное внимание при модульном тестировании должно уделяться общедоступному интерфейсу, вы получите гораздо более детальное представление о своем коде, если также протестируете частные методы. Платформа тестирования MS позволяет это за счет использования PrivateObject и PrivateType, а NUnit - нет. Вместо этого я делаю следующее:

private MethodInfo GetMethod(string methodName)
{
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("methodName cannot be null or whitespace");

    var method = this.objectUnderTest.GetType()
        .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} method not found", methodName));

    return method;
}

Это означает, что вам не нужно идти на компромисс с инкапсуляцией в пользу тестируемости. Имейте в виду, что вам нужно изменить свой BindingFlags, если вы хотите протестировать частные статические методы. Приведенный выше пример относится только к методам экземпляра.

user1039513
источник
7
Тестирование этих небольших вспомогательных методов захватывает единичную часть в модульном тестировании. Не все подходят и для служебных классов.
Йохан Ларссон,
12
Это именно то, что мне нужно. Спасибо! Все, что мне нужно, это проверить, правильно ли настроено частное поле, и мне НЕ нужно тратить 3 часа, чтобы выяснить, как определить это через открытый интерфейс. Это существующий код, который не может быть изменен по прихоти. Забавно, как на простой вопрос можно ответить идеалистической догмой ...
Дрой
5
Это должен быть выбранный ответ, так как он единственный, который действительно отвечает на вопрос.
bavaza
6
Согласовано. Выбранный ответ имеет свои достоинства, но это ответ на вопрос «должен ли я тестировать частные методы», а не на вопрос ОП.
chesterbr
2
работает. Спасибо! Это то, что ищут многие люди. Это должен быть выбранный ответ,
Эйбл Джонсон,
47

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

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

Было бы неправильно делать эти методы общедоступными в классе, в котором они сейчас находятся. Это нарушит контракт, который вы хотите, чтобы этот класс имел.

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

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

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

Morechilli
источник
1
+1 ага! Я пришел к такому выводу, когда писал свои тесты, хороший совет!
Энди
1
Перемещение частных методов, которые вы хотите протестировать, но не открывать для пользователей API, в другой класс, который не предоставляется, и сделать их общедоступными - это именно то, что я искал.
Thomas N
спасибо @morechilli. Никогда раньше не видел InternalsVisibleTo. Тестирование стало намного проще.
Эндрю МакНотон,
Здравствуй. Недавно получил несколько голосов против без комментариев. Пожалуйста, поделитесь своим мнением, чтобы объяснить свои мысли или опасения.
Morechilli
6
Итак, если у вас есть частный метод, который вызывается в нескольких местах внутри класса, чтобы делать что-то очень конкретное, необходимое только для этого класса, и он не должен быть представлен как часть общедоступного API ... вы говорите, поместите его в помощник класс просто для проверки? С таким же успехом вы можете просто сделать его общедоступным для всего класса.
Даниэль Масиас,
21

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

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx

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

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

Если вы все еще чувствуете потребность в тестировании внутреннего, вы также можете подумать о насмешках в своем тестировании, поскольку вы, вероятно, захотите сосредоточиться на одном фрагменте кода. Мокинг - это когда вы вводите в него зависимости объектов, но внедряемые объекты не являются «реальными» или производственными объектами. Это фиктивные объекты с жестко запрограммированным поведением, упрощающим выявление поведенческих ошибок. Rhino.Mocks - популярный бесплатный фреймворк для создания макетов, который по сути напишет объекты за вас. TypeMock.NET (коммерческий продукт с доступной версией сообщества) - это более мощный фреймворк, который может имитировать объекты CLR. Очень полезно для имитации классов SqlConnection / SqlCommand и Datatable, например, при тестировании приложения базы данных.

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

Дафидд Гиддинс
источник
12
Можно тестировать внутренние методы, а не частные (с NUnit).
TrueWill
6

Я за возможность тестировать частные методы. Когда xUnit запускался, он предназначался для тестирования функциональности после написания кода. Для этого достаточно протестировать интерфейс.

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

Марк Гласс
источник
5

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

По сути, у меня есть все классы модульного тестирования в сборке, которую они тестируют, в пространстве имен UnitTest ниже значения по умолчанию для этой сборки - каждый тестовый файл заключен в:

#if DEBUG

...test code...

#endif

block, и все это означает, что а) он не распространяется в выпуске и б) я могу использовать объявления internal/ Friendlevel без прыжков.

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

public partial class TheClassBeingTested
{
    private int TheMethodToBeTested() { return -1; }
}

в основных классах сборки и в тестовом классе:

#if DEBUG

using NUnit.Framework;

public partial class TheClassBeingTested
{
    internal int NUnit_TheMethodToBeTested()
    {
        return TheMethodToBeTested();
    }
}

[TestFixture]
public class ClassTests
{
    [Test]
    public void TestMethod()
    {
        var tc = new TheClassBeingTested();
        Assert.That(tc.NUnit_TheMethodToBeTested(), Is.EqualTo(-1));
    }
}

#endif

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

Стюарт Вуд
источник
4

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

Максим Руиллер
источник
3

Приносим извинения, если это не отвечает на вопрос, но такие решения, как использование отражения, операторов #if #endif или отображение частных методов, не решают проблему. Может быть несколько причин, по которым закрытые методы не становятся видимыми ... что, если это производственный код, и команда, например, ретроспективно пишет модульные тесты.

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

Ритеш
источник
2

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

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

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

Если ваш клиент может запустить Test-Suite и увидеть, что предоставленный вами код действительно «работает», я не вижу в этом проблемы (если вы не разглашаете свой IP-адрес). В каждый выпуск я включаю отчеты об испытаниях и отчеты о покрытии кода.

Тигрень
источник
Некоторые заказчики стараются ограничить бюджет и начинают обсуждение объема тестов. Этим людям трудно объяснить, что написание теста происходит не потому, что вы плохой программист и не доверяете своим навыкам.
MrFox
1

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

Итак, если вы действительно хотите протестировать внутренних членов, вы можете использовать один из следующих подходов:

  1. Сделайте своего участника публичным. Во многих книгах авторы предлагают такой подход как простой
  2. Вы можете сделать своих участников внутренними и добавить InternalVisibleTo в сборку
  3. Вы можете сделать члены класса защищенными и унаследовать свой тестовый класс от тестируемого класса.

Пример кода (псевдокод):

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}
буржуй
источник
Кажется, что таракан спрятан внутри вашего примера кода;) Метод внутри приспособления NUnit должен быть общедоступным, иначе вы получите Message: Method is not public.
Gucu112
@ Gucu112 Спасибо. Я починил это. В общем, это не имеет значения, так как цель - показать подход с точки зрения дизайна.
буржуй
1

Вы можете сделать свои методы защищенными внутренними, а затем использовать их assembly: InternalsVisibleTo("NAMESPACE") в пространстве имен тестирования.

Следовательно, НЕТ! Вы не можете получить доступ к частным методам, но это обходной путь.

Фургальный
источник
0

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

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

Я, кстати, парень, занимающийся Java, так что package visiblilty в C # можно назвать совершенно другим. Достаточно сказать, что это когда два класса должны находиться в одном пространстве имен, чтобы получить доступ к этим методам.

Fylke
источник