Unit testen implementeren met interfaces

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Jan_V
  • Registratie: Maart 2002
  • Laatst online: 22:19
Ben de laatste tijd redelijk wat aan het lezen hoe ik m'n code kan verbeteren. Een van de dingen die nagenoeg overal naar voren komt is het maken van unit testen. Op zich een redelijk goede stap. Nu heb ik in VS een test project aangemaakt en het maken van testen hierin is behoorlijk eenvoudig.
Echter zit ik (momenteel) nog met 1 ding, namelijk dat een test kort moet zijn (<1ms) en dat lukt vaak niet als je transacties doet tussen verschillende tiers of layers (webservice, database, bestanden). In de boeken die ik lees wordt er beschreven dat er dan gebruik gemaakt kan worden van mock objecten (met behulp van Interfaces). Op zich snap ik, denk ik, hoe zoiets gedaan moet worden, maar hoe gebruik je die interfaces (en dus de mock objecten) dan.

Stel dat ik een dal.Get() wil doen
Ik kan dan een interface maken IDataLayer met een methode Get() maken. Ook snap ik hoe ik dan een mock object kan maken in m'n test project, iets van
C#:
1
2
3
4
5
public class MockDAL : IDataLayer
{
    public void Get()

}


Wat ik echter niet snap is hoe ik m'n MockDAL aan ga roepen? Het is natuurlijk niet de bedoeling dat ik in de code uitzonderingen ga schrijven om testen uit te kunnen voeren. Dan schiet je je doel voorbij.

In de auto zat ik zojuist te denken om een extra Constructor te maken in m'n klassen. Normaal heb ik een constructor zonder parameters en dan doe ik er nu een bij met een parameter van de interface, dus:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
public class BusinesLayer
{
    private IDataLayer _dal;
    public BusinesLayer()
    {
        this._dal = new DataLayer();
    }
    public BusinesLayer(IDataLayer dal)
    {
        this._dal = dal;
    }
}

Momenteel is dit de enige 'schone' methode die ik heb kunnen verzinnen.
Las ook dat er mock frameworks beschikbaar zijn, maar daar heb ik nog niet naar gekeken. Kan dus zijn dat dit 'probleem' daarmee al wordt ondervangen, of dat ik iets heel triviaals over het hoofd zie.

Battle.net - Jandev#2601 / XBOX: VriesDeJ


Acties:
  • 0 Henk 'm!

  • YopY
  • Registratie: September 2003
  • Laatst online: 13-07 01:14
Je probleem zit hem in Dependency Management. Je laatste code voorbeeld is eigenlijk hoe het zou moeten. Je BusinessLayer heeft een IDataLayer eigenschap, maar tegelijkertijd is het voor je BusinessLayer niet belangrijk welke IDataLayer dit is.

Je BusinessLayer is, neem ik aan, afhankelijk van de IDataLayer voor de correcte werking, niet? Dwz, als een BusinessLayer object geen IDataLayer heeft werkt hij niet, niet? In zo'n geval moet je de IDataLayer altijd toekennen via de constructor.

Vervolgens kun je in je tests een 'mock' IDataLayer maken die geen verbinding maakt met de database, maar slechts de resultaten teruggeeft (of ook 'mock' resultaten) die de BusinessLayer nodig heeft om te werken.

In mijn eigen (beperkte) ervaring is het schrijven van unit tests een zeer goede oefening in het maken van goede code, dwz classes die onafhankelijk van elkaar werken en dergelijke. Er zijn echter genoeg slimmere mensen die het beter dan ik uit kunnen leggen.

Met betrekking tot mock frameworks, dat zijn een soort van tools waarmee je gemakkelijk je mock objecten aan kunt maken. Dus in plaats van het schrijven van een nieuwe class die IDataLayer implementeert, kun je inline een object in elkaar (laten) prutsen, door het definiëren van het gedrag daarvan met code. Ik heb hier echter alleen ervaring mee in Java. Een kort code voorbeeld van hoe het in Java eruit zou zien:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
Mockery context = new Mockery();
IDataLayer mockDataLayer = context.mock(IDataLayer.class);
Object returnObject = new Object(); // willekeurig

