Een object testen met databasequery’s

Ik heb gehoord dat het testen van eenheden “helemaal geweldig”, “echt cool” en “allerlei goede dingen” is, maar voor 70% of meer van mijn bestanden is databasetoegang nodig (sommige lezen en sommige schrijven) en ik ben niet zeker hoe je een eenheidstest voor deze bestanden moet schrijven.

Ik gebruik PHP en Python, maar ik denk dat dit een vraag is die van toepassing is op de meeste/alle talen die databasetoegang gebruiken.


Antwoord 1, autoriteit 100%

Ik zou willen voorstellen om uw oproepen naar de database te bespotten. Mocks zijn in feite objecten die eruitzien als het object waarop u een methode probeert aan te roepen, in die zin dat ze dezelfde eigenschappen, methoden, enz. beschikbaar hebben voor de beller. Maar in plaats van de actie uit te voeren waarvoor ze zijn geprogrammeerd wanneer een bepaalde methode wordt aangeroepen, slaat het dat helemaal over en retourneert het alleen een resultaat. Dat resultaat wordt doorgaans vooraf door u bepaald.

Om uw objecten in te stellen voor spotten, moet u waarschijnlijk een soort omkering van het controle-/afhankelijkheidsinjectiepatroon gebruiken, zoals in de volgende pseudo-code:

class Bar
{
    private FooDataProvider _dataProvider;
    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }
    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}
class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

In je unit-test maak je nu een mock van FooDataProvider, waarmee je de methode GetAllFoos kunt aanroepen zonder dat je de database hoeft te raken.

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);
        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}
        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)
        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()
        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

Een veelvoorkomend spotscenario, in een notendop. Natuurlijk wilt u waarschijnlijk nog steeds uw daadwerkelijke database-aanroepen testen, waarvoor u de database moet gebruiken.


Antwoord 2, autoriteit 32%

Idealiter zouden uw objecten voortdurend onwetend moeten zijn. U zou bijvoorbeeld een “gegevenstoegangslaag” moeten hebben waaraan u verzoeken zou doen, die objecten zouden retourneren. Op deze manier kun je dat deel uit je unit-tests weglaten, of ze afzonderlijk testen.

Als uw objecten nauw zijn gekoppeld aan uw gegevenslaag, is het moeilijk om de juiste unit-tests uit te voeren. het eerste deel van de eenheidstest is “eenheid”. Alle eenheden moeten afzonderlijk kunnen worden getest.

In mijn c#-projecten gebruik ik NHibernate met een volledig aparte gegevenslaag. Mijn objecten leven in het kerndomeinmodel en zijn toegankelijk vanuit mijn applicatielaag. De applicatielaag praat met zowel de datalaag als de domeinmodellaag.

De applicatielaag wordt ook wel de “Business Layer” genoemd.

Als je PHP gebruikt, maak dan een specifieke set klassen aan voor ALLEENgegevenstoegang. Zorg ervoor dat uw objecten geen idee hebben hoe ze worden bewaard en verbind de twee in uw toepassingsklassen.

Een andere optie zou zijn om mocking/stubs te gebruiken.


Antwoord 3, autoriteit 15%

De eenvoudigste manier om een ​​object met databasetoegang te testen is het gebruik van transactiebereiken.

