2013-03-27 10 views
6

Używam Django REST Framework dla API, nad którym pracuję. Z kilku powodów chciałbym użyć widoków opartych na klasach. Jednak jestem trochę szczególny na temat moich testów jednostkowych i nigdy nie pozwalam, aby moje testy jednostek dotykały bazy danych. Uwaga: zawsze używam "triku", o którym mówił Carl Meyer w Pycon 2012, gdzie drwi z opakowania Cursora.Funkcje szyderstwa w widokach opartych na klasach Django

cursor_wrapper = Mock() 
cursor_wrapper.side_effect = RuntimeError("No touching the database!") 

@patch('django.db.backends.util.CursorWrapper', cursor_wrapper) 
class TestMyCode(TestCase): 

Oto link, jeśli jesteś zainteresowany slajdem.

Mam metodę w jednym z widoków, które sprawdza coś w bazie danych. Aby być DRY, jest on dzielony pomiędzy POST i PUT. Ale mam problemy z kpiną z mojego testu jednostkowego. Dzieje się tak dlatego, że metoda class as_view tworzy nową instancję i wysyłkę klasy i zwraca funkcję "handler", którą wywołanie zwraca. Tak więc nie wydaje mi się, aby uzyskać metodę wspólną w moim widoku opartym na klasie, aby ją wyśmiewać.

Mogę wyśmiać modele używane w widoku klasowym, ale muszę zasadniczo złamać mój cel, jakim jest "SUCHA" i skopiować kod zarówno w POST jak i PUT. Sądzę, że mógłbym zmienić kod i przenieść logikę do Modelu. Ale nie jestem pewien, że chcę to zrobić.

Jak wyłudzić wspólną metodę widoku opartego na klasach, aby uniknąć rzeczywistego dotknięcia bazy danych? Po prostu ich unikaj?

Odpowiedz

3

Myślę, że odpowiedziałeś na własne pytanie. Te same rzeczy, których używasz do testowania jakiejkolwiek struktury sieciowej, odnoszą się do Django, na przykład inwersja wtrysku sterującego i zależności. W Pythonie możesz zachować go w prosty sposób, więc nie daj się zastraszyć ani wyłączyć przez to, co istnieje na przykład w Spring.

Dlaczego przenieść kod z widoku opartego na klasach? Twój kod nadal nie będzie SUCHY, jeśli z jakiegoś powodu potrzebujesz tej samej logiki w innym miejscu. To, że Django nie oznacza dobrych zasad programowania, nie ma zastosowania.

Proponuję po prostu abstrahować niektóre rzeczy w nowych klasach/modułach Pythona, takich jak usługi (jako koncepcja, a nie definicja usług Django) i inne abstrakcje logiczne dla dostępu do danych. Następnie jesteś całkowicie niezależny od cyklu życia żądania/odpowiedzi w widoku Django. Twórcy Django i Rails mają skłonność do umieszczania każdego elementu logiki bezpośrednio w modelu lub widoku. To po prostu prowadzi do klas Boga i rzeczy, które są trudne do przetestowania.

Możesz to również ułatwić, myśląc o swoim widoku jako lekkiej abstrakcji, która obsługuje takie rzeczy, jak paramerty sortujące (GET/POST) itd. Do reszty kodu i wywoływanie logiki koniecznej gdzie indziej. IMO, jeśli chcesz testowalny kod, 99% logiki powinno znajdować się poza kontekstem sieci, chyba że jest to absolutnie niezbędne dla procesu. Ułatwia to również prowadzenie działań w tle i równolegle.

Powinny powstać z normalnych modułów i klas Pythona, które można łatwo przetestować, ponieważ nie mają bezpośrednich zależności od HTTP. Jeśli chcesz kpić z HTTP, możesz po prostu sfałszować obiekt żądania. Masz szczęście, ponieważ kombinacja python/django ułatwia zrzucanie i drwi z tych rzeczy jako prostych dicts/kwargs.

Jedną z rzeczy, które udało mi się zrealizować przy użyciu widoków opartych na klasach, są dobre do używania z mieszankami i wymuszania niektórych konwencji (zwracanie jsonów, właściwości otwartego wykresu itp.), Ale korzystanie z bardziej "zaawansowanych" widoków opartych na klasach, które bezpośrednio wymagają modeli takie jak DetailView po prostu komplikują niepotrzebnie. Te widoki są dobre dla ekranów administracyjnych, ale dla prawdziwych aplikacji pomagają bardziej niż boli. Sprawiają, że trudno jest przetestować i zabić wydajność, chyba że znajdziesz ładny, bezproblemowy sposób na integrację warstw pamięci podręcznej i tym podobnych. W tym momencie zazwyczaj po prostu dziedziczy po widoku lub TemplateView i kończy się z nim.

Jeśli chodzi o kpiny z DB, stwórz swoje drwiny i przejdź przez logikę biznesową. Wtedy nie ma znaczenia, co wprowadzasz/wyprowadza, o ile jest zgodny z określonym zestawem reguł i interfejsów. Zobacz na przykład coś takiego jak Mixer. Możesz także po prostu tworzyć/niszczyć tymczasowe bazy danych podczas testowania. Jednym ze sposobów jest utworzenie osobnych modułów ustawień dla dev/inscenizacja/produkcja/testowanie, itp. I ładowanie ich dynamicznie w zależności od środowiska. W ten sposób unikniesz uszkodzenia działającej bazy danych dev po uruchomieniu testów jednostkowych. Oczywiście przechodzi to bardziej w formę testów integracyjnych, ale prawdopodobnie powinieneś także zrobić to trochę. Wyżej wymienione rozwiązania są powszechne w innych ORMach, takich jak Hibernate.

W powiązaniu z poprzednim, możesz zrobić coś w rodzaju poniższego kodu w swoich ustawieniach, aby użyć bazy danych w pamięci do testowania jednostkowego. Ostatecznie jednak, trzeba jeszcze rozważyć badanie integracyjnej wobec rzeczywistego typu magazynu danych, na przykład MySQL

if 'test' in sys.argv: 
    DATABASES['default']['ENGINE'] = 'sqlite3' 

; tldr

  1. Umieść swoje poglądy logiczne klasy zewnątrz do odpowiednich przedmiotów i modułów.

  2. Nie zamykaj się, próbując sprawić, aby różne powiązane widoki oparte na klasach działały dla rzeczywistych aplikacji i dla każdego przypadku użycia; Skręć swój własny.

  3. Używaj ogólnie dobrych zasad TDD, takich jak IOC, przekazywanie wymaganych parametrów do konstruktorów, luźne łączenie elementów, unikanie nadmiernych zastrzeżonych wymagań dotyczących stanu (w szczególności HTTP).

    1. Unikaj zależności między DB, tworząc standardowe obiekty próbne (patrz # 3) i przechodząc przez interfejsy podobne do usług (patrz # 1).
Powiązane problemy