Модульное тестирование частных методов в C #

293

Visual Studio позволяет модульное тестирование частных методов через автоматически сгенерированный класс средства доступа. Я написал тест частного метода, который успешно компилируется, но во время выполнения он не работает. Довольно минимальная версия кода и теста:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Ошибка во время выполнения:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Согласно intellisense - и, следовательно, я предполагаю, что цель компилятора - имеет тип TypeA_Accessor. Но во время выполнения он имеет тип TypeA, и, следовательно, добавление списка завершается неудачно.

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

Дзюнъитиро
источник
Вам нужен аксессор для частного класса TypeB. Аксессор TypeA_Accessor предоставляет доступ к закрытым и защищенным методам TypeA. Однако TypeB не является методом. Это класс.
Дима
Accessor предоставляет доступ к закрытым / защищенным методам, элементам, свойствам и событиям. Он не предоставляет доступ к закрытым / защищенным классам в вашем классе. А закрытые / защищенные классы (TypeB) предназначены для использования только методами класса-владельца (TypeA). Таким образом, в основном вы пытаетесь добавить закрытый класс (TypeB) извне TypeA в myList, который является закрытым. Поскольку вы используете аксессор, нет проблем с доступом к myList. Однако вы не можете использовать TypeB через аксессор. Возможным решением было бы переместить TypeB за пределы TypeA. Но это может сломать ваш дизайн.
Дима
Почувствуйте, что тестирование приватных методов должно быть выполнено с помощью следующего stackoverflow.com/questions/250692/…
nate_weldon

Ответы:

276

Да, не тестируйте частные методы .... Идея модульного теста состоит в том, чтобы протестировать модуль с помощью его открытого «API».

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

Один совет / инструмент мышления ..... Существует идея, что ни один метод не должен быть приватным. Это означает, что все методы должны жить в общедоступном интерфейсе объекта ... если вы считаете, что вам нужно сделать его закрытым, он, скорее всего, живет на другом объекте.

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

Кит Николас
источник
388
Я не согласен. В OOD закрытые методы и свойства являются неотъемлемым способом не повторяться ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). Идея программирования и инкапсуляции черного ящика заключается в том, чтобы скрыть технические детали от подписчика. Поэтому действительно необходимо иметь в своем коде нетривиальные частные методы и свойства. И если это нетривиально, это должно быть проверено.
AxD
8
это не свойственно, некоторые языки OO не имеют частных методов, частные свойства могут содержать объекты с открытыми интерфейсами, которые могут быть протестированы.
Кит Николас
7
Суть этого совета в том, что если ваш объект делает что-то одно и СУХОЙ, то часто бывает мало причин иметь частные методы. Часто частные методы делают что-то, за что объект на самом деле не отвечает, но весьма полезны, если нетривиальны, то, как правило, это другой объект, который, вероятно, нарушает SRP
Кит Николас
29
Неправильно. Вы можете использовать частные методы, чтобы избежать дублирования кода. Или для проверки. Или для многих других целей, о которых Общественный мир не должен знать.
Jorj
37
Когда вы оказались в ужасно спроектированной базе кодов OO и попросили «постепенно улучшать ее», было разочарование, когда я обнаружил, что не смог провести первые тесты для частных методов. Да, возможно, в учебниках этих методов не было бы здесь, но в реальном мире у нас есть пользователи, у которых есть требования к продукту. Я не могу просто провести масштабный рефакторинг «Посмотри на мой чистый код», не получив несколько тестов в проекте. Чувствуется, как еще один пример принуждения практикующих программистов к способам, которые наивно кажутся хорошими, но не в состоянии принять во внимание настоящий беспорядок.
dune.rocks
666

Вы можете использовать PrivateObject Class

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
люк
источник
25
Это правильный ответ, теперь, когда Microsoft добавила PrivateObject.
Зои
4
Хороший ответ, но, пожалуйста, обратите внимание, что PrivateMethod должен быть «защищен» вместо «private».
HerbalMart
23
@HerbalMart: Возможно, я вас неправильно понял, но если вы предполагаете, что PrivateObject может получить доступ только к защищенным, а не к частным пользователям, вы ошибаетесь.
kmote
17
@JeffPearce Для статических методов вы можете использовать «PrivateType pt = new PrivateType (typeof (MyClass));», а затем вызывать InvokeStatic для объекта pt, как если бы вы вызывали Invoke для частного объекта.
Стив Хибберт
12
На случай, если кто-то задумался о MSTest.TestFramework v1.2.1 - классы PrivateObject и PrivateType недоступны для проектов, нацеленных на .NET Core 2.0 - для этого есть проблема github: github.com/Microsoft/testfx/issues/366
shiitake
98

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

То же самое относится и к этой дискуссии.

введите описание изображения здесь

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

Если вы хотите вызывать приватные методы, вы можете использовать класс «PrivateObject» и вызвать метод invoke. Вы можете посмотреть это подробное видео на YouTube ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ), которое показывает, как использовать «PrivateObject», а также обсуждает, является ли тестирование частных методов логичным или нет.

Шивпрасад Койрала
источник
55

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

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

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

И юнит тест:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Джефф
источник
2
Да, это то, что я предлагаю. Это немного "хаки", но, по крайней мере, они не "публичные".
Джефф
28
Это замечательный ответ только потому, что в нем не сказано «не тестируйте приватные методы», но да, он довольно «хакерский». Я бы хотел, чтобы было решение. ИМО - плохо говорить «частные методы не должны проверяться», потому что, как я понимаю, это эквивалентно «частные методы не должны быть правильными».
MasterMastic
5
Да, Кен, меня также смущают те, кто утверждает, что частные методы не должны тестироваться в модульном тестировании. Публичный API является выходом, но иногда неправильная реализация также дает правильный вывод. Или реализация привела к некоторым плохим побочным эффектам, например, удерживая ресурсы, которые не нужны, ссылаясь на объекты, предотвращая их сбор gc ... и т. Д. Если только они не предоставляют другой тест, который может охватывать частные методы, а не модульный тест, в противном случае я бы посчитал, что они не могут поддерживать 100% проверенный код.
Пони
1
Ницца. Я не знал об этом. Именно то, что мне было нужно. Допустим, я создаю сторонний компонент для использования другими, но я только хочу представить очень ограниченный API. Внутренне это довольно сложно, и я хотел бы протестировать отдельные компоненты (например, анализатор ввода или валидатор), но я не хочу делать их общедоступными. Конечный пользователь не должен знать ничего о них. Я знаю, что стандартным подходом здесь является «тестирование только вашего публичного API», но я бы предпочел вместо этого тестировать единицы кода с одной ответственностью. Это позволяет мне делать это, не делая их публичными.
Коугли
1
Я думаю, что это тоже должен быть принятый ответ. Дискуссию о тестировании закрытых методов приятно читать, но это скорее вопрос мнения. Как мне кажется, с обеих сторон были выдвинуты довольно хорошие аргументы. Если вы все еще хотите / должны тестировать приватные методы, у вас должен быть способ. Это единственный, который был предоставлен. Кроме того, вы можете сделать это для всей сборки, используя: [assembly: InternalsVisibleTo ("UT.cs")] в вашем AssemblyInfo.cs
mgueydan
27

Один из способов проверить приватные методы - это рефлексия. Это относится и к NUnit и XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Джек Дэвидсон
источник
call methods статический и нестатический ?
Kiquenet
2
Недостатком методов, зависящих от отражения, является то, что они имеют тенденцию ломаться, когда вы переименовываете методы, используя R #. Это может не быть большой проблемой для небольших проектов, но на огромных основаниях кода становится немного неприятно, когда юнит-тесты ломаются таким образом, а затем приходится обходить их и быстро исправлять. В этом смысле мои деньги идут на ответ Джеффа.
XDS
2
@XDS Жаль, что nameof () не работает, чтобы получить имя приватного метода извне его класса.
Габриэль Морен
12

Э-э-э ... Пришел сюда с точно такой же проблемой: протестировать простой , но ключевой частный метод. После прочтения этой темы, похоже, что «я хочу просверлить эту простую дыру в этом простом куске металла, и я хочу убедиться, что качество соответствует спецификациям», а затем приходит «Хорошо, это не так просто. Прежде всего, нет подходящего инструмента для этого, но вы можете построить обсерваторию гравитационных волн в своем саду. Прочитайте мою статью на http://foobar.brigther-than-einstein.org/ Во-первых, конечно, вы Я должен посещать некоторые курсы квантовой физики, тогда вам понадобятся тонны ультра-холодного азотия, и, конечно же, моя книга доступна на Amazon "...

