Jak przetestować obiekt z zapytaniami do bazy danych

Słyszałem, że testy jednostkowe są "totally awesome"," really cool " i "all manner of good things", ale 70% lub więcej moich plików wiąże się z dostępem do bazy danych (niektóre odczytywane, inne zapisywane) i nie jestem pewien, jak napisać test jednostkowy dla tych plików.

Używam PHP i Pythona, ale myślę, że to pytanie dotyczy większości / wszystkich języków, które używają dostępu do bazy danych.

Author: Teifion, 2008-08-27

13 answers

Sugerowałbym wyśmianie twoich telefonów do bazy danych. Mocki to w zasadzie obiekty, które wyglądają jak Obiekt, na którym próbujesz wywołać metodę, w tym sensie, że mają te same właściwości, metody itp. dostępne dla rozmówcy. Zamiast jednak wykonywać jakąkolwiek czynność, do jakiej są zaprogramowani, gdy dana metoda jest wywoływana, pomija to całkowicie i po prostu zwraca wynik. Wynik ten jest zazwyczaj definiowany przez Ciebie z wyprzedzeniem.

Aby skonfigurować swoje obiekty aby wyśmiać, prawdopodobnie musisz użyć pewnego rodzaju inwersji wzorca wtrysku sterowania / zależności, jak w poniższym pseudo-kodzie:

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();
    }
}

Teraz w teście jednostkowym tworzysz makietę FooDataProvider, która pozwala na wywołanie metody GetAllFoos bez konieczności wchodzenia do bazy danych.

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)
    }
}

Powszechny scenariusz wyśmiewania, w skrócie. Oczywiście nadal będziesz prawdopodobnie chciał przetestować swoje rzeczywiste połączenia z bazą danych, za które będziesz musiał trafić baza danych.

 71
Author: Doug R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-15 19:44:02

Idealnie, twoje obiekty powinny być uporczywe ignoranckie. Na przykład, powinieneś mieć "warstwę dostępu do danych", do której składasz żądania, które zwracają obiekty. W ten sposób można pozostawić tę część z testów jednostkowych lub przetestować je w izolacji.

Jeśli obiekty są ściśle powiązane z warstwą danych, trudno jest wykonać odpowiednie testy jednostkowe. pierwsza część testu jednostkowego to "unit". Wszystkie jednostki powinny być w stanie być badane w izolacji.

W moich projektach c# używam NHibernate z całkowicie oddzielną warstwą danych. Moje obiekty żyją w modelu domeny podstawowej i są dostępne z warstwy aplikacji. Warstwa aplikacji komunikuje się zarówno z warstwą danych, jak i warstwą modelu domeny.

Warstwa aplikacji jest czasami nazywana "warstwą biznesową".

Jeśli używasz PHP, Utwórz określony zestaw klas dla tylko dla dostępu do danych. Upewnij się, że Twoje obiekty nie mają pojęcia, w jaki sposób są utrzymywane i połącz te dwa elementy w aplikacji klasy.

Inną opcją byłoby użycie mocking/stubs.

 23
Author: Sean Chambers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:49:24

Najprostszym sposobem testowania jednostkowego obiektu z dostępem do bazy danych jest użycie zakresów transakcji.

Na przykład:

    [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);
        }
    }

Spowoduje to przywrócenie stanu bazy danych, w zasadzie jak wycofanie transakcji, dzięki czemu można uruchomić test tyle razy, ile chcesz, bez żadnych efektów ubocznych. Z powodzeniem wykorzystaliśmy to podejście w dużych projektach. Nasza budowa trwa trochę długo (15 minut), ale nie jest straszna dla testów jednostkowych 1800. Również, jeśli czas budowy jest problemem, możesz zmienić proces budowania, aby mieć wiele kompilacji, jeden do budowania src, drugi, który odpala się później, który obsługuje testy jednostkowe, analizę kodu, pakowanie itp...

 11
Author: BZ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 18:14:54

Powinieneś wyśmiewać dostęp do bazy danych, jeśli chcesz przetestować swoje klasy. W końcu nie chcesz testować bazy danych w teście jednostkowym. To byłby test integracyjny.

Abstract wywołania z dala, a następnie wstawić makietę, która po prostu zwraca oczekiwane dane. Jeśli Twoje klasy nie robią więcej niż wykonują zapytania, może nawet nie warto ich testować...

 6
Author: Martin Klinke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:49:28

