419 Stimmen

Unit-Tests für private Methoden in C#

Visual Studio ermöglicht Unit-Tests von privaten Methoden über eine automatisch generierte Accessor-Klasse. Ich habe einen Test einer privaten Methode geschrieben, der erfolgreich kompiliert, aber zur Laufzeit fehlschlägt. Eine ziemlich minimale Version des Codes und der Test ist:

//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
}

Der Laufzeitfehler lautet:

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]'.

Laut intellisense - und damit wohl auch dem Compiler - ist target vom Typ TypeA_Accessor. Aber zur Laufzeit ist es vom Typ TypeA, und daher schlägt das Hinzufügen der Liste fehl.

Gibt es eine Möglichkeit, diesen Fehler zu beheben? Oder, vielleicht wahrscheinlicher, welche anderen Ratschläge haben andere Leute (ich sagen vielleicht "nicht private Methoden testen" und "nicht haben Unit-Tests manipulieren den Zustand der Objekte").

2voto

Amirhossein Yari Punkte 1848

Ich hatte einen anderen Ansatz, dass es für mich funktioniert. weil ich immer meine Tests im Debug-Modus ausführen, so dass ich verwendet #if DEBUG zum Hinzufügen public vor meiner privaten Methode. Meine private Methode ist also wie folgt:

public class Test
{
    #if (DEBUG)
      public
    #endif
    string PrivateMehtod()
    {
      return "PrivateMehtod called";
    }
}

1voto

Patrick Knott Punkte 1278
public static class PrivateMethodTester
{
    public static object InvokePrivateMethodWithReturnType<T>(this T testObject, string methodName, Type[] methodParamTypes, object[] parameters)
    {
        //shows that we want the nonpublic, static, or instance methods.
        var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance;

        //gets the method, but we need the methodparamtypes so that we don't accidentally get an ambiguous method with different params.
        MethodInfo methodInfo = testObject.GetType().GetMethod(methodName, flags, null, methodParamTypes, null);
        if (methodInfo == null)
        {
            throw new Exception("Unable to find method.");
        }

        //invokes our method on our object with the parameters.
        var result = methodInfo.Invoke(testObject, parameters);
        if (result is Task task)
        {
            //if it is a task, it won't resolve without forcing it to resolve, which means we won't get our exceptions.
            task.GetAwaiter().GetResult();
        }

        return result;
    }
}

Sagen wir es mal so:

        Type[] paramTypes = new Type[] { typeof(OrderTender), typeof(string) };
        var parameters = new object[] { orderTender, OrderErrorReasonNames.FailedToCloneTransaction };

        myClass.InvokePrivateMethodWithReturnType("myPrivateMethodName", paramTypes, parameters);

0voto

Allen Punkte 559

In VS 2005/2008 können Sie verwenden privater Accessor Privatperson zu testen, aber dieser Weg war verschwinden in einer späteren Version von VS

0voto

cyril.andreichuk Punkte 333

Sie können verwenden verschachtelte Klassen um private Methoden zu testen. Zum Beispiel (NUnit v3 wird verwendet):

    internal static class A
    {
        // ... other code

        private static Int32 Sum(Int32 a, Int32 b) => a + b;

        [TestFixture]
        private static class UnitTests
        {
            [Test]
            public static void OnePlusTwoEqualsThree()
            {
                Assert.AreEqual(3, Sum(1, 2));
            }
        }
    }

Darüber hinaus kann testbezogener Code mit der Funktion "partielle Klasse" in eine andere Datei verschoben oder mit der Funktion "bedingte Kompilierung" von der Erstellung von Versionen ausgeschlossen werden usw. Erweitertes Beispiel:

Datei A.cs

    internal static partial class A
    {
        // ... other code

        private static Int32 Sum(Int32 a, Int32 b) => a + b;
    }

Datei A.UnitTests.cs

#if UNIT_TESTING
    partial class A
    {
        [TestFixture]
        private static class UnitTests
        {
            [Test]
            public static void OnePlusTwoEqualsThree()
            {
                Assert.AreEqual(3, Sum(1, 2));
            }
        }
    }
#endif

0voto

Johannes Weih Punkte 23

Leider gibt es keine PrivateObject klasse in .net6

Ich habe jedoch eine kleine Erweiterungsmethode geschrieben, die in der Lage ist, private Methoden mit Reflection aufzurufen.

Schauen Sie sich den Beispielcode an:

class Test
{
  private string GetStr(string x, int y) => $"Success! {x} {y}";
}

var test = new Test();
var res = test.Invoke<string>("GetStr", "testparam", 123);
Console.WriteLine(res); // "Success! testparam 123"

Und hier ist die Implementierung der Erweiterungsmethode:

/// <summary>
/// Invokes a private/public method on an object. Useful for unit testing.
/// </summary>
/// <typeparam name="T">Specifies the method invocation result type.</typeparam>
/// <param name="obj">The object containing the method.</param>
/// <param name="methodName">Name of the method.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
/// <returns>The result of the method invocation.</returns>
/// <exception cref="ArgumentException">When no such method exists on the object.</exception>
/// <exception cref="ArgumentException">When the method invocation resulted in an object of different type, as the type param T.</exception>
/// <example>
/// class Test
/// {
///   private string GetStr(string x, int y) => $"Success! {x} {y}";
/// }
///
/// var test = new Test();
/// var res = test.Invoke&lt;string&gt;("GetStr", "testparam", 123);
/// Console.WriteLine(res); // "Success! testparam 123"
/// </example>
public static T Invoke<T>(this object obj, string methodName, params object[] parameters)
{
  var method = obj.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  if (method == null)
  {
    throw new ArgumentException($"No private method \"{methodName}\" found in class \"{obj.GetType().Name}\"");
  }

  var res = method.Invoke(obj, parameters);
  if (res is T)
  {
    return (T)res;
  }

  throw new ArgumentException($"Bad type parameter. Type parameter is of type \"{typeof(T).Name}\", whereas method invocation result is of type \"{res.GetType().Name}\"");
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X