Модульное тестирование частных методов в C # - PullRequest
241 голосов
/ 03 февраля 2012

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, и, следовательно, добавление списка завершается неудачей.

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

Ответы [ 10 ]

572 голосов
/ 25 марта 2013

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

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
248 голосов
/ 03 февраля 2012

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

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

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

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

84 голосов
/ 04 августа 2013

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

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

enter image description here

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

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

37 голосов
/ 03 февраля 2012

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

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

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
8 голосов
/ 01 января 2019

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

В другихслова ...

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

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

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

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

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

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

private double TranslateMeasurementIntoLinear (double actualMeasurement);

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

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

Первое: всех с наступающим Новым годом!

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

Третье: модификатор "public" - это религия, а не решение.

6 голосов
/ 13 марта 2014

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

Обратите внимание, что уровень доступа метода должен измениться на [защищенный]

Пример кода:

public class Order
{
    public int Quantity { get; set; }

    protected bool OrderIsBig ()
    {
        //This is the method we want to test. It needs to be protected in stead of private. Else... no cigar
        return Quantity > 100;
    }
}

//Use this wrapper class in your unit test.
public class FakeOrder : Order
{

    public bool Call_OrderIsBig()
    {
        //This makes the actual call to the protected method "OrderIsBig"
        return OrderIsBig();
    }
}

Код модульного теста может выглядеть следующим образом:

FakeOrder order = new FakeOrder();
order.Quantity = 200;

bool isBig = order.Call_OrderIsBig();   //Make a call to a public method of the FakeOrder class which in turn makes a call to the protected method.
2 голосов
/ 01 февраля 2018

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

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

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

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

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

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

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

0 голосов
/ 12 июля 2019

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

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

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

ОДНАКО ...

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

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

0 голосов
/ 20 августа 2016

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

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