πŸ” Djangoμ—μ„œ λ‹¨μœ„ ν…ŒμŠ€νŠΈ μ‹€ν–‰ν•˜κΈ°

λ‹¨μœ„ ν…ŒμŠ€νŠΈμ™€ κΈ°λŠ₯ ν…ŒμŠ€νŠΈμ˜ 차이

κΈ°λŠ₯ ν…ŒμŠ€νŠΈ

μ‚¬μš©μž κ΄€μ μ—μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ™ΈλΆ€λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” 것 μƒμœ„ 레벨의 개발 주도

λ‹¨μœ„ ν…ŒμŠ€νŠΈ

ν”„λ‘œκ·Έλž˜λ¨Έ κ΄€μ μ—μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄λΆ€λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” 것 ν•˜μœ„ 레벨의 개발 주도

TDD μž‘μ—… μˆœμ„œ

  1. κΈ°λŠ₯ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•΄μ„œ μ‚¬μš©μž κ΄€μ μ˜ μƒˆλ‘œμš΄ κΈ°λŠ₯성을 μ •μ˜ν•˜λŠ” 것뢀터 μ‹œμž‘ν•œλ‹€.
  2. κΈ°λŠ₯ ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•˜κ³  λ‚˜λ©΄ μ–΄λ–»κ²Œ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Ό ν…ŒμŠ€νŠΈλ₯Ό 톡과할지λ₯Ό 생각해보도둝 ν•œλ‹€. 이 μ‹œμ μ—μ„œ ν•˜λ‚˜ λ˜λŠ” κ·Έ μ΄μƒμ˜ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μ΄μš©ν•΄μ„œ μ–΄λ–»κ²Œ μ½”λ“œκ°€ λ™μž‘ν•΄μ•Ό ν•˜λŠ”μ§€ μ •μ˜ν•œλ‹€.
  3. λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•˜κ³  λ‚˜λ©΄ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό 톡과할 수 μžˆμ„ μ •λ„μ˜ μ΅œμ†Œν•œμ˜ μ½”λ“œλ§Œ μž‘μ„±ν•œλ‹€. κΈ°λŠ₯ ν…ŒμŠ€νŠΈκ°€ μ™„μ „ν•΄μ§ˆ λ•ŒκΉŒμ§€ κ³Όμ • 2와 3을 λ°˜λ³΅ν•΄μ•Ό ν•  μˆ˜λ„ μžˆλ‹€.
  4. κΈ°λŠ₯ ν…ŒμŠ€νŠΈλ₯Ό μž¬μ‹€ν–‰ν•΄μ„œ ν†΅κ³Όν•˜λŠ”μ§€ λ˜λŠ” μ œλŒ€λ‘œ λ™μž‘ν•˜λŠ”μ§€ ν™•μΈν•œλ‹€. 이 κ³Όμ •μ—μ„œ μƒˆλ‘œμš΄ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•΄μ•Ό ν•  μˆ˜λ„ μžˆλ‹€.

Djangoμ—μ„œμ˜ λ‹¨μœ„ ν…ŒμŠ€νŠΈ

startapp을 톡해 앱을 μƒμ„±ν•˜κ²Œ 되면 ν•˜μœ„μ— tests.py λΌλŠ” 파일이 생긴닀. ν•΄λ‹Ή 파일의 λ‚΄μš©μ€ λ‹€μŒκ³Ό κ°™λ‹€.

from django.test import TestCase

# Create your tests here.

Django의 TestCaseλŠ” unittest.TestCase의 ν™•μž₯ λ²„μ „μœΌλ‘œ Django에 λŒ€ν•œ νŠΉν™” κΈ°λŠ₯듀이 μΆ”κ°€λ˜μ–΄ μžˆλ‹€κ³  ν•œλ‹€. κ°„λ‹¨ν•œ ν…ŒμŠ€νŠΈλ₯Ό λ§Œλ“€μ–΄ μ‹€ν–‰ν•΄λ³΄μž.

from django.test import TestCase


class SmokeTest(TestCase):
    def test_bad_maths(self):
        self.assertEqual(1 + 1, 3)

μ‹€νŒ¨ν•  수 밖에 μ—†λŠ” ν…ŒμŠ€νŠΈμ΄μ§€λ§Œ μš°μ„  μ‹€ν–‰ν•΄λ³΄μž.