context.checking(new Expectations(){{
  one(mockDataLayer).get(); will(returnValue(returnObject));
}});

BusinessLayer layer = new BusinessLayer(mockDataLayer);
layer.doSomething(); // doSomething doet iets met je IDataLayer.
Object results = layer.getResults();

assertThat(results, is(returnObject));


(losse pols, weet niet zeker of het zo werkt)
(JUnit 4 met JMock... een of andere recente versie. Imports niet erbij gedaan)

Acties:
  • 0 Henk 'm!

  • TheNameless
  • Registratie: September 2001
  • Laatst online: 07-02 21:38

TheNameless

Jazzballet is vet!

Ik denk dat je wel op de goede weg zit.

Een extra constructor maken die een IDataLayer accepteerd is inderdaad een oplossing. Je kan ook een IDataLayer property maken op je IBussinessLayer, die je kan wijzigen tijdens unittests.

De beste manier zo een DI/IoC manier zijn. Waarbij je het maken van objecten zelf niet meer regeld, maar laat regelen door een Inversion of Control container. Je configureerd dan zeg maar dat je in productie omgeving een andere implementatie van IDataLayer wilt hebben dan tijdens Unit testing.

Misschien schiet dit echter wel je doel voorbij :)

Ducati: making mechanics out of riders since 1946


Acties:
  • 0 Henk 'm!

  • YopY
  • Registratie: September 2003
  • Laatst online: 13-07 01:14
quote: TheNameless
Je kan ook een IDataLayer property maken op je IBussinessLayer, die je kan wijzigen tijdens unittests.
Nu weet ik niet of dat een goed idee is. Je moet de IDataLayer property alleen schrijfbaar / vervangbaar maken indien die ook echt moet veranderen gedurende de levensduur van een object (en in de meeste gevallen is dat niet zo). Daarnaast zal dit, bij een goede unit test, ook weinig uitmaken - bij elke test wil je het te testen object 'vers' hebben, zodat de eventuele aanpassingen van een vorige test er geen invloed op hebben.

Acties:
  • 0 Henk 'm!

  • Jan_V
  • Registratie: Maart 2002
  • Laatst online: 22:19
Mooi om te weten dat ik met het idee van een extra constructor op de juiste weg ben (of een IDataLayer property).
Zal het binnenkort eens uitproberen.
Dank!

Battle.net - Jandev#2601 / XBOX: VriesDeJ


Acties:
  • 0 Henk 'm!

  • Sebazzz
  • Registratie: September 2006
  • Laatst online: 19:03

Sebazzz

3dp

Zijn er toevallig tools om mock interfaces mee te maken? Dus een tooltje dat de interface + een implementatie naar de class die je zowieso zou willen gebruiken genereert?

[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]


Acties:
  • 0 Henk 'm!

  • sig69
  • Registratie: Mei 2002
  • Laatst online: 01:10
Ik gebruik Resharper: rechtsklikken -> extract interface. Maar dat kost wel weer wat natuurlijk.
Waar baseer je eigenlijk op dat unit tests niet langer dan 1 ms mogen duren? Het Visual Studio unit test framework heeft volgens mij ook support om voor unit tests aparte databases te attachen, zou ook een oplossing kunnen zijn misschien.

[ Voor 60% gewijzigd door sig69 op 04-05-2010 13:00 ]

Roomba E5 te koop


Acties:
  • 0 Henk 'm!

  • eamelink
  • Registratie: Juni 2001
  • Niet online

eamelink

Droptikkels

Jan_V schreef op dinsdag 04 mei 2010 @ 12:26:
Mooi om te weten dat ik met het idee van een extra constructor op de juiste weg ben (of een IDataLayer property).
Zal het binnenkort eens uitproberen.
Dank!
Het is waarschijnlijk beter om niet een extra constructor aan te maken, maar de oude te vervangen door de nieuwe. In principe moet BusinessLayer alleen weet hebben van de interface IDataLayer, maar in jouw oude code maakt hij zelf de concrete klasse DataLayer aan.

