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





Как правило, модульное тестирование обращается к общедоступному интерфейсу класса, исходя из теории, что реализация несущественна, если результаты верны с точки зрения клиента.
Итак, NUnit не предоставляет никакого механизма для тестирования непубличных членов.
+1 Я только что столкнулся с этой проблемой, и в моем случае есть алгоритм «сопоставления», который происходит между частным и публичным смыслом, если бы я был общедоступным модульным тестом, это был бы фактически интеграционный тест. В этом сценарии я думаю, что он пытается записать тестовые точки для проблемы дизайна в коде. в этом случае мне, вероятно, следует создать другой класс, который выполняет этот «частный» метод.
@MrFox на самом деле вся частная логика доступна внешнему миру общедоступными методами, поэтому в конечном итоге вы можете покрыть все это с помощью тестов для публичного API вашего класса. Однако разделение может быть лучшим вариантом, если частная логика добавляет вашему классу слишком много цикломатической сложности.
Я полностью не согласен с этим аргументом. Модульное тестирование - это не класс, это код. Если бы у меня был класс с одним общедоступным методом и десятью частными, используемыми для создания результата общедоступного, у меня не было бы возможности гарантировать, что каждый частный метод работает должным образом. Я мог бы попытаться создать тестовые примеры для общедоступного метода, которые правильно попадут в правильный частный метод. Но тогда я понятия не имею, действительно ли был вызван частный метод, если только я не доверяю общедоступному методу никогда не изменяться. Частные методы предназначены для того, чтобы быть частными для производственных пользователей кода, а не для автора.
@Quango, в стандартной настройке тестирования, «автор» является является производственным пользователем кода. Тем не менее, если вы не чувствуете проблемы с дизайном, у вас есть варианты для тестирования закрытых методов без изменения класса. System.Reflection позволяет вам получать доступ и вызывать закрытые методы, используя флаги привязки, так что вы можете взломать NUnit или настроить свою собственную структуру. Или (я думаю, проще) вы можете установить флаг времени компиляции (#if TESTING), чтобы изменить модификаторы доступа, что позволит вам использовать существующие фреймворки.
Частный метод - это деталь реализации. Во многом смысл модульных тестов состоит в том, чтобы разрешить рефакторинг выполнение без изменения поведение. Если частные методы становятся настолько сложными, что их хочется покрывать тестами индивидуально, то можно использовать следующую лемму: «Частный метод всегда может быть публичным (или внутренним) методом отдельного класса». То есть, если часть логики достаточно продвинута для тестирования, она, вероятно, является кандидатом на роль собственного класса со своими собственными тестами.
@AndersForsgren Но суть тестирования Ед. изм, в отличие от тестирования интеграция или функциональный, как раз в том, чтобы проверить такие детали. А покрытие кода считается важной метрикой модульного тестирования, поэтому теоретически часть логики каждый «достаточно продвинута, чтобы подвергаться тестированию».
Основная цель модульного тестирования - проверить общедоступные методы класса. Эти общедоступные методы будут использовать эти частные методы. Модульное тестирование проверяет поведение общедоступных материалов.
Вы не тестируете частные функции. Есть способы использовать отражение для доступа к частным методам и свойствам. Но это непросто, и я категорически не рекомендую эту практику.
Вы просто не должны тестировать ничего, что не является общедоступным.
Если у вас есть какие-то внутренние методы и свойства, вам следует подумать о том, чтобы сделать их общедоступными или отправить свои тесты вместе с приложением (что я не вижу в этом проблемы).
Если ваш клиент может запустить Test-Suite и увидеть, что предоставленный вами код действительно «работает», я не вижу в этом проблемы (если вы не разглашаете свой IP через это). В каждый выпуск я включаю отчеты об испытаниях и отчеты о покрытии кода.
Некоторые заказчики стараются ограничить бюджет и начинают обсуждать объем тестов. Этим людям трудно объяснить, что написание теста происходит не потому, что вы плохой программист и не доверяете своим собственным навыкам.
Обычно при написании модульных тестов тестируются только общедоступные методы.
Если вы обнаружите, что у вас есть много частных методов, которые вы хотите протестировать, обычно это знак того, что вам следует реорганизовать свой код.
Было бы неправильно делать эти методы общедоступными в классе, в котором они сейчас находятся. Это нарушит контракт, который вы хотите, чтобы этот класс имел.
Возможно, будет правильным переместить их во вспомогательный класс и сделать их там общедоступными. Этот класс не может быть предоставлен вашим API.
Таким образом, тестовый код никогда не смешивается с вашим общедоступным кодом.
Аналогичная проблема заключается в тестировании частных классов, т.е. классы, которые вы не экспортируете из сборки. В этом случае вы можете явно сделать вашу сборку тестового кода другом сборки производственного кода, используя атрибут InternalsVisibleTo.
+1 ага! Я пришел к такому выводу, когда писал свои тесты, хороший совет!
Перемещение частных методов, которые вы хотите протестировать, но не предоставлять пользователям API, в другой класс, который не предоставляется, и сделать их общедоступными - это именно то, что я искал.
спасибо @morechilli. Никогда раньше не видел InternalsVisibleTo. Тестирование стало намного проще.
Привет. Недавно получил несколько голосов против без комментариев. Пожалуйста, поделитесь своим мнением, чтобы объяснить свои мысли или опасения.
Итак, если у вас есть частный метод, который вызывается в нескольких местах в классе, чтобы делать что-то очень конкретное, необходимое только этому классу, и он не должен быть представлен как часть общедоступного API ... вы говорите, поместите его в помощник класс просто для проверки? С таким же успехом вы можете просто сделать его общедоступным для всего класса.
Прошло 7 лет с тех пор, как я написал этот ответ ... глядя на него сейчас, я бы сказал, что сохранение состояния - еще одно интересное соображение. Альтернативой перемещению метода во вспомогательный класс было бы обеспечение того, чтобы метод был статическим, таким образом устраняя его способность изменять состояние в объекте и, следовательно, возможно, устраняя проблемы, связанные с его общедоступностью (часто переход к вспомогательному классу привел бы к этому же изменению). Тогда решение становится одним из чистых для API.
Хороший ответ - вот как я поступил бы с проектом коричневого поля, в котором нет тестов, и вы еще не можете подкрепить код модульным тестом из-за этого сценария, поэтому начните рефакторинг, переместите код в общедоступный метод во вспомогательном классе и верните этот класс интерфейсом, если вам нужно для DI. Затем это можно проверить (мне здесь нравится nunit).
Я бы сделал видимым пакет частных методов. Таким образом, вы сохраните его в достаточной степени приватным, но при этом сможете протестировать эти методы. Я не согласен с тем, что люди говорят, что общедоступные интерфейсы - единственные, которые следует тестировать. Часто в частных методах есть действительно критический код, который нельзя должным образом протестировать, пройдя только через внешние интерфейсы.
Так что это действительно сводится к тому, заботитесь ли вы больше о правильном коде или сокрытии информации. Я бы сказал, что видимость пакета - хороший компромисс, поскольку для доступа к этому методу кому-то нужно будет поместить свой класс в ваш пакет. Это действительно должно заставить их дважды подумать, действительно ли это разумно.
Я, кстати, парень, занимающийся Java, так что package visiblilty можно было бы назвать чем-то совершенно другим в C#. Достаточно сказать, что это когда два класса должны находиться в одном пространстве имен, чтобы получить доступ к этим методам.
Можно протестировать частные методы, объявив вашу тестовую сборку как дружественную сборку целевой сборки, которую вы тестируете. См. Ссылку ниже для получения подробной информации:
http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx
Это может быть полезно, поскольку оно в основном отделяет ваш тестовый код от вашего производственного кода. Сам я этим методом никогда не пользовался, потому что никогда не испытывал в нем необходимости. Я полагаю, что вы могли бы использовать его, чтобы попробовать и протестировать экстремальные тестовые случаи, которые вы просто не можете воспроизвести в своей тестовой среде, чтобы увидеть, как ваш код справляется с этим.
Однако, как уже было сказано, вам действительно не нужно тестировать частные методы. Вы более чем вероятно захотите реорганизовать свой код на более мелкие строительные блоки. Один совет, который может помочь вам, когда вы приступите к рефакторингу, - это попытаться подумать о домене, к которому относится ваша система, и подумать о «реальных» объектах, которые населяют этот домен. Ваши объекты / классы в вашей системе должны напрямую относиться к реальному объекту, что позволит вам изолировать точное поведение, которое должен содержать объект, а также ограничить ответственность за объекты. Это будет означать, что вы проводите рефакторинг логически, а не просто для того, чтобы дать возможность протестировать конкретный метод; вы сможете протестировать поведение объектов.
Если вы все еще чувствуете потребность в тестировании внутреннего, вы также можете подумать о насмешках в своем тестировании, поскольку вы, вероятно, захотите сосредоточиться на одном фрагменте кода. Мокинг - это когда вы вводите в него зависимости объектов, но внедряемые объекты не являются «реальными» или производственными объектами. Это фиктивные объекты с жестко запрограммированным поведением, облегчающим выявление поведенческих ошибок. Rhino.Mocks - популярный бесплатный фреймворк для создания макетов, который по сути напишет объекты за вас. TypeMock.NET (коммерческий продукт с доступной версией сообщества) - это более мощный фреймворк, который может имитировать объекты CLR. Очень полезно для имитации классов SqlConnection / SqlCommand и Datatable, например, при тестировании приложения базы данных.
Надеюсь, этот ответ даст вам немного больше информации о модульном тестировании в целом и поможет вам получить лучшие результаты от модульного тестирования.
Можно протестировать методы внутренний, а не частные (с NUnit).
Приносим извинения, если это не отвечает на вопрос, но такие решения, как использование отражения, операторов #if #endif или отображение частных методов, не решают проблему. Может быть несколько причин не делать частные методы видимыми ... что, если это производственный код, и команда, например, ретроспективно пишет модульные тесты.
Для проекта, над которым я работаю, только MSTest (к сожалению), похоже, имеет способ, используя аксессоры, для частных методов модульного тестирования.
Хотя я согласен с тем, что основное внимание при модульном тестировании должно уделяться общедоступному интерфейсу, вы получите гораздо более детальное представление о своем коде, если также протестируете частные методы. Платформа тестирования 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, если вы хотите протестировать частные статические методы. Приведенный выше пример относится только к методам экземпляра.
Тестируя частные методы, вы, кажется, усложняете выполнение всего цикла TDD: красный-> зеленый-> рефакторинг. Если мне придется постоянно изменять свои тесты для рефакторинга, я зря трачу время и не уверен, что смогу действительно доверять этим тестам. Я знаю, что не все, кто пишет модульные тесты, используют TDD, но вы все равно причиняете больше боли, когда вам нужно провести рефакторинг.
Не могли бы вы привести пример изменения ваших тестов? Я не понимаю твою точку зрения.
Тестирование этих небольших вспомогательных методов фиксирует единичную часть в модульном тестировании. Не все подходят и для служебных классов.
Это именно то, что мне было нужно. Спасибо! Все, что мне нужно было, это проверить, правильно ли настроено частное поле, и мне НЕ нужно было тратить 3 часа, чтобы выяснить, как определить это через общедоступный интерфейс. Это существующий код, который не может быть изменен по прихоти. Забавно, как на простой вопрос можно ответить идеалистической догмой ...
Это должен быть выбранный ответ, так как он единственный, который действительно отвечает на вопрос.
Согласовано. Выбранный ответ имеет свои достоинства, но это ответ на вопрос «должен ли я тестировать частные методы», а не на вопрос ОП.
работает. Спасибо! Это то, что ищут многие люди. Это должен быть выбранный ответ,
Я за возможность тестировать частные методы. Когда xUnit запускался, он предназначался для тестирования функциональности после написания кода. Для этого достаточно протестировать интерфейс.
Модульное тестирование превратилось в разработку, основанную на тестировании. Для этого приложения полезно иметь возможность тестировать все методы.
Это вопрос в последние годы, но я подумал, что поделюсь своим способом решения этого вопроса.
По сути, у меня есть все классы модульного тестирования в сборке, которую они тестируют, в пространстве имен UnitTest ниже значения по умолчанию для этой сборки - каждый тестовый файл заключен в:
#if DEBUG
...test code...
#endif
block, и все это означает, что а) он не распространяется в выпуске и б) я могу использовать объявления уровня internal / Friend без прыжков через кольцо.
Еще одна вещь, которую это предлагает, более подходящая для этого вопроса, - это использование классов 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 вскоре укажет на случайный вызов этого метода, если вы это сделаете.
Вы можете сделать свои методы защищенными внутренними, а затем использовать
assembly: InternalsVisibleTo("NAMESPACE")
в ваше тестовое пространство имен.
Следовательно, НЕТ! Вы не можете получить доступ к частным методам, но это обходной путь.
По теории модульного тестирования тестировать следует только контракт. т.е. только публичные члены класса. Но на практике разработчик обычно хочет протестировать и внутренних участников - и это неплохо. Да, это противоречит теории, но на практике иногда может быть полезно.
Итак, если вы действительно хотите протестировать внутренних членов, вы можете использовать один из следующих подходов:
Пример кода (псевдокод):
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 Спасибо. Я починил это. В общем, это не имеет значения, так как цель - показать подход с точки зрения дизайна.
Если вам нужно получить доступ к нестатическому частному методу класса, попробуйте следующее:
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
Вы совершенно правы. Если есть какая-то частная логика, которую нужно срочно протестировать (из-за часто встречающихся ошибок), тогда я мог бы подумать о декомпозиции, а не о тестировании внутренних схем.