In einer Session auf der ADC fragte Stefan Lieser mich heute, ob nicht die dynamischen Features von C# helfen könnten, private Methoden von Objekten zu testen. Erst war ich skeptisch – doch dann half Google. Es geht tatsächlich.
Wer das also dringend tun will, der kann es so wie folgt beschrieben tun. Besonders mag das in Brownfield-Projekten helfen. Denn ansonsten ziehe ich es vor, Privates privat sein zu lassen und nicht direkt zu testen. Und Internes mache ich für Tests mit InternalsVisibleTo zugänglich.
Gegeben dieses Systen under Test:
public class MyFizzBuzzer
{
public static IEnumerable<string> FizzBuzz()
{
return Enumerable.Range(1, 100).Select(FizzBuzzThis);
}
private string FizzBuzzThis(int i)
{
if (IsFizzBuzz(i)) return "FizzBuzz";
if (IsFizz(i)) return "Fizz";
if (IsBuzz(i)) return "Buzz";
return i.ToString();
}
private static bool IsFizz(int i)
{
return i % 3 == 0;
}
…
Dann können Sie in Zukunft solche Tests schreiben für die privaten Methoden:
[TestFixture]
public class testMyFizzBuzzer
{
[Test]
public void Check_fizz_numbers()
{
dynamic sut = typeof(MyFizzBuzzer).Undress();
Assert.IsTrue(sut.IsFizz(3));
Assert.IsTrue(sut.IsFizz(12));
Assert.IsFalse(sut.IsFizz(5));
}
[Test]
[TestCase(1, "1")]
[TestCase(2, "2")]
[TestCase(3, "Fizz")]
[TestCase(5, "Buzz")]
[TestCase(15, "FizzBuzz")]
public void FizzBuzz_number(int number, string expected)
{
dynamic sut = new MyFizzBuzzer().Undress();
Assert.AreEqual(expected, sut.FizzBuzzThis(number));
}
…
Dazu ist es nur nötig, dass Sie diese Klassen ins Testprojekt einbinden:
namespace System.Dynamic
{
public static class UndressObjects
{
public static dynamic Undress(this Type type)
{
return new UndressedObject(type);
}
public static dynamic Undress(this object obj)
{
return new UndressedObject(obj);
}
}
public class UndressedObject : DynamicObject
{
private const BindingFlags MEMBERS_FLAGS =
BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic;
private readonly object _dressedObject;
public UndressedObject(object o)
{
_dressedObject = o;
}
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
var method = TypeOfDressed().GetMethod(binder.Name,
MEMBERS_FLAGS,
null,
GetParameterTypes(args).ToArray(),
null);
if (method == null) return base.TryInvokeMember(binder, args, out result);
result = method.Invoke(_dressedObject, args);
return true;
}
private IEnumerable<Type> GetParameterTypes(object[] args)
{
return from a in args select GetActualType(a);
}
private Type GetActualType(object t)
{
return (t is ParameterInfo) ? (t as ParameterInfo).ParameterType
: t.GetType();
}
private Type TypeOfDressed()
{
return (_dressedObject is Type) ? (Type)_dressedObject
: _dressedObject.GetType();
}
}
}
Die Idee stammt aus diesem Artikel. Ich hab sie lediglich etwas, hm, fokussiert.
Getestet werden können also private statische Methoden oder Instanzmethoden.
Enjoy your brownfield! ;-)
7 Kommentare:
Nice... - Good bye InternalVisibleTo -
-Mike
- InternalsVisibleTo -
Gibt ab VS 2008 einen Private Accessor - find ich die schönste Art private Methoden zu testen. (MSDN Seite)
@Querschädel: Der private accessor ist MSTest-spezifisch. Damit kann und will nicht jeder arbeiten. Außerdem wird er - wenn ich mich recht erinnere - umständlich generiert.
@Ralf - Nein mit NUnit funktioniert er auch. Weitere Frameworks hab ich nicht ausprobiert. Die Generierung ist einfach - rechte Maustaste und Auswahl von "Generate Private Accessor"
Private Accessor?!? Entschuldige - schon beim "Regenerate Private Accessor" bekomme ich irgendwie Bauchschmerzen.
@Ralf: Wie wäre es mit einem kleinem NuPack(age)? Dann geht die Bindung schnell und einfach.
@Mike Private Accessoren sind jedoch in VS2010 deprecated. Microsoft hat diese nicht .Net4.0 fähig gemacht und man weiss nicht ob das noch gemacht wird.
http://blogs.msdn.com/b/vstsqualitytools/archive/2010/01/18/publicize-and-code-generation-for-visual-studio-2010.aspx
Kommentar veröffentlichen