Как вы тестируете приватные методы? - PullRequest
466 голосов
/ 30 октября 2008

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

Как правильно это сделать?

Ответы [ 31 ]

8 голосов
/ 30 октября 2008

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;
    }
5 голосов
/ 04 ноября 2008

В CodeProject есть статья, в которой кратко обсуждаются плюсы и минусы тестирования частных методов. Затем он предоставляет некоторый код отражения для доступа к закрытым методам (аналогично коду, представленному Маркусом выше). Единственная проблема, с которой я столкнулся в этом примере, заключается в том, что код не учитывает перегруженные методы.

Вы можете найти статью здесь:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

4 голосов
/ 30 октября 2008

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

4 голосов
/ 30 октября 2008

Объявите их internal, а затем используйте InternalsVisibleToAttribute, чтобы разрешить вашей сборочной тестовой сборке их видеть.

4 голосов
/ 03 июля 2014

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

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

По этой причине вам следует избегать использования InternalsVisibleToAtrribute.

Вот замечательный доклад Иана Купера, который освещает эту тему: Ян Купер: TDD, где все пошло не так

3 голосов
/ 16 декабря 2015

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

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");
3 голосов
/ 16 июля 2015

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

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

  /**
   *
   * @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));

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

3 голосов
/ 01 мая 2010

Иногда бывает полезно протестировать частные объявления. По сути, у компилятора есть только один открытый метод: Compile (строка outputFileName, params string [] sourceSFileNames). Я уверен, что вы понимаете, что было бы сложно протестировать такой метод без проверки каждого «скрытого» объявления!

Вот почему мы создали Visual T #: чтобы упростить тестирование. Это бесплатный язык программирования .NET (совместим с C # v2.0).

Мы добавили оператор «.-». Это просто ведет себя как "." оператор, за исключением того, что вы также можете получить доступ к любой скрытой декларации из ваших тестов, не меняя ничего в тестируемом проекте.

Взгляните на наш веб-сайт: скачать это бесплатно .

3 голосов
/ 06 июня 2011

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

Кроме того, на языке сценариев (с возможностями OO, такими как Python, Ruby и PHP), вы можете сами выполнять проверку файла при запуске. Хороший быстрый способ убедиться, что ваши изменения ничего не сломали. Это, очевидно, делает масштабируемое решение для тестирования всех ваших классов: просто запустите их все. (Вы также можете сделать это на других языках с помощью void main, которая также всегда выполняет свои тесты).

2 голосов
/ 31 октября 2008
CC -Dprivate=public

"CC" - это компилятор командной строки в системе, которую я использую. -Dfoo=bar соответствует #define foo bar. Таким образом, эта опция компиляции эффективно меняет все личные вещи на публичные.

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