Acties:
  • 0 Henk 'm!

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 13-09 09:39

Janoz

Moderator Devschuur®

!litemod

Sebazzz schreef op dinsdag 04 mei 2010 @ 12:31:
Zijn er toevallig tools om mock interfaces mee te maken? Dus een tooltje dat de interface + een implementatie naar de class die je zowieso zou willen gebruiken genereert?
In veel Java IDE's is het een simpele klik van de muis om een interface te genereren uit een bestaande classe en eventueel aan te geven in de andere code waar die classe door een interface vervangen zou moeten worden.

Voor het genereren van Mocks gebruik ik vaak EasyMock. Daar is ook een .NET variant van. Daar worden runtime implementaties gegenereerd van een interface en vervolgens kun je in je unittest 'opnemen', replayen en verifieren.

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


Acties:
  • 0 Henk 'm!

  • Sebazzz
  • Registratie: September 2006
  • Laatst online: 19:03

Sebazzz

3dp

sig69 schreef op dinsdag 04 mei 2010 @ 12:43:
Ik gebruik Resharper: rechtsklikken -> extract interface. Maar dat kost wel weer wat natuurlijk.
Helaas heb ik de source van bijvoorbeeld de ASP.NET Membership class niet ;) (om een voorbeeld te geven, daarnaast is de class ook static dus gaat dat truukje sowieso niet lukken)

[ Voor 15% gewijzigd door Sebazzz op 04-05-2010 14:05 ]

[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]


Acties:
  • 0 Henk 'm!

  • HMS
  • Registratie: Januari 2004
  • Laatst online: 21-08 23:06

HMS

Sebazzz schreef op dinsdag 04 mei 2010 @ 13:43:
[...]

Helaas heb ik de source van bijvoorbeeld de ASP.NET Membership class niet ;) (om een voorbeeld te geven, daarnaast is de class ook static dus gaat dat truukje sowieso niet lukken)
Zelf een IMembershipProvider interface + een wrapper om de ASP.NET Membership heen ;)?

Acties:
  • 0 Henk 'm!

  • Sebazzz
  • Registratie: September 2006
  • Laatst online: 19:03

Sebazzz

3dp

HMS schreef op dinsdag 04 mei 2010 @ 15:21:
[...]


Zelf een IMembershipProvider interface + een wrapper om de ASP.NET Membership heen ;)?
Dat hoef je me niet te vertellen. Ik vraag dus of er een tool bestaat die dat voor je kan doen ;)

[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]


Acties:
  • 0 Henk 'm!

  • Jan_V
  • Registratie: Maart 2002
  • Laatst online: 22:19
sig69 schreef op dinsdag 04 mei 2010 @ 12:43:
....Waar baseer je eigenlijk op dat unit tests niet langer dan 1 ms mogen duren?....
Volgens mij heb ik het gelezen in het boek Clean Code en staat zeker in het boek Working Effectively with Legacy Code.
Argumentatie die ze er voor geven is dat wanneer je 10.000 unit tests hebt en die gaat draaien dat het heel lang gaat duren. Is geen 'wet', maar een goede leidraad om aan te houden. Ben het er op zich wel mee eens, hoewel ik nog ooit een unit test heb geschreven, dus kan niet oordelen of het ook in de praktijk werkbaar is.
eamelink schreef op dinsdag 04 mei 2010 @ 12:49:
[...]

Het is waarschijnlijk beter om niet een extra constructor aan te maken, maar de oude te vervangen door de nieuwe. In principe moet BusinessLayer alleen weet hebben van de interface IDataLayer, maar in jouw oude code maakt hij zelf de concrete klasse DataLayer aan.
Zou natuurlijk ook kunnen, maar kan dat?
Vanuit m'n Presenter ga ik naar m'n Business laag. De Presenter heeft helemaal geen weet dan de DAL, waardoor je ook geen echte klasse mee kunt geven aan de constructor.Wellicht dat ik daar nog even naar moet zoeken dan.

