Я будую бібліотеку класів, яка матиме деякі публічні та приватні методи. Я хочу мати можливість тестувати приватні методи (в основному під час розробки, але також це може бути корисно для подальшого рефакторингу).
Який правильний спосіб це зробити?
Я будую бібліотеку класів, яка матиме деякі публічні та приватні методи. Я хочу мати можливість тестувати приватні методи (в основному під час розробки, але також це може бути корисно для подальшого рефакторингу).
Який правильний спосіб це зробити?
Відповіді:
Якщо ви використовуєте .net, вам слід використовувати InternalsVisibleToAttribute .
#if DEBUG
навколо InternalsVisibleTo
атрибута , щоб зробити це не відноситься до коду випуску?
#if RELEASE_TEST
навколо , InternalsVisibleTo
як каже Майк, і зробити копію конфігурації побудови релізу , який визначає RELEASE_TEST
. Ви можете перевірити свій код випуску за допомогою оптимізацій, але коли ви фактично будуєте для випуску, ваші тести будуть опущені.
Якщо ви хочете перевірити приватний метод, щось може бути не так. Одиничні тести - це, як правило, призначені для перевірки інтерфейсу класу, тобто його публічних (і захищених) методів. Ви, звичайно, можете "зламати" рішення для цього (навіть якщо лише оприлюднити методи), але ви також можете розглянути:
Тестування приватних методів може бути не корисним. Однак я також іноді люблю називати приватні методи з методів тестування. Більшість часу з метою запобігання дублювання коду для генерації тестових даних ...
Microsoft пропонує два механізми для цього:
Аксесуари
Однак механізм іноді є дещо незмінним, коли мова йде про зміни інтерфейсу початкового класу. Отже, більшість випадків я уникаю цього використовувати.
Клас PrivateObject Іншим способом є використання Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject
// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );
// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );
// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
Я не згоден з філософією "ви повинні бути зацікавлені лише в тестуванні зовнішнього інтерфейсу". Це трохи схоже на те, що автомайстерня повинна мати лише тести, щоб побачити, чи крутяться колеса. Так, в кінцевому рахунку мене цікавить зовнішня поведінка, але мені подобається, що мої власні, приватні, внутрішні тести мають бути дещо конкретнішими та точнішими. Так, якщо я рефактор, можливо, мені доведеться змінити деякі тести, але якщо це не масовий рефактор, мені доведеться змінити лише декілька, і той факт, що інші (незмінені) внутрішні тести все ще працюють, є чудовим показником того, що рефакторинг пройшов успішно.
Ви можете спробувати охопити всі внутрішні випадки, використовуючи лише загальнодоступний інтерфейс, і теоретично можливо перевірити кожен внутрішній метод (або принаймні кожен, який має значення) повністю, використовуючи публічний інтерфейс, але, можливо, вам доведеться стояти на голові, щоб досягти це та зв'язок між тестовими випадками, що проходять через загальнодоступний інтерфейс, та внутрішньою частиною рішення, яке вони розроблені для тестування, можуть бути важкими або неможливими розрізнити. Зазначивши, індивідуальні тести, які гарантують, що внутрішня техніка працює належним чином, заслуговують на незначні зміни тесту, які виникають при рефакторингу - принаймні, це був мій досвід. Якщо вам доведеться внести величезні зміни у свої тести для кожного рефакторингу, то, можливо, це не має сенсу, але в такому випадку, можливо, вам слід повністю переосмислити свій дизайн.
FooService
що X
вам потрібно зробити , все, що вам слід подбати, це те, що це дійсно робиться, X
коли просять. Як це не має значення. Якщо в класі є проблеми, які не помітні через інтерфейс (навряд чи), це все-таки дійсно FooService
. Якщо це проблема , яка є видимою через інтерфейс, тест на публічних членах повинен виявити його. Вся справа в тому, що доки колесо повертається належним чином, воно може використовуватися як колесо.
PrivMethod
, тест, на PubMethod
якому дзвінки PrivMethod
повинні піддавати її? Що відбувається, коли ви перейдете SimpleSmtpService
на свій GmailService
? Раптом ваші приватні тести вказують на код, який більше не існує або, можливо, працює інакше і може вийти з ладу, навіть якщо програма може працювати ідеально, як задумано. Якщо є складна обробка, яка застосовуватиметься до обох відправників електронної пошти, можливо, вона повинна бути в тій, EmailProcessor
яку можуть використовувати обидва і перевіряти окремо?
У рідкісних випадках, коли я хотів перевірити приватні функції, я зазвичай змінив їх, щоб захистити їх, і я написав підклас з функцією загального обгортання.
Клас:
...
protected void APrivateFunction()
{
...
}
...
Підклас для тестування:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
Я думаю, що слід задати більш фундаментальне питання - це чому ви намагаєтесь протестувати приватний метод в першу чергу. Це запах коду, який ви намагаєтеся перевірити приватним методом через загальнодоступний інтерфейс цього класу, тоді як цей метод є приватним через певну причину, оскільки це деталізація реалізації. Слід потурбуватися лише про поведінку публічного інтерфейсу, а не про те, як він реалізований під кришками.
Якщо я хочу перевірити поведінку приватного методу, використовуючи загальні рефактори, я можу витягнути його код в інший клас (можливо, з видимістю пакету, щоб переконатися, що він не є частиною публічного API). Тоді я можу перевірити його поведінку ізольовано.
Продукт рефакторингу означає, що приватний метод тепер є окремим класом, який став співпрацею до початкового класу. Її поведінка стане добре зрозумілою завдяки власним тестам.
Тоді я можу знущатися над його поведінкою, коли я намагаюся перевірити початковий клас, щоб потім я міг сконцентруватися на тесті поведінки публічного інтерфейсу цього класу, а не на тому, щоб перевірити комбінаторний вибух публічного інтерфейсу та поведінку всіх його приватних методів .
Я бачу це аналогічно керуванню автомобілем. Коли я за кермом автомобіля, я не заїжджаю з капотом вгору, щоб побачити, що двигун працює. Я покладаюся на інтерфейс, який надає автомобіль, а саме лічильник обертів та спідометр, щоб знати, що працює двигун. Я покладаюся на те, що машина насправді рухається, коли натискаю на педаль газу. Якщо я хочу перевірити двигун, я можу зробити перевірку цього в ізоляції. : D
Звичайно, безпосередньо тестування приватних методів може бути крайнім засобом, якщо у вас є застаріле додаток, але я вважаю за краще, щоб застарілий код був відновлений для відновлення для кращого тестування. Майкл Перо написав чудову книгу на цю саму тему. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
Приватні типи, внутрішні та приватні члени - це так з якоїсь причини, і часто ви не хочете возитися з ними безпосередньо. І якщо ви це зробите, швидше за все, ви зламаєтесь пізніше, оскільки немає гарантії, що хлопці, які створили ці збори, збережуть приватні / внутрішні реалізації як такі.
Але, часом, роблячи певні хаки / дослідження збірних чи сторонніх зборів, я вже в кінцевому підсумку хотів ініціалізувати приватний клас чи клас із приватним чи внутрішнім конструктором. Або, часом, коли я маю справу з попередньо складеними застарілими бібліотеками, які я не можу змінити - я закінчую писати деякі тести на приватний метод.
Таким чином, народився AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - це швидкий клас обгортки, який полегшить роботу, використовуючи динамічні функції та відображення C # 4.0.
Ви можете створювати внутрішні / приватні типи типу
//Note that the wrapper is dynamic
dynamic wrapper = AccessPrivateWrapper.FromType
(typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");
//Access the private members
wrapper.PrivateMethodInPrivateClass();
Ну, ви можете розділити тест приватного методу двома способами
Ви можете створити екземпляр PrivateObject
класу, синтаксис виглядає наступним чином
PrivateObject obj= new PrivateObject(PrivateClass);
//now with this obj you can call the private method of PrivateCalss.
obj.PrivateMethod("Parameters");
Можна використовувати рефлексію.
PrivateClass obj = new PrivateClass(); // Class containing private obj
Type t = typeof(PrivateClass);
var x = t.InvokeMember("PrivateFunc",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance, null, obj, new object[] { 5 });
PrivateClass
і скористатися цим. stackoverflow.com/questions/9122708 / ...
Я також використовував метод InternalsVisibleToAttribute. Варто також зазначити, що, якщо вам неприємно робити свої раніше приватні методи внутрішніми, щоб досягти цього, то, можливо, вони не повинні бути предметом прямих тестових одиниць.
Зрештою, ви протестуєте поведінку свого класу, а не його конкретного впровадження - ви можете змінити останнього, не змінюючи першого, і ваші тести все одно повинні пройти.
Існує 2 типи приватних методів. Статичні приватні методи та нестатичні приватні методи (інстанційні методи). Наступні 2 статті пояснюють, як з’єднати тестові приватні методи із прикладами.
MS Test має вподобану функцію, завдяки якій приватні члени та методи доступні в проекті, створюючи файл під назвою VSCodeGenAccessors
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class BaseAccessor
{
protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
{
m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
}
protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
:
this(null, type)
{
}
internal virtual object Target
{
get
{
return m_privateObject.Target;
}
}
public override string ToString()
{
return this.Target.ToString();
}
public override bool Equals(object obj)
{
if (typeof(BaseAccessor).IsInstanceOfType(obj))
{
obj = ((BaseAccessor)(obj)).Target;
}
return this.Target.Equals(obj);
}
public override int GetHashCode()
{
return this.Target.GetHashCode();
}
}
З класами, які походять від BaseAccessor
як от
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{
protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
internal SomeClassAccessor(global::Namespace.Someclass target)
: base(target, m_privateType)
{
}
internal static string STATIC_STRING
{
get
{
string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
return ret;
}
set
{
m_privateType.SetStaticField("STATIC_STRING", value);
}
}
internal int memberVar {
get
{
int ret = ((int)(m_privateObject.GetField("memberVar")));
return ret;
}
set
{
m_privateObject.SetField("memberVar", value);
}
}
internal int PrivateMethodName(int paramName)
{
object[] args = new object[] {
paramName};
int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
typeof(int)}, args)));
return ret;
}
Про CodeProject є стаття, в якій коротко обговорюються плюси і мінуси тестування приватних методів. Потім він надає деякий код відображення для доступу до приватних методів (подібний до коду, який надає Маркус вище). Єдине питання, яке я знайшов у зразку, - це те, що код не враховує перевантажених методів.
Ви можете знайти статтю тут:
Задекларуйте їх internal
, а потім скористайтеся, InternalsVisibleToAttribute
щоб дозволити їхній тестовій збірці пристрою бачити їх.
Я схильний не використовувати директиви компілятора, тому що вони швидко захаращують речі. Один із способів пом'якшити це, якщо вони вам справді потрібні, - це помістити їх у частковий клас та створити ігнорувати цей файл .cs під час створення виробничої версії.
У першу чергу не слід тестувати приватні методи свого коду. Ви повинні тестувати "загальнодоступний інтерфейс" або API, загальнодоступні речі ваших класів. API - це всі публічні методи, які ви піддаєте стороннім абонентам.
Причина полягає в тому, що після того, як ви почнете тестувати приватні методи та внутрішні засоби свого класу, ви з'єднуєте реалізацію свого класу (приватних речей) з вашими тестами. Це означає, що коли ви вирішите змінити деталі своєї реалізації, вам також доведеться змінити свої тести.
З цієї причини вам слід уникати використання InternalsVisibleToAtrribute.
Ось чудова розмова Ian Cooper, яка висвітлює цю тему: Ian Cooper: TDD, де все пішло не так
Іноді може бути добре перевірити приватні декларації. По суті, компілятор має лише один відкритий метод: Compile (string outputFileName, params string [] sourceSFileNames). Я впевнений, що ви розумієте, що перевірити такий метод було б складно без тестування кожної "прихованої" декларації!
Ось чому ми створили Visual T #: для спрощення тестів. Це безкоштовна мова програмування .NET (сумісна з C # v2.0).
Ми додали оператора ".-". Це просто так, як "." Оператор, окрім того, ви також можете отримати доступ до будь-якої прихованої декларації з ваших тестів, не змінюючи нічого у вашому тестованому проекті.
Погляньте на наш веб-сайт: завантажте його безкоштовно .
Я здивований, що ніхто ще цього не сказав, але я застосував рішення - зробити статичний метод всередині класу, щоб перевірити себе. Це дає вам доступ до всього публічного та приватного для тестування.
Крім того, мовою сценаріїв (з вміннями OO, як-от Python, Ruby та PHP) ви можете зробити тест файлів під час запуску. Приємний швидкий спосіб переконатися, що ваші зміни нічого не порушили. Це очевидно робить масштабним рішенням для тестування всіх ваших класів: просто запустіть їх усі. (ви також можете це зробити на інших мовах з пустотою main, яка завжди також виконує свої тести).
Я хочу створити тут чіткий приклад коду, який ви можете використовувати в будь-якому класі, в якому ви хочете перевірити приватний метод.
У свій клас тестових випадків просто включіть ці методи та використовуйте їх, як зазначено.
/**
*
* @var Class_name_of_class_you_want_to_test_private_methods_in
* note: the actual class and the private variable to store the
* class instance in, should at least be different case so that
* they do not get confused in the code. Here the class name is
* is upper case while the private instance variable is all lower
* case
*/
private $class_name_of_class_you_want_to_test_private_methods_in;
/**
* This uses reflection to be able to get private methods to test
* @param $methodName
* @return ReflectionMethod
*/
protected static function getMethod($methodName) {
$class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
$method = $class->getMethod($methodName);
$method->setAccessible(true);
return $method;
}
/**
* Uses reflection class to call private methods and get return values.
* @param $methodName
* @param array $params
* @return mixed
*
* usage: $this->_callMethod('_someFunctionName', array(param1,param2,param3));
* {params are in
* order in which they appear in the function declaration}
*/
protected function _callMethod($methodName, $params=array()) {
$method = self::getMethod($methodName);
return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
}
$ this -> _ callMethod ('_ someFunctionName', масив (param1, param2, param3));
Просто задайте параметри в тому порядку, як вони відображаються в початковій приватній функції
Для всіх, хто хоче запускати приватні методи без всякої безладності та безладу. Це працює з будь-якою базовою рамкою тестування, не використовуючи нічого, крім старого хорошого відображення.
public class ReflectionTools
{
// If the class is non-static
public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
{
Type t = objectUnderTest.GetType();
return t.InvokeMember(method,
BindingFlags.InvokeMethod |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static,
null,
objectUnderTest,
args);
}
// if the class is static
public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
{
MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
foreach(var member in members)
{
if (member.Name == method)
{
return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
}
}
return null;
}
}
Тоді у власних тестах ви можете зробити щось подібне:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");
MbUnit отримав гарну обгортку для цього під назвою Reflector.
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
Ви також можете встановити та отримати значення з властивостей
dogReflector.GetProperty("Age");
Щодо "тесту приватного" я згоден, що .. у ідеальному світі. немає сенсу робити тести приватного підрозділу. Але в реальному світі ви, можливо, захочете написати приватні тести замість коду рефакторингу.
Reflector
його замінили більш потужні Mirror
в Gallio / MbUnit v3.2. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Ось хороша стаття про одиничне тестування приватних методів. Але я не впевнений, що краще, щоб зробити програму, розроблену спеціально для тестування (це як створення тестів лише для тестування) або використовувати рефлексію для тестування. Напевно, більшість із нас обирає другий шлях.
На мою думку, ви повинні лише перевірити публічний API вашого класу.
Оголошення методу загальнодоступним для його тестування розбиває інкапсуляцію, викриваючи деталі реалізації.
Хороший публічний API вирішує безпосередню мету клієнтського коду та вирішує цю ціль повністю.
Я використовую клас PrivateObject . Але як було сказано раніше, краще уникати тестування приватних методів.
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
CC -Dprivate=public
"CC" - компілятор командного рядка в системі, яку я використовую. -Dfoo=bar
робить еквівалент #define foo bar
. Отже, цей варіант компіляції фактично змінює всі приватні речі на публічні.
Ось приклад, спочатку підпис методу:
private string[] SplitInternal()
{
return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
.Cast<Match>()
.Select(m => m.Value)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
}
Ось тест:
/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
object[] values = new object[] { 2, "Martin" };
XPathString xp = new XPathString(path, values);
PrivateObject param0 = new PrivateObject(xp);
XPathString_Accessor target = new XPathString_Accessor(param0);
string[] expected = new string[] {
"pair[path/to/@Key={0}]",
"Items",
"Item[Name={1}]",
"Date"
};
string[] actual;
actual = target.SplitInternal();
CollectionAssert.AreEqual(expected, actual);
}
Спосіб зробити це - створити свій метод protected
і написати тестовий прилад, який успадковує ваш клас, який потрібно перевірити. Таким чином, ви не перетворюєте свій метод public
, але ви включаєте тестування.
1) Якщо у вас є застарілий код, то єдиний спосіб перевірити приватні методи - це відображення.
2) Якщо це новий код, у вас є такі варіанти:
Я вважаю за краще метод анотації, найпростіший і найменш складний. Єдине питання полягає в тому, що ми збільшили видимість, що, на мою думку, не викликає особливих проблем. Ми завжди повинні кодувати інтерфейс, тому якщо у нас є інтерфейс MyService та реалізація MyServiceImpl, тоді ми можемо мати відповідні тестові класи, це MyServiceTest (методи тестового інтерфейсу) та MyServiceImplTest (тестові приватні методи). Усі клієнти так чи інакше повинні використовувати інтерфейс, але певним чином, хоча видимість приватного методу збільшена, це насправді не має значення.
Ви також можете оголосити це як загальнодоступне або внутрішнє (за допомогою InternalsVisibleToAttribute) під час створення в режимі налагодження:
/// <summary>
/// This Method is private.
/// </summary>
#if DEBUG
public
#else
private
#endif
static string MyPrivateMethod()
{
return "false";
}
Він роздуває код, але він буде private
в версії версії.
Ви можете створити метод тестування для приватного методу з Visual studio 2008. Коли ви створюєте тестовий модуль для приватного методу, до вашого тестового проекту додається папка Test References, а до цієї папки додається аксесуар. У логіці методу одиничного тестування також посилається на приєднувач. Цей аксесуар дозволяє вашому тестуванню пристрою викликати приватні методи в коді, який ви тестуєте. Детальніше дивіться
Також зауважте, що InternalsVisibleToAtrribute має вимогу, щоб ваша збірка була чітко названа , що створює власний набір проблем, якщо ви працюєте над рішенням, яке раніше не вимагало цієї вимоги. Я використовую аксесуар для тестування приватних методів. Дивіться це питання як приклад цього.
InternalsVisibleToAttribute
це не вимагає, щоб ваші збори були чітко названі. Зараз я використовую його в проекті, де це не так.
pre-historic
в інтернеті років, але одиничне тестування приватних методів тепер є простим і прямим вперед, Visual Studio виробляє необхідні класи аксесуарів, коли це потрібно і попередньо заповнивши логіку тестів фрагментами, що може бути близьким до того, що можна отримати для простих функціональних тестів. Див. Наприклад. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx