Testy
Code without tests is broken by design.Jacobian
Rodzaje testów
- integracyjne
- systemowe
- akceptacyjne
- regresji
- jednostkowe
Testy ogólnie
from django.test.testcases import TestCase from example.models import Author class AuthorTestCase(TestCase): def test_method_get_full_name(self): author = Author(first_name='Marian', last_name='Opania') full_name = author.get_full_name() self.assertEqual(full_name, 'Marian Opania')
Co testować?
class CreateAuthorView(CreateView): model = Author form_class = AuthorForm
def form_valid(self, form): messsages.success(self.request, 'Added author!') return super(CreateAuthorView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(CreateAuthorView, self).get_context_data(**kwargs)
context['my_little_pony'] = PonyFactory(author=self.object)
return context
Nie testujemy
- Wbudowanych funkcji Pythona.
- Django.
- Czystej konfiguracji.
Testujemy
Całą resztę
Przygotuj
(ang. given/arrange)
Przygotuj dane
- Nie używamy fixtur.
- Tworzymy obiekty kiedy są potrzebne.
- Staramy się nie dotykać bazy.
- Używamy fabryk obiektów (np. model_mommy).
class AuthorTestCase(TestCase): def test_get_full_name_with_db_call(self): author = Author.objects.create(first_name='Marian', last_name='Opania') full_name = author.get_full_name() self.assertEqual(full_name, 'Marian Opania')
def test_get_full_name_with_no_db_calls(self): author = Author(first_name='Marian', last_name='Opania')
def test_get_full_name_with_mommy(self): author = mommy.prepare(Author, first_name='Marian', last_name='Opania')
Przygotuj "okoliczności"
class AuthorDetailViewTestCase(TestCase): def setUp(self): self.author = mommy.make(Author) self.view = AuthorDetailView.as_view()
# a lot of tests here
def tearDown(self): self.author.remove_some_author_stuff_from_outside_db()
class ElasticsearchTestCase(TestCase): index_name = 'test' def setUpClass(self): es.indices.remove(self.index_name) es.indices.create(self.index_name) def tearDownClass(self): es.indices.remove(self.index_name)
Zamockuj!
Mock to bardzo rozbudowana biblioteka, ale warto jej używać!
def test_form_valid_should_return_proper_info(self):
form = mock.Mock()
self.view.request = mock.Mock()
self.view.survey = mock.Mock(captcha_verification__return_value=False)
self.view.success_message = 'success'
self.view.render_to_response = lambda x: x
context = self.view.form_valid(form)
self.assertEqual(context['message'], 'success')
self.assertTrue(context['clearform'])
self.view.survey.responded.assert_called_once_with(context)
Podsumowanie
-
Nie nadużywaj setUp i setUpClass.
- Pisz własne funkcje wołane w testach.
- Używaj biblioteki mock.
- Pamiętaj o dekoratorze override_settings.
- Przygotowanie nie powinno być bolesne!
Zrób
(ang. when/act)
Dobre przykłady
template_name = view.get_template_name()
response = self.view.get(request)
with self.assertRaises(Http404):
self.view.get(request)
Z reguły więc jest to wywołanie pojedynczej, testowanej funkcji, która została wcześniej maksymalnie odizolowana od innych.
Sprawdź
(ang. then/assert)
Asercje i nie tylko
class ConfirmResponseViewTestCase(K2TestCase): view_class = ConfirmResponseView def test_response_should_be_confirmed_and_time_should_be_set(self): before = datetime.datetime.now() response = mommy.make(models.Response, confirmed=False) request = self.request_factory.get('') self.view.request = request
self.view.get(request=request) refreshed_response = models.Response.objects.get(id=response.id) self.assertTrue(refreshed_response.confirmed) self.assertGreaterEqual(refreshed_response.time_confirmed, before)
Wnioski
- Asercji nie powinno być za wiele.
- Można pisać własne asercje!
- Warto czasem użyć self.fail()
Twój test powinienbyć jak dobre haikutak samo prosty
Mistrz Karimata
Egzegeza haiku Mistrza Karimaty
Czasami zdarza się tak, że musimy napisać dużo dziwnych rzeczy, żeby coś przetestować, a sam test staje się długi i nieczytelny.
To znak, że najwyraźniej nie testujemy czegoś
naprawdę jednostkowo.
Niewykluczone, że to sam kod wymaga zmiany i lepszego podziału!
Testy w Django
problemy i wyzwania ;)
Unit test?
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self): response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200) def test_index(self): response = self.client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
Unit test? Nie!
Przetestowaliśmy:
- Urlresolvera i nasze ustawienia w urls.py
- Wszelkie dekoratory (login_required, etc.)
- Wszystkie middlewary.
- Renderowanie szablonu, template tagi.
- Sam widok.
Testowanie widoków klasowych 1
Używajmy RequestFactory i TemplateResponse.
from django.test import TestCase, RequestFactory
class SimpleTest(TestCase):
def setUp(self): self.factory = RequestFactory() def test_details(self): request = self.factory.get('/customer/details') response = my_view(request)
self.assertEqual(response.status_code, 200) self.assertIn('customer', response.context_data)
Testowanie widoków klasowych 2
Testujmy jednostkowo pojedyncze metody widoku!
Musimy sami "przygotować" magię, która normalnie dzieje się w metodzie as_view.
def test_single_view_method(self):
request = self.factory.get('/')
self.view.request = request
self.view.kwargs = {
'code': 'small code',
}
result = self.view.method_to_test()
self.assertEqual(result, 'big code')
Inne problemy
- Pliki zostające między testami.
- Skąd brać pliki do testów?
Zapamiętaj
- Testowanie wcale nie boli!
- ... a jeżeli boli, to nie jest to wina testów, tylko kodu.
- Test jednostkowy dobrze izoluje testowany kod.
- Testy jednostkowe to większość, ale nie wszystkie testy.
Napisz test!
testy
By Szymon Teżewski
testy
- 2,350