[ Voor 33% gewijzigd door Jan_V op 04-05-2010 15:36 ]

Battle.net - Jandev#2601 / XBOX: VriesDeJ


Acties:
  • 0 Henk 'm!

  • HMS
  • Registratie: Januari 2004
  • Laatst online: 21-08 23:06

HMS

Als je de constructors gaat vervangen kijk dan even naar Dependency Injection ;). StructureMap of iets dergelijks om je objecten te instantiëren ;)

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Wat ook kan helpen is Pex en Moles, waarmee je ook zonder interfaces willekeurige methodes kan vervangen om te testen en automatisch unit tests kan maken. Wel nog een beetje in testfase (versie 0.91 voor VS 2010); iemand al ervaringen? :)

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • SiErRa
  • Registratie: Februari 2000
  • Laatst online: 14-09 10:20
Jan_V schreef op dinsdag 04 mei 2010 @ 15:31:
[...]

Volgens mij heb ik het gelezen in het boek Clean Code en staat zeker in het boek Working Effectively with Legacy Code.
Argumentatie die ze er voor geven is dat wanneer je 10.000 unit tests hebt en die gaat draaien dat het heel lang gaat duren. Is geen 'wet', maar een goede leidraad om aan te houden. Ben het er op zich wel mee eens, hoewel ik nog ooit een unit test heb geschreven, dus kan niet oordelen of het ook in de praktijk werkbaar is.


[...]

Zou natuurlijk ook kunnen, maar kan dat?
Vanuit m'n Presenter ga ik naar m'n Business laag. De Presenter heeft helemaal geen weet dan de DAL, waardoor je ook geen echte klasse mee kunt geven aan de constructor.Wellicht dat ik daar nog even naar moet zoeken dan.
Snelle testen zijn fijn, maar je zit niet zo heel snel aan 10000 tests hoor :)
Maar het is wel slim om te gaan unit testen niet niet alleen scenario tests te schrijven.
Dat kan met mocks erg goed, met bijvoorbeeld rhinomocks ziet dat er in c# zo uit (beetje jouw voorbeeld aangehouden):

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[TestClass]
public class PresenterTests 
{
   private Presenter _sut;
   private IBusinessLayer _businessLayer;

   [TestInitialization]
   public void Init() 
   {
     _businessLayer= MockRepository.GenerateMock<IBusinessLayer>();

     _sut = new Presenter();
     _sut._businessLayer= _businessLayer;
   }

   [TestMethod]
   public void GetTest() 
   {
      int klantId = 1;
      Klant mockResult = new Klant(1);

      _businessLayer.Expect(x => x.Get(klantId)).Return(mockResult);
    
      Klant result = _sut.GetKlant(klantId);

      Assert.IsNotNull(result);
      Assert.AreEqual(klantId, result.Id);
      _dataLayer.VerifyAllExpectations();
   }

}

Code zo uit de losse pols gedaan, dus zal wel niet helemaal syntactisch correct zijn.

De truc is dat je een BusinessLayer mock als dependancy moet zien te injecten.
Dat kan je doen door een constructor toe te voegen die een IBusinessLayer accepteerd, of een public property die een IBusinessLayer accepteerd (en in de default constructor met new BusinessLayer() vult) of zoals ik in het bovenstaande voorbeeldje heb gedaan de IBusinessLayer variabele internal heb gemaakt en de internals expose aan het unittest project.

Stel dat je op deze manier nu al je methodes op je presenter laag hebt getest is het toch nog wel een aanrader om scenariotests te schrijven waar ook echt data uit de db gehaald wordt.

Deze tests zijn een stuk lastiger te onderhouden, maar zorgen wel dat alles ook in het echt werkt en niet alleen met mocks.
Pagina: 1