Lättlästa unit-tester med ”extension methods”

2013-03-20

Detta inlägg kommer ge er en beskrivning på hur jag kontrollerar utfallet (assert) av mina tester så de är lättlästa och lätta att underhålla. NUnit är det ramverk jag använder mig av och ni kan hitta det på Nuget.

Standard

Om ni har använt er av NUnit tidigare så vet ni hur det fungerar när man skall kontrollera utfallet. Man måste hela tiden anropa den statiska klassen ”Assert” när man vill testa något. Så som exemplet nedan:

NUnit   
  1. [Test]
  2. public void Test_Int()
  3. {
  4. var toTest = 4;
  5. Assert.AreEqual(4, toTest);
  6. }

Yoda_1

När man läser detta från väster till höger, som man är van vid att göra i vanliga texter, så påminner det mig om Yoda från StarWars. Han är inte direkt känd för att ha den bästa meningsbyggnaden precis. Dessutom har det hänt att man placerar parametrarna fel i anropet, vilket kan leda till lite frågetecken om testet inte skulle gå igenom och man läser felmeddelandet.

Extension

Min lösning på problemet är att använda ”Extensionmethods” som introducerades till C# i version 3.0. Om vi tar samma exempel så ser det ut såhär istället:

Mitt test   
  1. [Test]
  2. public void Test_Int()
  3. {
  4. var toTest = 4;
  5. toTest.ShouldEqual(4);
  6. }

Själva ”extension”-klassen ser ut på följande sätt:

Extension   
  1. public static class NUnitExtensions
  2. {
  3. public static void ShouldEqual(this object a, object b, string message = "")
  4. {
  5. Assert.AreEqual(b, a, message);
  6. }
  7. }

Personligen tycker jag att det är viktigt att skriva lättläst kod även om det betyder en extra klass som tar hand om alla ”extension”-metoder. En annan fördel med detta är att man inte bara behöver testa värdet som skickas in, utan man kan också förändra det. Exempelvis dess typ.

Blanda in ”Generics”

Om vi sedan blandar in ”Generics” i bilden så kan man enkelt få ”extension”-metoderna att retunera värdet och på så sätt fortsätta testa det. Se exemplet nedan:

Testet   
  1. [Test]
  2. public void Test_Int()
  3. {
  4. var toTest = new List<User>
  5. {
  6. new User { Name = "Dennis" }
  7. };
  8. toTest.ShouldBeCountedTo(1)
  9. .And()
  10. .First().Name.ShouldEqual("Dennis");
  11. }

Observera metoden ”And()”. Dess syfte är inte att testa något utan endast att göra ”Assert”:en ännu mer lättläst.  Det är dock en smaksak om man ska använda eller hur ofta man använder den metoden. Kollat vi på ”extension”-metoderna som används i detta exemplet så ser de ut såhär:

Testare   
  1. public static T ShouldNotBeNull<T>(this T a)
  2. {
  3. Assert.IsNotNull(a);
  4. return a;
  5. }
  6. public static IEnumerable<T> ShouldBeCountedTo<T>(this IEnumerable<T> a, int count)
  7. {
  8. a.ShouldNotBeNull();
  9. if(a.Count() != count)
  10. {
  11. throw new Exception(string.Format("Count failed! Expected: {0} but was: {1}", count, a.Count()));
  12. }
  13. }
  14.  
  15. public static T And<T>(this T a)
  16. {
  17. return a;
  18. }

Typa om

En annan sak man kan göra med denna typ av testning är att typa om ett värde samtidigt som man testar det, för att därefter retunera värdet av den rätta typen. Det kan exempelvis se ut såhär:

Testet   
  1. [Test]
  2. public void Test_Int()
  3. {
  4. object toTest = User { Name = "Dennis" };
  5. toTest.ShouldBeOfType<User>()
  6. .And()
  7. .Name.ShouldEqual("Dennis");
  8. }

Som ni kan se så sparar jag ”User” objectet i en ”object”-typad variabel, vilket betyder att man inte når ”Name” egenskapen innan man typat om den.

Testare   
  1. public static T ShouldBeType(this object a)
  2. {
  3. try
  4. {
  5. var tmp = (T)a;
  6. return tmp;
  7. }
  8. catch (Exception)
  9. {
  10. throw new Exception(string.Format("Expexted type: {0}. But was: {1}", typeof(T).Name, typeof(a).Name));
  11. }
  12. }

Smidigt! Eller var tycker du?

Slutligen

Denna metoden gör inte bara att era tester blir lättare att läsa. Utan den gör också att det blir roligare att skriva testerna. Gör mig en tjänst och testa detta i ert nästa projekt!

Taggar: , ,

I wrote this!

The name is Dennis Sangmo!

Jag har ett brinnande intresse av att utveckla webbapplikationer och gör detta både professionellt och på min fritid. En stor del av den tiden jag inte spenderar till att utveckla brukar jag lägga på att läsa på och hålla mig uppdaterad i utvecklings-sfären på internet.

Under det senaste året har jag ökat mitt intresse för att designa användarvänliga applikationer och allt som hör till att öka den positiva upplevelsen hos en besökare eller användare.