Lese log4net-loggen i enhetstester

Av og til har jeg behov for å sjekke hva som logges til log4net i enhets- eller integrasjonstester. Dette er ikke “best practise”, man bør normalt kunne verifisere forventet adferd på andre måter, men det finnes alltid unntak. I vår test-suite på rundt 700 tester har dette behoved dukket opp en håndfull steder.

Til dette formålet har jeg kommet opp med klassen nedenfor. C#-koden konfigurerer opp log4net med en appender som legger alt som logges til en List of LoggingEvent. For enkelhets skyld, siden jeg allerede normalt har en avhengighet til mockingrammeverket Moq, bruker jeg Moq til å opprette en appender for meg. Om du ikke bruker noe tilsvarende kan du selvsagt lage din egen in-memory appender.

    1 using System;
    2 using System.Collections.Generic;
    3 using Moq;
    4 using log4net.Appender;
    5 using log4net.Core;
    6 using log4net.Config;
    7 
    8 namespace SharedTestLib
    9 {
   10     /// <summary>
   11     /// Will capture all events logged through log4net 
   12     /// for investigation in automated tests
   13     /// </summary>
   14     public static class TestLog
   15     {
   16         private static bool _initialized;
   17         private static List<LoggingEvent> _log_events = new List<LoggingEvent>();
   18 
   19         /// <summary>
   20         /// FakeLog will start to capture log events. 
   21         /// Call it in TestFixtureSetUp.
   22         /// </summary>
   23         public static void Initialize()
   24         {
   25             if (_initialized)
   26                 return;
   27 
   28             var mockAppender = new Mock<IAppender>();
   29             mockAppender
   30                 .Setup(appender => appender.DoAppend(It.IsAny<LoggingEvent>()))
   31                 .Callback((LoggingEvent le) => _log_events.Add(le));
   32             BasicConfigurator.Configure(mockAppender.Object);
   33             _initialized = true;
   34         }
   35 
   36         /// <summary>
   37         /// Clears the events.
   38         /// Call it in SetUp.
   39         /// </summary>
   40         public static void Clear()
   41         {
   42             _log_events = new List<LoggingEvent>();
   43         }
   44 
   45         public static IEnumerable<LoggingEvent> Events
   46         {
   47             get
   48             {
   49                 return _log_events;
   50             }
   51         }
   52     }
   53 }

Det er ikke ofte jeg lager statiske klasser, men i dette tilfellet var det helt greit, og den blir veldig enkel å bruke i enhetstestene. Jeg valgte å implementere en Initialize()-metode for eksplisit initialisering, men en statisk konstruktør kunne nok gjort samme nytten. Initialize kan forøvrig (som du ser) kalles flere ganger, appenderen blir bare lagt til én gang.

Jeg er forresten klar over at log4net også har en MemoryAppender jeg kunne ha brukt, men min fremgangsmåte virket noe enklere sammenlignet med det jeg fant om den på nettet.

Videre kan du inkludere noen properties for å hente ut siste event av en bestemt type – det er ikke usannsynlig at det er akkurat det du er ute etter:

   55 #region Convenience methods
   56 
   57 public static LoggingEvent LastEvent { get { return _log_events.Last(); } }
   58 
   59 public static LoggingEvent LastDebug { get { return Last(Level.Debug); } }
   60 public static LoggingEvent LastInfo { get { return Last(Level.Info); } }
   61 public static LoggingEvent LastWarn { get { return Last(Level.Warn); } }
   62 public static LoggingEvent LastError { get { return Last(Level.Error); } }
   63 public static LoggingEvent LastFatal { get { return Last(Level.Fatal); } }
   64 
   65 private static LoggingEvent Last(Level level)
   66 {
   67     return _log_events.Where(e => e.Level == level).Last();
   68 }
   69 
   70 #endregion

Merk at du må inkludere System.Linq for å få tilgang til metodene Where() og Last().

For å gjøre testloggen enda enklere og mere beskrivende å bruke i test-sammenheng, har jeg lagt til noen funksjonelle godbiter for å gjøre verifiseringer. Den første metoden verifiserer at alle eventene i loggen tilfredstiller et gitt predikat, den andre at minst ett event gjør det (First() kaster et Exception om ingen matcher). Disse metodene har navn som passer inn med testmetodene fra coretdd som jeg alltid bruker.

   72 #region Asserts
   73 
   74 /// <summary>
   75 /// All events in log must satisfy the predicate
   76 /// </summary>
   77 public static void ShouldSatisfy(Predicate<LoggingEvent> predicate)
   78 {
   79     _log_events.ForEach(e => 
   80     {
   81         if (!predicate(e))
   82             Assert.Fail(
   83                 "Event does not satisfy predicate: {0} - {1}", 
   84                 e.Level, 
   85                 e.RenderedMessage);
   86     });
   87 }
   88 
   89 public static void ShouldContain(Func<LoggingEvent, bool> predicate)
   90 {
   91     _log_events.First(predicate);
   92 }
   93 
   94 #endregion

Merk at du må legge til en referanse til NUnit for bruken av Assert.Fail() i ShouldSatisfy-metoden.

Her er et par eksempler på hvordan disse siste metodene kan brukes:

   89 private void log_should_not_contain_any_fatal_events()
   90 {
   91     TestLog.ShouldSatisfy(logEvent => logEvent.Level != Level.Fatal);
   92 }
   93 
   94 public void log_should_contain_text(string value)
   95 {
   96     TestLog.ShouldContain(logEvent => logEvent.RenderedMessage.Equals(value));
   97 }

Håper dette kan være til nytte for noen. Lykke til med testingen!

Kategorier: Testing / TDD.
RSS feed for kommentarene. Tilbaketråkk.

Én kommentar til “Lese log4net-loggen i enhetstester”

  1. Torbjørn Says:

    Ups!! Jeg sa vi hadde rundt 700 tester. Det var veldig feil. Tallet var 870. Differnansen hadde vi faktisk glemt å inkludere i vår continous integration build. Fyskammeseg, det var pinlig!!!

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


Einar W. Høst: Det er jo læringen som gjør det morsomt! Se også http://norvig.com/21-days...

Pagliacci: OBS! tl;wr. Det er vel akuratt det jeg sliter med med min læring innenfor pr...

Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

Mulig relaterte linker

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • En historie om programmering
  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (21)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta