niedziela, 3 listopada 2013

Testowanie prywatnych metod

Ok, ważna zasada testowania prywatnych metod - prywatne metody nie testuje się!!!. Traktuje się je jako szczegóły implementacji, które nie muszą być przedstawiane, a testy tych metod są poprzez metody publiczne. Jednak, jeżeli chcielibyśmy przetestować te "szczegóły implementacji" to moglibyśmy ukrytą implementację wyekstraktować do oddzielnej klasy. Inne możliwości przedstawiam Ci w tym wpisie.


Przypuśćmy, że mamy klasę, która w logice zwraca GUID typu string:
public class GuidReturner
{
        public string GetGuidString(int arg)
        {
            var cmplexStr = GetGuid(arg);
            return cmplexStr == Guid.Empty ? "" : cmplexStr.ToString();
        }
 
        private Guid GetGuid(int arg)
        {
            return arg == 0 ? GetEmptyGuid() : Guid.NewGuid();
        }
 
        private static Guid GetEmptyGuid()
        {
            return Guid.Empty;
        }
}
 
W tej klasie mamy prywatną metodę GetGuid(int) : Guid. Aby ją przetestować to możemy zmienić prawa dostępu na internal. Wtedy wystarczy zastosować InternalsVisibleToAttribute i wpisać do AssemblyInfo.cs
[assembly: InternalsVisibleTo("YourLib.Tests")]
Minusem takiego rozwiązania jest modyfikacja zależności między projektem produkcyjnym, a projektem testującym, przymus pamiętania o zmianie stringa przy zmianie nazwy assembly oraz dodawanie dodatkowych InternalsVisibleTo dla każdego projektu testowego.

Inną możliwością testowania prywatnej metody jest zmiana prawa dostępu na protected i dziedziczenia po tej klasie. Dla naszego przykładu mamy:
public class GuidReturner
{
        protected Guid GetGuid(int arg)
        {
            return arg == 0 ? GetEmptyGuid() : Guid.NewGuid();
        }
}
A w projekcie w którym mamy testy tworzymy klasę testowalną:
public class TestableGuidReturner : GuidReturner
{
        public Guid TestGetGuid(int arg)
        {
            return base.GetGuid(arg);
        }
}
Minusem takiego rozwiązania jest tworzenie dodatkowej klasy testowalnej. Ma to też swoje plusy, gdzie możemy dodać dodatkową implementację, logowanie lub walidację dla testów.

Jeżeli nie podobają Ci się sposoby zmiany praw dostępu to można użyć mechanizmów refleksji. Do tego użyjemy obiektu dynamic.
public class NonPublicInvoker<T> : DynamicObject
{
        private readonly T _closeType;
        
        public NonPublicInvoker(T closeType )
        {
            _closeType = closeType;
        }
 
        public override bool TryInvokeMember(InvokeMemberBinder binder, 
                            object[] args, out object result)
        {
            result = _closeType.GetType()
                     .InvokeMember(binder.Name, 
                      BindingFlags.Instance 
                        | BindingFlags.NonPublic 
                        | BindingFlags.InvokeMethod
                     ,null, _closeType, args);
            return true;
        }
}
Test może wyglądać następująco:
[Test]
public void Dynamic_It_should_return_empty_guid_for_0()
{
      dynamic sbc = new NonPublicInvoker<GuidReturner>(new GuidReturner());
      Assert.AreEqual(sbc.GetGuid(0), Guid.Empty);
}
Do tego stwórzmy dynamika dla metod statycznych:
public class StaticNonPublicInvoker<T> : DynamicObject
{
        public override bool TryInvokeMember(InvokeMemberBinder binder, 
                             object[] args, out object result)
        {
            result = typeof(T)
                          .InvokeMember(binder.Name, 
                           BindingFlags.NonPublic 
                              | BindingFlags.Static 
                              | BindingFlags.InvokeMethod
                          ,null, null, args);
            return true;
        }
}
[Test]
public void Dynamic_It_should_return_empty_guid_for_static_private_method()
{
            dynamic sbc = new StaticNonPublicInvoker<GuidReturner>();
            Assert.AreEqual(sbc.GetEmptyGuid(), Guid.Empty);
}

W poniższym filmiki jest przedstawiony podobny sposób testowania prywatnych metod za pomocą obiektu PrivateObject



Brak komentarzy:

Prześlij komentarz