Mogę być może dać ci przedsmak naszego doświadczenia, kiedy zaczęliśmy przyglądać się jednostkowym testom naszego procesu średniego szczebla, który obejmował mnóstwo operacji sql "logiki biznesowej".

Najpierw stworzyliśmy warstwę abstrakcji, która pozwoliła nam na "gniazdo" dowolnego rozsądnego połączenia z bazą danych (w naszym przypadku po prostu obsługiwaliśmy pojedyncze połączenie typu ODBC).

Kiedy już to było na miejscu, byliśmy wtedy w stanie zrobić coś takiego w naszym kodzie (pracujemy w C++, ale jestem pewien, że dostaniesz idea):

GetDatabase ().ExecuteSQL ("INSERT INTO foo (blah, blah)")

W normalnym czasie działania GetDatabase() zwróci obiekt, który dostarczy wszystkie nasze zapytania sql (łącznie z zapytaniami), poprzez ODBC bezpośrednio do bazy danych.

Potem zaczęliśmy przyglądać się bazom danych w pamięci - najlepszym przez długi czas wydaje się być SQLite. ( http://www.sqlite.org/index.html). jest niezwykle prosty w konfiguracji i użyciu, i pozwolił nam podklasę i nadpisać GetDatabase (), aby przekazać sql do baza danych w pamięci, która została utworzona i zniszczona dla każdego wykonanego testu.

Wciąż jesteśmy na wczesnym etapie, ale wygląda to dobrze na razie, jednak musimy upewnić się, że tworzymy wszelkie tabele, które są wymagane i wypełniamy je danymi testowymi - jednak zmniejszyliśmy nieco obciążenie pracą, tworząc ogólny zestaw funkcji pomocniczych, które mogą zrobić wiele z tego wszystkiego dla nas.

Ogólnie rzecz biorąc, bardzo pomogło to w naszym procesie TDD, ponieważ sprawiło, że to, co wydaje się całkiem nieszkodliwe zmiany mające na celu naprawienie pewnych błędów mogą mieć dość dziwny wpływ na inne (trudne do wykrycia) obszary systemu - ze względu na sam charakter sql/baz danych.

Oczywiście nasze doświadczenia koncentrowały się wokół środowiska programistycznego C++, jednak jestem pewien, że możesz uzyskać coś podobnego działającego pod PHP / Python.

Mam nadzieję, że to pomoże.

 6
Author: Alan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-06 23:04:23

Książka xUnit test Patterns opisuje kilka sposobów obsługi kodu testowania jednostek, który trafia do bazy danych. Zgadzam się z innymi, którzy mówią, że nie chcesz tego robić, bo to powolne, ale musisz to kiedyś zrobić, IMO. Wyśmiewanie połączenia db w celu przetestowania rzeczy wyższego poziomu jest dobrym pomysłem, ale sprawdź tę książkę, aby uzyskać sugestie dotyczące rzeczy, które możesz zrobić, aby wejść w interakcję z rzeczywistą bazą danych.

 4
Author: Chris Farmer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:59:51

Dostępne opcje:

  • napisz skrypt, który usunie bazę danych przed rozpoczęciem testów jednostkowych, a następnie zapełni bazę danych predefiniowanym zestawem danych i uruchomi testy. Możesz to zrobić również przed każdym testem – będzie to powolne, ale mniej podatne na błędy.
  • Wstrzyknąć bazę danych. (Przykład w pseudo-Javie, ale dotyczy wszystkich języków OO)

    class Database {
     public Result query(String query) {... real db here ...}
    }
    
    

    Class MockDatabase extends Database { public Result query(String query) { return "mock result"; } }

    Class ObjectThatUsesDB { public ObjectThatUsesDB(Database db) { this.database = db; } }

    teraz w produkcji używasz zwykłej bazy danych i dla wszystkich testów po prostu wstrzykujesz makietę bazy danych, którą możesz utworzyć ad hoc.
  • Do nie używaj w ogóle DB w większości kodu (to i tak zła praktyka). Utwórz obiekt "database", który zamiast zwracać z wynikami zwróci normalne obiekty (tzn. zwróci User zamiast krotki {name: "marcin", password: "blah"}) zapisz wszystkie testy z obiektami zbudowanymi ad hoc prawdziwymi i napisz jeden duży test, który zależy od bazy danych, która upewni się, że ta konwersja działa dobrze.

Oczywiście te podejścia nie wykluczają się wzajemnie i można je mieszać i dopasowywać w zależności od potrzeb.

 4
Author: Marcin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 18:11:37

Zazwyczaj staram się rozdzielić moje testy między testowanie obiektów (i ORM, jeśli istnieją) i testowanie db. Testuję obiektową stronę rzeczy, wyśmiewając wywołania dostępu do danych, podczas gdy testuję stronę db rzeczy, testując interakcje obiektu z db, które są, z mojego doświadczenia, zwykle dość ograniczone.

Kiedyś byłem sfrustrowany pisaniem testów jednostkowych, dopóki nie zacząłem szydzić z części dostępu do danych, więc nie musiałem tworzyć testowego bazy danych ani generować testowych danych w locie. Przez wyśmiewając dane możesz wygenerować je wszystkie w czasie wykonywania i mieć pewność, że Twoje obiekty działają poprawnie ze znanymi wejściami.

 2
Author: akmad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:51:40

Nigdy nie robiłem tego w PHP i nigdy nie używałem Pythona, ale to, co chcesz zrobić, to wyśmiewać połączenia do bazy danych. Aby to zrobić, można zaimplementować niektóre IoC czy 3rd party narzędzie lub zarządzać nim samodzielnie, następnie można zaimplementować niektóre mock wersja bazy danych dzwoniącego, który jest, gdzie będzie kontrolować wynik tego fałszywego połączenia.

Prosta forma IoC może być wykonywana tylko przez kodowanie do interfejsów. Wymaga to pewnego rodzaju orientacji obiektu w kodzie więc może to nie dotyczyć tego ,co robisz (mówię, że ponieważ wszystko, co mam do zrobienia, to wzmianka o PHP i Pythonie)

Mam nadzieję, że to pomocne, jeśli nic więcej masz jakieś warunki do wyszukiwania teraz.

 2
Author: codeLes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:55:53

Zgadzam się z pierwszym Post-dostęp do bazy danych powinien zostać usunięty do warstwy DAO, która implementuje interfejs. Następnie możesz przetestować swoją logikę pod kątem implementacji początkowej warstwy DAO.

 2
Author: Chris Marasti-Georg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:56:19

Możesz użyć szyderczych frameworków do abstrakcji silnika bazy danych. Nie wiem czy PHP / Python ma jakieś ale dla języków typowanych (C#, Java itp.) istnieje wiele możliwości

Zależy również od tego, jak zaprojektowałeś kod dostępu do bazy danych, ponieważ niektóre projekty są łatwiejsze do przetestowania jednostkowego niż inne, o których wspominaliśmy we wcześniejszych postach.

 2
Author: chakrit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-27 17:59:02

Testowanie jednostkowe dostęp do bazy danych jest wystarczająco łatwe, jeśli projekt ma dużą spójność i luźne powiązania w całym projekcie. W ten sposób możesz testować tylko to, co robi każda klasa, bez konieczności testowania wszystkiego na raz.

Na przykład, jeśli testujesz jednostkowo klasę interfejsu użytkownika, testy, które piszesz, powinny tylko sprawdzać, czy logika wewnątrz interfejsu działa zgodnie z oczekiwaniami, a nie logika biznesowa lub akcja bazy danych za tą funkcją.

Jeśli chcesz test jednostkowy rzeczywisty dostęp do bazy danych w rzeczywistości skończy się bardziej testem integracji, ponieważ będziesz zależny od stosu sieciowego i serwera bazy danych, ale możesz sprawdzić, czy Twój kod SQL robi to, o co go poprosiłeś.

Ukrytą siłą testów jednostkowych dla mnie osobiście było to, że zmusza mnie to do projektowania moich aplikacji w znacznie lepszy sposób niż mógłbym bez nich. To dlatego, że naprawdę pomogło mi oderwać się od "ta funkcja powinna robić wszystko" mentalność.

Przepraszam, że nie mam żadnych konkretnych przykładów kodu dla PHP / Python, ale jeśli chcesz zobaczyć przykład. NET mam post opisujący technikę, której użyłem do tego samego testowania.

 2
Author: Toran Billups,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:42

Ustawienie danych testowych dla testów jednostkowych może być wyzwaniem.

Jeśli chodzi o Javę, jeśli używasz API Spring do testowania jednostek, możesz kontrolować transakcje na poziomie jednostek. Innymi słowy, można wykonać testy jednostkowe, które obejmują aktualizacje/wstawianie/usuwanie bazy danych i wycofywanie zmian. Pod koniec wykonania pozostawiasz wszystko w bazie danych tak, jak było przed rozpoczęciem wykonania. Dla mnie jest tak dobry, jak tylko może być.

 2
Author: Bino Manjasseril,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-18 21:50:14