Bijvoorbeeld:

   [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {
        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();
            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

Hiermee wordt de staat van de database teruggezet, in feite als het terugdraaien van een transactie, zodat u de test zo vaak als u wilt kunt uitvoeren zonder bijwerkingen. We hebben deze aanpak met succes toegepast in grote projecten. Onze build duurt een beetje lang (15 minuten), maar het is niet verschrikkelijk voor het hebben van 1800 unit-tests. Als de bouwtijd een probleem is, kunt u het bouwproces ook wijzigen om meerdere builds te hebben, een voor het bouwen van src, een andere die daarna wordt gestart en die unit-tests, code-analyse, verpakking, enz. afhandelt…


Antwoord 4, autoriteit 12%

Je moet de toegang tot de database bespotten als je je lessen wilt testen. Je wilt de database immers niet testen in een unittest. Dat zou een inburgeringstest zijn.

Abstract de oproepen weg en voeg vervolgens een mock in die alleen de verwachte gegevens retourneert. Als je lessen niet meer doen dan het uitvoeren van query’s, is het misschien niet eens de moeite waard om ze te testen, hoewel…


Antwoord 5, autoriteit 12%

Misschien kan ik u een voorproefje geven van onze ervaring toen we begonnen te kijken naar unit-testing van ons middle-tier proces dat een heleboel “business logic” sql-bewerkingen omvatte.

We hebben eerst een abstractielaag gemaakt waarmee we elke redelijke databaseverbinding konden “invoegen” (in ons geval ondersteunden we gewoon een enkele ODBC-verbinding).

Toen dit eenmaal op zijn plaats was, konden we zoiets in onze code doen (we werken in C++, maar ik weet zeker dat je het idee begrijpt):

GetDatabase().ExecuteSQL( “INSERT INTO foo ( blah, blah )” )

Bij normale runtime retourneert GetDatabase() een object dat al onze sql (inclusief query’s) via ODBC rechtstreeks naar de database voedt.

Toen begonnen we te kijken naar in-memory databases – SQLite lijkt verreweg de beste te zijn. (http://www.sqlite.org/index.html). Het is opmerkelijk eenvoudig in te stellen en te gebruiken, en stelde ons in staat om GetDatabase() te subklassen en te negeren om sql door te sturen naar een in-memory database die is gemaakt en vernietigd voor elke uitgevoerde test.

We bevinden ons hier nog in de beginfase, maar het ziet er tot nu toe goed uit, maar we moeten er wel voor zorgen dat we alle benodigde tabellen maken en deze vullen met testgegevens, maar we hebben de werklast enigszins verminderd hier door een generieke set hulpfuncties te creëren die veel van dit alles voor ons kunnen doen.

Over het algemeen heeft het enorm geholpen met ons TDD-proces, aangezien het maken van schijnbaar onschuldige wijzigingen om bepaalde bugs op te lossen nogal vreemde effecten kan hebben op andere (moeilijk te detecteren) delen van uw systeem – vanwege de aard van sql /databases.

Uiteraard hebben onze ervaringen zich gecentreerd rond een C++-ontwikkelomgeving, maar ik weet zeker dat je iets soortgelijks zou kunnen krijgen onder PHP/Python.

Hopelijk helpt dit.


Antwoord 6, autoriteit 8%

Het boek xUnit Test Patternsbeschrijft enkele manieren om unit-testing code te verwerken die een database raakt. Ik ben het eens met de andere mensen die zeggen dat je dit niet wilt doen omdat het traag is, maar je moet het een keer doen, IMO. De db-verbinding belachelijk maken om dingen van een hoger niveau te testen is een goed idee, maar lees dit boek voor suggesties over dingen die u kunt doen om met de eigenlijke database om te gaan.


Antwoord 7, autoriteit 6%

Ik probeer meestal mijn tests op te splitsen tussen het testen van de objecten (en ORM, indien aanwezig) en het testen van de db. Ik test de object-kant van dingen door de gegevenstoegangsaanroepen te bespotten, terwijl ik de db-kant van de dingen test door de objectinteracties met de db te testen, die, naar mijn ervaring, meestal vrij beperkt is.

Vroeger raakte ik gefrustreerd door het schrijven van eenheidstests totdat ik begon te spotten met het gedeelte over gegevenstoegang, zodat ik geen test-db hoefde te maken of testgegevens ter plekke hoefde te genereren. Door de gegevens te bespotten, kunt u het allemaal tijdens runtime genereren en ervoor zorgen dat uw objecten correct werken met bekende invoer.


Antwoord 8, autoriteit 5%

Het testen van uw databasetoegang is eenvoudig genoeg als uw project overal een hoge samenhang en losse koppeling heeft. Op deze manier kunt u alleen de dingen testen die elke specifieke klasse doet, zonder dat u alles tegelijk hoeft te testen.

Als u bijvoorbeeld uw gebruikersinterfaceklasse test, moeten de tests die u schrijft alleen proberen te verifiëren dat de logica in de gebruikersinterface werkte zoals verwacht, niet de bedrijfslogica of databaseactie achter die functie.

Als je de daadwerkelijke toegang tot de database wilt testen, krijg je eigenlijk meer een integratietest, omdat je afhankelijk bent van de netwerkstack en je databaseserver, maar je kunt verifiëren dat je SQL-code doet wat je doet. vroeg het te doen.

De verborgen kracht van unit testing voor mij persoonlijk is dat het me dwingt om mijn applicaties op een veel betere manier te ontwerpen dan ik zou kunnen zonder hen. Dit komt omdat het me echt heeft geholpen om los te komen van de “deze functie zou alles moeten doen”-mentaliteit.

Sorry, ik heb geen specifieke codevoorbeelden voor PHP/Python, maar als je een .NET-voorbeeld wilt zien, heb ik een postdat een techniek beschrijft die ik gebruikte om deze zelfde test uit te voeren.


Antwoord 9, autoriteit 5%

Opties die je hebt:

  • Schrijf een script dat de database zal wissen voordat u unit-tests start, vul vervolgens db in met een vooraf gedefinieerde set gegevens en voer de tests uit. Je kunt dat ook voor elke test doen – het gaat langzaam, maar minder foutgevoelig.
  • Injecteer de database. (Voorbeeld in pseudo-Java, maar geldt voor alle OO-talen)

    class Database {
     openbare resultaatquery (stringquery) {... echte db hier ...}
    }

    klasse MockDatabase breidt Database { uit openbare resultaatquery (stringquery) { retourneer "nepresultaat"; } }

    klasse ObjectThatUsesDB { public ObjectThatUsesDB (Database db) { deze.database = db; } }

    nu in productie gebruik je de normale database en voor alle tests injecteer je gewoon de nepdatabase die je ad hoc kunt maken.

  • Gebruik in de meeste code helemaal geen DB (dat is sowieso een slechte gewoonte). Maak een “database”-object dat in plaats van terug te keren met resultaten, normale objecten retourneert (dwz Userretourneert in plaats van een tuple {name: "marcin", password: "blah"}) schrijf al je tests met ad hoc geconstrueerde echteobjecten en schrijf één grote test die afhankelijk is van een database die ervoor zorgt dat deze conversie goed werkt.

Natuurlijk sluiten deze benaderingen elkaar niet uit en kun je ze naar behoefte mixen en matchen.


Antwoord 10, autoriteit 3%

Ik heb dit nog nooit in PHP gedaan en ik heb nog nooit Python gebruikt, maar wat je wilt doen, is de aanroepen naar de database spotten. Om dat te doen, kunt u een IoCimplementeren, of u nu een hulpprogramma van een derde partij of u het zelf beheert. kan een nepversie van de databaseaanroeper implementeren, waarmee u de uitkomst van die nepoproep kunt bepalen.

Een eenvoudige vorm van IoC kan worden uitgevoerd door alleen naar interfaces te coderen. Dit vereist een soort objectoriëntatie in uw code, dus het is mogelijk niet van toepassing op wat u doet (ik zeg dat aangezien ik alleen maar verder hoef te gaan met uw vermelding van PHP en Python)

Ik hoop dat dit nuttig is, anders heb je nu een aantal termen om op te zoeken.


Antwoord 11, autoriteit 3%

Ik ben het eens met de eerste post: databasetoegang moet worden weggehaald in een DAO-laag die een interface implementeert. Vervolgens kunt u uw logica testen tegen een stub-implementatie van de DAO-laag.


Antwoord 12, autoriteit 3%

Je zou spottende kaderskunnen gebruiken om de database-engine te abstraheren. Ik weet niet of PHP/Python er een heeft, maar voor getypte talen (C#, Java enz.) zijn er genoeg keuzes

Het hangt ook af van hoe je die databasetoegangscode hebt ontworpen, omdat sommige ontwerpen gemakkelijker te testen zijn dan andere, zoals in eerdere berichten is vermeld.


Antwoord 13, autoriteit 2%

Het instellen van testgegevens voor unit-tests kan een uitdaging zijn.

Als het op Java aankomt, kunt u, als u Spring API’s gebruikt voor het testen van eenheden, de transacties op eenheidsniveau beheren. Met andere woorden, u kunt unit-tests uitvoeren waarbij database-updates/inserts/deletes betrokken zijn en de wijzigingen ongedaan maken. Aan het einde van de uitvoering laat u alles in de database zoals het was voordat u met de uitvoering begon. Voor mij is het zo goed als maar kan.

Other episodes