Follow my new blog

Dienstag, 26. Oktober 2010

Private Methoden einfach testen

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! ;-)

Kommentare:

Mike Bild hat gesagt…

Nice... - Good bye InternalVisibleTo -
-Mike

Mike Bild hat gesagt…

- InternalsVisibleTo -

Querschaedel hat gesagt…

Gibt ab VS 2008 einen Private Accessor - find ich die schönste Art private Methoden zu testen. (MSDN Seite)

Ralf Westphal - One Man Think Tank hat gesagt…

@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.

Querschaedel hat gesagt…

@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"

Mike Bild hat gesagt…

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.

Anonym hat gesagt…

@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