Другими словами...

Нет, обо всем по порядку.

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

Зачем? Именно из- за архитектурных упоминаний, сделанных некоторыми авторами. Возможно, простое повторение принципов программного обеспечения может устранить некоторые недоразумения.

В этом случае обычные подозреваемые: OCP, SRP и, как всегда, KIS.

Но подожди минутку. Идея сделать все публично доступным является скорее менее политической и некой позицией. Но. Когда дело доходит до кода, даже в тогдашнем Open Source Community, это не догма. Вместо этого «сокрытие» чего-либо является хорошей практикой, чтобы облегчить знакомство с определенным API. Например, вы бы скрыли основные расчеты вашего нового рыночного стандартного блока цифровых термометров - не для того, чтобы скрыть математику за реальной измеряемой кривой для любопытных читателей кода, но чтобы предотвратить зависимость вашего кода от некоторых, возможно, неожиданно важные пользователи, которые не смогли устоять перед тем, чтобы использовать свой ранее закрытый, внутренний защищенный код для реализации своих собственных идей.

О чем я говорю?

частное двойное TranslateMeasurementIntoLinear (двойное фактическое измерение);

Легко объявить Эру Водолея или то, что сейчас называют, но если мой сенсорный датчик получит от 1,0 до 2,0, реализация Translate ... может измениться от простого линейного уравнения, которое легко понять и применимый "для всех", к довольно сложным вычислениям, использующим анализ или что-то еще, и поэтому я бы нарушил чужой код. Зачем? Потому что они не понимали сами принципы программного кодирования, даже KIS.

Короче говоря: нам нужен простой способ проверить приватные методы - без лишних слов.

Первое: всех с новым годом!

Второе: репетируйте уроки своего архитектора.

Третье: «публичный» модификатор - это религия, а не решение.

CP70
источник
5

Другой вариант, который не был упомянут, - это просто создать класс модульного теста в качестве дочернего объекта тестируемого объекта. Пример NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

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

ТЕМ НЕ МЕНИЕ...

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

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

РЕДАКТИРОВАТЬ: Я нахожу действительно веселым, что люди голосуют за этот ответ, так как я явно описываю это как плохую идею. Значит ли это, что люди соглашаются со мной? Я так растерялся.....

Роджер Хилл
источник
Это креативное решение, но довольно хакерское.
Синдзоу
3

Из книги « Эффективная работа с устаревшим кодом» :

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

По словам автора, способ исправить это - создать новый класс и добавить метод as public.

Автор объясняет далее:

«Хороший дизайн поддается тестированию, а дизайн, который не поддается тестированию, плох».

Таким образом, в этих пределах ваш единственный реальный вариант - создать метод publicв текущем или новом классе.

Кай Хартманн
источник
2

TL; DR: Извлечь приватный метод в другой класс, протестировать на этом классе; Узнайте больше о принципе SRP (принцип единой ответственности)

Кажется, вам нужно извлечь privateметод в другой класс; в этом должно быть public. Вместо того, чтобы пытаться проверить privateметод, вы должны проверить publicметод этого другого класса.

У нас есть следующий сценарий:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Нам нужно проверить логику _someLogic; но похоже, что они Class Aиграют больше роли, чем нужно (нарушают принцип ПСП); просто рефакторинг на два класса

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Таким способом someLogicможно было протестировать на A2; в A1 просто создайте поддельный A2, а затем введите его в конструктор, чтобы проверить, что A2 вызывается для функции с именем someLogic.

o0omycomputero0o
источник
0

В VS 2005/2008 вы можете использовать приватный аксессор для тестирования приватного члена, но этот путь исчез в более поздней версии VS

Аллен
источник
1
Хороший ответ в 2008 году, возможно, в начале 2010 года. Теперь, пожалуйста, обратитесь к альтернативам PrivateObject и Reflection (см. Несколько ответов выше). VS2010 имел ошибку (и) доступа, MS устарела в VS2012. Если вы не вынуждены оставаться в VS2010 или старше (> инструментальные средства сборки старше 18 лет), пожалуйста, экономьте свое время, избегая частных средств доступа. :-).
Зефан Шредер