Önceki bölümlerde; Python (Django) ile Test Odaklı Geliştirme (TDD) – 2
FT’i ve Unit Test’i iyice netleştirmek için şöyle düşünebiliriz;
Uygulamamızı yaparken hem FT hem de Unit Test kullanacağız. Çalışma adımlarımız hemen hemen şu şekilde olacak;
FT ve Unit Testler’den birisi gereksiz diye düşünebilirsiniz. Fakat her ikisinin de oldukça farklı amaçları olduğunu unutmamak gerek.
Yeni bir Django “app”i oluşturarak devam edelim. Adı “lists
” olacak.
$ python manage.py startapp lists
Proje dizini hemen hemen şöyle gözükmeli;
. ├── db.sqlite3 ├── functional_tests.py ├── lists │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── manage.py ├── superlists │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── virtualenv ├── [...]
lists/tests.py
dosyamızı açalım. Django’nun bizim için TestCase
sınıfını import ettiğini görebiliriz. Bu TestCase
, Django’ya özel birkaç özelliğin eklendiği bir sınıf.
from django.test import TestCase # Create your tests here.
Testimizi yazmaya başlamadan önce, isterseniz burada commit
yapabilirsiniz.
Öncelikle iki şeyi test etmek istiyoruz;
view
fonksiyonunu döndürüyor mu?view
fonksiyonu, FT’den geçecek HTML sayfasını döndürüyor mu?lists/tests.py
dosyasını şu şekilde güncelleyelim;
from django.test import TestCase from django.urls import resolve from lists.views import home_page class HomePageTest(TestCase): def test_root_url_resolves_to_home_page_view(self): found = resolve('/') self.assertEqual(found.func, home_page)
4.satırdaki from lists.views import home_page
ifadesi kafanızı karıştırmasın. Henüz bu view
fonksiyonunu yazmadık ama şimdiden anlayabiliriz ki, istediğimiz HTML sayfasını bu fonksiyonun döndürmesini planlıyoruz. Testimizi çalıştıralım (doğal olarak hata bekliyoruz);
$ python manage.py test ImportError: cannot import name 'home_page'
Şimdi biraz uygulama kısmı için kod yazmaya başlayabiliriz. Ama önce TDD ne demekti hatırlayın; TDD demek, her seferinde bir adım atmak demekti. Ufak güzel adımlar…
O zaman ne yapıyoruz? Sadece aldığımız hatayı geçecek kadar kod yazıyoruz;
lists/views.py
dosyasını açalım ve düzenleyelim;
from django.shortcuts import render # Create your views here. home_page = None
Evet, sadece bu kadar. Şimdi testimizi tekrar çalıştıralım;
$ python manage.py test Creating test database for alias 'default'... E ====================================================================== ERROR: test_root_url_resolves_to_home_page_view (lists.tests.HomePageTest) --------------------------------------------------------------------- Traceback (most recent call last): File "...python-tdd-book/lists/tests.py", line 8, in test_root_url_resolves_to_home_page_view found = resolve('/') File ".../django/urls/base.py", line 27, in resolve return get_resolver(urlconf).resolve(path) File ".../django/urls/resolvers.py", line 392, in resolve raise Resolver404({'tried': tried, 'path': new_path}) django.urls.exceptions.Resolver404: {'tried': [[<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>]], 'path': ''} --------------------------------------------------------------------- Ran 1 test in 0.002s FAILED (errors=1) System check identified no issues (0 silenced). Destroying test database for alias 'default'...
Farklı bir hata aldık. Yeni küçük güzel adımlar atma zamanı. Hata, aradığımız path
‘in bulunamadığını söylüyor. O zaman, superlists/urls.py
dosyasını açıp düzenleyelim;
from django.conf.urls import url from django.contrib import admin from lists import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', views.home_page, name='home'), ]
Ve testimizi tekrar çalıştıralım;
$ python manage.py test [...] TypeError: view must be a callable or a list/tuple in the case of include().
404 hatası yerine yeni bir hatamız var. Harika!
Hatanın söylediği, view
‘ın (home_page
) geçersiz bir tipte olduğu. O zaman bunu düzeltelim;
lists/views.py
from django.shortcuts import render # Create your views here. def home_page(): pass
Tekrar testimizi çalıştırma zamanı 🙂 .
$ python manage.py test Creating test database for alias 'default'... . --------------------------------------------------------------------- Ran 1 test in 0.003s OK System check identified no issues (0 silenced). Destroying test database for alias 'default'...
O zaman hemen bi commit yapalım: git commit -am "First unit test and url mapping, dummy view"
Şimdi, hiçbir şey yapmayan bu view fonksiyonunu, HTML sayfası döndürecek hale sokalım;
lists/tests.py
from django.urls import resolve from django.test import TestCase from django.http import HttpRequest from lists.views import home_page class HomePageTest(TestCase): def test_root_url_resolves_to_home_page_view(self): found = resolve('/') self.assertEqual(found.func, home_page) def test_home_page_returns_correct_html(self): request = HttpRequest() response = home_page(request) html = response.content.decode('utf8') self.assertTrue(html.startswith('<html>')) self.assertIn('<title>To-Do lists</title>', html) self.assertTrue(html.endswith('</html>'))
Testimizi çalıştıralım;
TypeError: home_page() takes 0 positional arguments but 1 was given
Döngümüzü hatırlayalım;
İşte hatamızı çözecek kadar gereken minimal kod değişikliği;
lists/views.py
def home_page(request): pass
Test:
html = response.content.decode('utf8') AttributeError: 'NoneType' object has no attribute 'content'
Kod değişikliği;
lists/views.py
from django.http import HttpResponse # Create your views here. def home_page(request): return HttpResponse()
Test:
self.assertTrue(html.startswith('<html>')) AssertionError: False is not true
Kod;
lists/views.py
def home_page(request): return HttpResponse('<html>')
Test:
AssertionError: '<title>To-Do lists</title>' not found in '<html>'
Kod:
lists/views.py
def home_page(request): return HttpResponse('<html><title>To-Do lists</title>')
Test:
self.assertTrue(html.endswith('</html>')) AssertionError: False is not true
Kod:
lists/views.py
def home_page(request): return HttpResponse('<html><title>To-Do lists</title></html>')
Test:
$ python manage.py test Creating test database for alias 'default'... .. --------------------------------------------------------------------- Ran 2 tests in 0.001s OK System check identified no issues (0 silenced). Destroying test database for alias 'default'...
Hayır, bitmedi. FT çalıştırmaya geldi sıra. runserver
komutunu farklı bir terminalde çalıştırmayı unutmayın önce.
$ python functional_tests.py F ====================================================================== FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest) --------------------------------------------------------------------- Traceback (most recent call last): File "functional_tests.py", line 19, in test_can_start_a_list_and_retrieve_it_later self.fail('Finish the test!') AssertionError: Finish the test! --------------------------------------------------------------------- Ran 1 test in 1.609s FAILED (failures=1)
Yine mi hata? Minimal kod değişikliği yapmaya mı başlıyoruz?
Hayır 😀 . Evet, FT başarısız oldu ama hata veren kod satırı; self.fail('Finish the test!')
. Bu satırın amacı zaten, hata verdirmek. Testimizin bittiğini anlamak için biz yazmıştık.
Son bir kez daha commit yaptıktan sonra bu kısmı da bitirebiliriz.
$ git commit -am "Basic view now returns minimal HTML"
Bu yazı, Test-Driven Development with Python: Obey the Testing Goat: Using Django, Selenium, and JavaScript kitabının, yazarından da onayı alınarak, geniş şekilde Türkçe olarak özetlenmiş halidir. Kitabı okumaya ücretsiz olarak devam edebilir, destek olmak için satın alabilirsiniz. Detaylar için buraya tıklayabilirsiniz.
Celery ile alakalı "best practice"leri ve faydalı araçları bir araya getiren güzel bir checklist'e denk…
Diziler en temel ve sık kullandığımız araçlardan... Kod yazarken işimizi kolaylaştıracak, daha temiz kod yazmamızı…
listve tuple bilginizi test etmek ister misiniz? realpython.com da keşfettiğim ve Türkçe'ye çevirdiğim mini teste…
Rehberlik sağlaması ve bilgi tazelemesi açısından faydalı olduğunu düşündüğüm bir Toptal blog paylaşımınıTürkçe'ye çevirdim.Devamını okuyunBilmeniz…
Angular componentlerine console üzerinden hızlıca erişmek için kullanılan bir teknik. Unutmamak için kendime not düşüyorum.Devamını…
Geçtiğimiz günlerde keşfettiğim ve oldukça da hoşuma giden repoyu paylaşmak istiyorum: lydiahallie/javascript-questions Genel olarak temel…