python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_bad_maths (superlists.lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/yugyeong/Study/TDD4CleanCode/superlists/lists/tests.py", line 6, in test_bad_maths
    self.assertEqual(1+1, 3)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

μ œλŒ€λ‘œ λ™μž‘ν•œλ‹€.

System check identified no issues (0 silenced).
EE
======================================================================
ERROR: superlists.lists (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: superlists.lists
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 462, in _find_test_path
    package = self._get_module_from_name(name)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'superlists.lists'

ν˜Ήμ‹œ 참고쀑인 λ„μ„œμ˜ λ‚΄μš©μ„ κ·ΈλŒ€λ‘œ λ”°λΌν•˜λ‹€κ°€ ν˜„ μ‹œμ μ—μ„œ μœ„μ™€ 같은 μ—λŸ¬κ°€ λ‚˜μ‹  뢄이 μžˆλ‹€λ©΄ ν”„λ‘œμ νŠΈ 루트 디렉토리 μ•„λž˜μ˜ superlists ν΄λ”μ˜ 이름을 μž„μ‹œλ‘œ λ³€κ²½ν•΄μ•Ό ν•œλ‹€. 루트 디렉토리 이름과 λ™μΌν•΄μ„œ ImportErrorκ°€ λ‚˜λŠ” 것이닀. ν•˜μ§€λ§Œ 또 이뢀뢄을 μˆ˜μ •ν•˜λ‹ˆ μ„œλ²„λ₯Ό μΌ€ λ•Œ λ¬Έμ œκ°€ μžˆλ‹€β€¦ 이 뢀뢄은 더 고민해봐야할 것 κ°™λ‹€.

λͺ©ν‘œ

Django의 기본적인 처리 흐름은 λ‹€μŒκ³Ό κ°™λ‹€.

  1. νŠΉμ • URL에 λŒ€ν•œ HTTP μš”μ²­μ„ λ°›λŠ”λ‹€.
  2. DjangoλŠ” νŠΉμ • κ·œμΉ™μ„ μ΄μš©ν•΄μ„œ ν•΄λ‹Ή μš”μ²­μ— μ–΄λ–€ λ·° ν•¨μˆ˜λ₯Ό 싀행할지 κ²°μ •ν•œλ‹€.
  3. 이 λ·° κΈ°λŠ₯이 μš”μ²­μ„ μ²˜λ¦¬ν•΄μ„œ HTTP μ‘λ‹΅μœΌλ‘œ λ°˜ν™˜ν•œλ‹€.

λ”°λΌμ„œ ν…ŒμŠ€νŠΈν•΄μ•Ό ν•  것은 두 가지이닀.

  • URL의 μ‚¬μ΄νŠΈ 루트(/)λ₯Ό ν•΄μ„ν•΄μ„œ νŠΉμ • λ·° κΈ°λŠ₯에 λ§€μΉ­μ‹œν‚¬ 수 μžˆλŠ”κ°€?
  • 이 λ·° κΈ°λŠ₯이 νŠΉμ • HTML을 λ°˜ν™˜ν•˜κ²Œ ν•΄μ„œ κΈ°λŠ₯ ν…ŒμŠ€νŠΈλ₯Ό 톡과할 수 μžˆλŠ”κ°€?

첫 번째 ν…ŒμŠ€νŠΈ

μœ„μ˜ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜μž.

from django.test import TestCase
from django.urls import resolve
from .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)

resolveλŠ” Django의 λ‚΄λΆ€ ν•¨μˆ˜λ‘œ URL을 ν•΄μ„ν•΄μ„œ μΌμΉ˜ν•˜λŠ” λ·° ν•¨μˆ˜λ₯Ό μ°ΎλŠ”λ‹€. μ—¬κΈ°μ„œλŠ” /κ°€ 호좜될 λ•Œ resolveλ₯Ό μ‹€ν–‰ν•΄μ„œ home_pageλΌλŠ” ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œλ‹€.

home_pageλŠ” 곧 μž‘μ„±ν•  λ·° ν•¨μˆ˜λ‘œ HTML을 λ°˜ν™˜ν•œλ‹€.

ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄λ³΄μž.

System check identified no issues (0 silenced).
E
======================================================================
ERROR: superlists.lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: superlists.lists.tests
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "/Users/yugyeong/Study/TDD4CleanCode/superlists/lists/tests.py", line 3, in <module>
    from .views import home_page
ImportError: cannot import name 'home_page'

첫 번째 μ—λŸ¬λ‘œ home_pageλ₯Ό import ν•  수 μ—†λ‹€κ³  λœ¬λ‹€. κ·Έλ ‡λ‹€λ©΄ home_page ν•¨μˆ˜λ₯Ό μž‘μ„±ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€. μš°μ„  views.py에 home_pageλ₯Ό μ„ μ–Έν•΄μ£Όμž.

from django.shortcuts import render

home_page = None

그리고 λ‹€μ‹œ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄λ³΄μž.

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_root_url_resolves_to_home_page_view (superlists.lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/yugyeong/Study/TDD4CleanCode/superlists/lists/tests.py", line 8, in test_root_url_resolves_to_home_page_view
    found = resolve('/')
  File "/Users/yugyeong/Study/TDD4CleanCode/venv/lib/python3.6/site-packages/django/urls/base.py", line 24, in resolve
    return get_resolver(urlconf).resolve(path)
  File "/Users/yugyeong/Study/TDD4CleanCode/venv/lib/python3.6/site-packages/django/urls/resolvers.py", line 566, in resolve
    raise Resolver404({'tried': tried, 'path': new_path})
django.urls.exceptions.Resolver404: {'tried': [[<URLResolver <URLPattern list> (admin:admin) 'admin/'>]], 'path': ''}

----------------------------------------------------------------------
Ran 1 test in 0.005s

FAILED (errors=1)
Destroying test database for alias 'default'...

μ—λŸ¬ λ‚΄μš©μ΄ λ°”λ€Œμ—ˆλ‹€. μ΄λ²ˆμ—λŠ” URL κ΄€λ ¨ μ—λŸ¬λ‹€. μš°λ¦¬κ°€ μ°ΎμœΌλ €λŠ” /에 ν•΄λ‹Ήν•˜λŠ” URL 맀핑을 찾을 수 μ—†μ–΄μ„œ 404 μ—λŸ¬κ°€ λ°œμƒν•œ 걸둜 보인닀. urls.py νŒŒμΌμ„ μ—΄μ–΄μ„œ URL νŒ¨ν„΄μ„ μΆ”κ°€ν•΄μ£Όμž.

from django.urls import path, include

from superlists.lists.views import *

urlpatterns = [
    path('', home_page, name='home'),
]

λ‹€μ‹œ ν…ŒμŠ€νŠΈν•΄λ³΄μž.

(μƒλž΅)
TypeError: view must be a callable or a list/tuple in the case of include().

viewλ₯Ό ν˜ΈμΆœν•  수 μ—†λ‹€λŠ” λ©”μ‹œμ§€κ°€ 뜨고 μžˆλ‹€. home_pageκ°€ 아직 ν•¨μˆ˜κ°€ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ΄λ‹€. 이제 μ‹€μ œ ν•¨μˆ˜λ‘œ 변경해보도둝 ν•˜μž. λ‹€μ‹œ views.py둜 λŒμ•„κ°€ home_pageλ₯Ό ν•¨μˆ˜μ˜ ν˜•νƒœλ‘œ λ°”κΎΌλ‹€.

def home_page():
    pass

λ‹€μ‹œ ν…ŒμŠ€νŠΈν•΄λ³΄μž.

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

λ“œλ””μ–΄ 첫 λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν–ˆλ‹€.

두 번째 ν…ŒμŠ€νŠΈ

λ·°λ₯Ό μœ„ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  λ•ŒλŠ” 빈 ν•¨μˆ˜λ₯Ό μž‘μ„±ν•˜λŠ” 것이 μ•„λ‹ˆλΌ HTML ν˜•μ‹μ˜ μ‹€μ œ 응닡을 λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό μž‘μ„±ν•΄μ•Ό ν•œλ‹€. test.pyλ₯Ό μ—΄μ–΄ λ‹€μŒκ³Ό 같이 μƒˆλ‘œμš΄ ν…ŒμŠ€νŠΈλ₯Ό μΆ”κ°€ν•˜μž.

from django.http import HttpRequest
from django.test import TestCase
from django.urls import resolve
from .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_to_home_page_view(self):
        request = HttpRequest()
        response = home_page(request)
        self.assertTrue(response.content.startswith(b'<html>'))
        self.assertIn(b'<title>To-Do lists</title>', response.content)
        self.assertTrue(response.content.endswith(b'</html>'))

HttpResponse 객체λ₯Ό μƒμ„±ν•΄μ„œ μ‚¬μš©μžκ°€ μ–΄λ–€ μš”μ²­μ„ λΈŒλΌμš°μ €μ— λ³΄λ‚΄λŠ”μ§€ ν™•μΈν•œλ‹€.

이것을 home_page 뷰에 μ „λ‹¬ν•΄μ„œ 응닡을 μ·¨λ“ν•œλ‹€. 이 κ°μ²΄λŠ” HttpResponseλΌλŠ” 클래슀의 μΈμŠ€ν„΄μŠ€λ‹€. 응닡 λ‚΄μš©μ΄ νŠΉμ • 속성을 가지고 μžˆλŠ”μ§€ ν™•μΈν•œλ‹€.

κ·Έ λ‹€μŒμ€ 응닡 λ‚΄μš©μ΄ <html>둜 μ‹œμž‘ν•˜κ³  </html>둜 λλ‚˜λŠ”μ§€ ν™•μΈν•œλ‹€. response.contentλŠ” byteν˜• λ°μ΄ν„°λ‘œ 파이썬 λ¬Έμžμ—΄μ΄ μ•„λ‹ˆλ‹€. λ”°λΌμ„œ b' ꡬ문을 μ‚¬μš©ν•΄μ„œ λΉ„κ΅ν•œλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œ λ°˜ν™˜ λ‚΄μš©μ˜ <title> νƒœκ·Έμ— To-Do listsλΌλŠ” 단어가 μžˆλŠ”μ§€ ν™•μΈν•œλ‹€.

ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄λ³΄μž.

(μƒλž΅)
TypeError: home_page() takes 0 positional arguments but 1 was given

μ—¬κΈ°μ„œλΆ€ν„° TDD λ‹¨μœ„ ν…ŒμŠ€νŠΈ - μ½”λ“œ 주기에 λŒ€ν•΄ 생각해야 ν•œλ‹€.

λ‹¨μœ„ ν…ŒμŠ€νŠΈ - μ½”λ“œ μ£ΌκΈ°

  1. ν„°λ―Έλ„μ—μ„œ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄μ„œ μ–΄λ–»κ²Œ μ‹€νŒ¨ν•˜λŠ”μ§€ ν™•μΈν•œλ‹€.
  2. νŽΈμ§‘κΈ°μƒμ—μ„œ ν˜„μž¬ μ‹€νŒ¨ ν…ŒμŠ€νŠΈλ₯Ό μˆ˜μ •ν•˜κΈ° μœ„ν•œ μ΅œμ†Œν•œμ˜ μ½”λ“œλ₯Ό λ³€κ²½ν•œλ‹€.

그리고 이것을 λ°˜λ³΅ν•œλ‹€.

μ–Όλ§ˆλ‚˜ 빨리 이 μ£ΌκΈ°λ₯Ό λ”°λΌκ°ˆ 수 μžˆλŠ”μ§€ ν™•μΈν•΄λ³΄μž.

  • μ΅œμ†Œν•œμ˜ μ½”λ“œ λ³€κ²½
def home_page(request):
    pass
  • ν…ŒμŠ€νŠΈ
AttributeError: 'NoneType' object has no attribute 'content'
  • μ½”λ“œ : κ°€μ •ν•œ λŒ€λ‘œ django.http.httpResponseλ₯Ό μ‚¬μš©ν•œλ‹€.
from django.http import HttpResponse


def home_page(request):
    return HttpResponse
  • λ‹€μ‹œ ν…ŒμŠ€νŠΈ
AttributeError: 'property' object has no attribute 'startswith'
  • λ‹€μ‹œ μ½”λ“œ
def home_page(request):
    return HttpResponse('<html><title>To-Do lists</title>')
  • ν…ŒμŠ€νŠΈ
AssertionError: False is not true
  • μ½”λ“œ
def home_page(request):
    return HttpResponse('<html><title>To-Do lists</title></html>')
  • ν…ŒμŠ€νŠΈ 성곡!
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
Destroying test database for alias 'default'...

References


Written by@ugaemi
Record things I want to remember

🐱 GitHubπŸ“š Reading Space