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

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

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


person MrFox    schedule 30.10.2008    source источник


Ответы (13)


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

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

person harpo    schedule 30.10.2008
comment
Вы совершенно правы. Если есть какая-то частная логика, которую нужно срочно протестировать (из-за ошибок frequnet), тогда я мог бы подумать о декомпозиции, а не о тестировании внутренних схем. - person MrFox; 30.10.2008
comment
+1 Я только что столкнулся с этой проблемой, и в моем случае есть алгоритм сопоставления, который происходит между частным и общедоступным, что означает, что если бы я был общедоступным модульным тестом, это был бы фактически интеграционный тест. В этом сценарии я думаю, что он пытается записать тестовые точки для проблемы дизайна в коде. в этом случае мне, вероятно, следует создать другой класс, который выполняет этот частный метод. - person andy; 19.07.2010
comment
@MrFox на самом деле вся частная логика доступна внешнему миру с помощью общедоступных методов, поэтому в конечном итоге вы можете покрыть все это с помощью тестов для общедоступного API вашего класса. Однако разделение может быть лучшим вариантом, если частная логика добавляет вашему классу слишком много цикломатической сложности. - person the_joric; 21.12.2011
comment
Я полностью не согласен с этим аргументом. Модульное тестирование - это не класс, а код. Если бы у меня был класс с одним общедоступным методом и десятью частными, используемыми для создания результата общедоступного, у меня не было бы возможности гарантировать, что каждый частный метод работает должным образом. Я мог бы попытаться создать тестовые примеры для общедоступного метода, которые правильно попадут в правильный частный метод. Но тогда я понятия не имею, действительно ли был вызван частный метод, если я не доверяю общедоступному методу никогда не изменяться. Частные методы предназначены для того, чтобы быть частными для производственных пользователей кода, а не для автора. - person Quango; 17.01.2013
comment
@Quango, в стандартной настройке тестирования автор является производственным пользователем кода. Тем не менее, если вы не чувствуете проблемы с дизайном, у вас есть варианты для тестирования закрытых методов без изменения класса. System.Reflection позволяет вам получать доступ к закрытым методам и вызывать их с помощью флагов привязки, чтобы вы могли взломать NUnit или настроить свою собственную структуру. Или (я думаю, проще) вы можете установить флаг времени компиляции (#if TESTING), чтобы изменить модификаторы доступа, что позволит вам использовать существующие фреймворки. - person harpo; 17.01.2013
comment
Частный метод - это деталь реализации. Во многом смысл модульных тестов состоит в том, чтобы разрешить рефакторинг реализации без изменения поведения. Если частные методы становятся настолько сложными, что их хочется покрывать тестами индивидуально, то можно использовать следующую лемму: частный метод всегда может быть публичным (или внутренним) методом отдельного класса. То есть, если часть логики достаточно продвинута для тестирования, она, вероятно, является кандидатом на то, чтобы быть ее собственным классом со своими собственными тестами. - person Anders Forsgren; 22.10.2013
comment
@AndersForsgren Но суть модульного тестирования, в отличие от интеграционного или функционального тестирования, как раз в том, чтобы проверить такие детали. Покрытие кода считается важной метрикой модульного тестирования, поэтому теоретически каждая логика достаточно развита, чтобы подвергаться тестированию. - person Kyle Strand; 30.01.2019

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

person user1039513    schedule 27.01.2012
comment
Тестируя частные методы, вы, похоже, усложняете выполнение всего цикла TDD: красный- ›зеленый-› рефакторинг. Если мне придется постоянно изменять свои тесты для рефакторинга, я зря трачу время и не уверен, что смогу действительно доверять этим тестам. Я знаю, что не все, кто пишет модульные тесты, используют TDD, но вы все равно причиняете больше боли, когда вам нужно провести рефакторинг. - person Jim D'Angelo; 27.01.2012
comment
Не могли бы вы привести пример необходимости изменения ваших тестов? Я не понимаю твою точку зрения. - person user1039513; 30.01.2012
comment
Тестирование этих небольших вспомогательных методов захватывает единичную часть в модульном тестировании. Не все подходят и для служебных классов. - person Johan Larsson; 28.09.2012
comment
Это именно то, что мне нужно. Спасибо! Все, что мне нужно было, это проверить, правильно ли настроено частное поле, и мне НЕ нужно было тратить 3 часа, чтобы выяснить, как определить это через общедоступный интерфейс. Это существующий код, который не может быть изменен по прихоти. Забавно, как на простой вопрос можно ответить идеалистической догмой ... - person Droj; 10.11.2012
comment
Это должен быть выбранный ответ, поскольку он единственный, который действительно отвечает на вопрос. - person bavaza; 10.12.2013
comment
Согласованный. Выбранный ответ имеет свои достоинства, но это ответ на вопрос, должен ли я тестировать частные методы, а не вопрос OP. - person chesterbr; 13.03.2014
comment
работает. Спасибо! Это то, что ищут многие люди. Это должен быть выбранный ответ, - person Able Johnson; 02.02.2016

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

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

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

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

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

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

person morechilli    schedule 30.10.2008
comment
+1 ага! Я пришел к такому выводу, когда писал свои тесты, хороший совет! - person andy; 19.07.2010
comment
Перемещение частных методов, которые вы хотите протестировать, но не предоставлять пользователям API, в другой класс, который не предоставляется, и сделать их общедоступными - это именно то, что я искал. - person Thomas N; 09.04.2013
comment
спасибо @morechilli. Никогда раньше не видел InternalsVisibleTo. Тестирование стало намного проще. - person Andrew MacNaughton; 30.10.2014
comment
Привет. Недавно получил несколько голосов против без комментариев. Пожалуйста, поделитесь своим мнением, чтобы объяснить свои мысли или опасения. - person morechilli; 12.02.2015
comment
Итак, если у вас есть частный метод, который вызывается в нескольких местах в классе, чтобы делать что-то очень конкретное, необходимое только этому классу, и он не должен отображаться как часть общедоступного API ... вы говорите, поместите его в помощник класс просто для проверки? С таким же успехом вы можете просто сделать его общедоступным для всего класса. - person Daniel Macias; 21.10.2015
comment
Прошло 7 лет с тех пор, как я написал этот ответ ... глядя на него сейчас, я бы сказал, что сохранение состояния - еще одно интересное соображение. Альтернативой перемещению метода во вспомогательный класс было бы обеспечение того, чтобы метод был статическим, таким образом, устраняя его способность изменять состояние в объекте и, следовательно, возможно, устраняя проблемы, связанные с его общедоступностью (часто переход к вспомогательному классу привел бы к к этому же изменению). Тогда решение становится для API чистотой. - person morechilli; 21.10.2015
comment
Хороший ответ - вот как я поступил бы с проектом коричневого поля, в котором нет тестов, и вы еще не можете подкрепить код модульным тестом из-за этого сценария, поэтому начните рефакторинг, переместите код в общедоступный метод во вспомогательном классе и верните этот класс интерфейсом, если вам нужно для DI. Затем это можно проверить (мне здесь нравится nunit). - person Trevor; 25.05.2018

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

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

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

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

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

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

person Dafydd Giddins    schedule 30.10.2008
comment
Можно тестировать внутренние методы, а не частные (с NUnit). - person TrueWill; 29.05.2012

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

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

person Mark Glass    schedule 16.07.2013

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

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

#if DEBUG

...test code...

#endif

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

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

person Stuart Wood    schedule 19.09.2014

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

person Maxime Rouiller    schedule 30.10.2008

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

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

person Ritesh    schedule 02.05.2011

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

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

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

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

person Tigraine    schedule 30.10.2008
comment
Некоторые заказчики стараются ограничить бюджет и начинают обсуждать объем тестов. Этим людям трудно объяснить, что написание теста происходит не потому, что вы плохой программист и не доверяете своим собственным навыкам. - person MrFox; 30.10.2008

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

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

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

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

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{
    
    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}
person Maxim Kitsenko    schedule 03.11.2016
comment
Кажется, что таракан спрятан внутри вашего примера кода;) Метод внутри приспособления NUnit должен быть общедоступным, иначе вы получите Message: Method is not public. - person Gucu112; 25.11.2017
comment
@ Gucu112 Спасибо. Я починил это. В общем, это не имеет значения, так как цель - показать подход с точки зрения дизайна. - person Maxim Kitsenko; 27.11.2017

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

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

person Furgalicious    schedule 31.05.2016

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

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

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

person Fylke    schedule 30.10.2008

Если вам нужно получить доступ к нестатическому частному методу класса, попробуйте следующее:

class Foo 
{
    private int Sum(int num1, int num2)
    {
        return num1 + num2;
    }
}
MethodInfo sumPrivate = 
    typeof(Foo).GetMethod("Sum", BindingFlags.NonPublic | BindingFlags.Instance);

int sum = (int)sumPrivate.Invoke(new Foo(), new object[] { 2, 5 });
// 7
person Rohim Chou    schedule 07.09.2020