TDD (Test Driven Development)

210729, 210730

TDD๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ

๊ธฐ์กด ๊ฐœ๋ฐœ ๋ฐฉ์‹

  1. ๊ตฌํ˜„

  2. ์›น ๋ธŒ๋ผ์šฐ์ €๋กœ ์ง์ ‘ ํ™•์ธ

  3. ์„ฑ๊ณต

  4. ๊ฐœ์„ ์  ์ฐพ๊ธฐ

TDD

  1. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

  2. ๊ธฐ๋Šฅ ๊ตฌํ˜„

  3. ๋ฆฌํŒฉํ† ๋ง

TDD๋ฅผ ํ•˜๋Š” ์ด์œ 

  • ํ”„๋กœ๊ทธ๋žจ์ด ๋ณต์žกํ•ด ์งˆ์ˆ˜๋ก, ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค ๊ธฐ๋Šฅ ์‚ฌ์ด์— ์—ฐ๊ด‘์„ฑ์ด ์ปค์ง„๋‹ค

  • ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋  ๋•Œ ๋งˆ๋‹ค ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธ์ด ์–ด๋ ค์›Œ์ง„๋‹ค

  • ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๋ชจ๋ฅด๊ณ  ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค๊ฐ€, ์ถ”ํ›„์— ์ˆ˜์ •ํ•  ๋•Œ ๊ฑด๋“œ๋ฆฌ๊ธฐ ์–ด๋ ค์›€

๋ธ”๋กœ๊ทธ ๋ชฉ๋ก ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋งŒ๋“ค๊ธฐ

from django.test import TestCase

# Create your tests here.
class TestView(TestCase):
    def test_post_list(self):
        self.assertEqual(2, 2)

        # 1.1 ํฌ์ŠคํŠธ ๋ชฉ๋ก ํŽ˜์ด์ง€(post list)๋ฅผ ์—ฐ๋‹ค
        # 1.2 ์ •์ƒ์ ์œผ๋กœ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋œ๋‹ค.
        # 1.3 ํŽ˜์ด์ง€์˜ ํƒ€์ดํ‹€์— Blog๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์žˆ๋‹ค.
        # 1.4 NavBar๊ฐ€ ์žˆ๋‹ค
        # 1.5 Blog, About me๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ Nav์— ์žˆ๋‹ค.

        # 2.1 ๊ฒŒ์‹œ๋ฌผ์ด ํ•˜๋‚˜๋„ ์—†์„ ๋•Œ
        # 2.2 ๋ฉ”์ธ ์˜์—ญ์— "์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค" ๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ๋‚˜์˜จ๋‹ค.

        # 3.1 ๋งŒ์•ฝ ๊ฒŒ์‹œ๋ฌผ์ด 2๊ฐœ ์žˆ๋”ฐ๋ฉด,
        # 3.2 ํฌ์ŠคํŠธ ๋ชฉ๋ก ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ ๊ณ ์นจํ–ˆ์„ ๋•Œ,
        # 3.3 ๋ฉ”์ธ ์˜์—ญ์— ํฌ์ŠคํŠธ 2๊ฐœ์˜ ํƒ€์ดํ‹€์ด ์กด์žฌํ•œ๋‹ค
        # 3.4 "์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค" ๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์—†์–ด์•ผ ํ•œ๋‹ค๋‹ค
  • ์‹œ์ž‘์€ ์ด๋ ‡๊ฒŒ ๋ง๋กœ ๋‹ค ์จ๋†“๋Š”๋‹ค.

  • ํ˜ผ์ž์„œ ๊ฐœ๋ฐœํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฟ์ด ๊ฐœ๋ฐœํ•  ๋•Œ ์ด๋Ÿฌํ•œ ๊ณผ์ •์ด ๋„์›€์ด ๋œ๋‹ค.

test.py

from django.test import TestCase, Client
from bs4 import BeautifulSoup
from .models import Post


class TestView(TestCase):
    def setUp(self):
        self.client = Client()

    def test_post_list(self):
        pass
        # 1.1 ํฌ์ŠคํŠธ ๋ชฉ๋ก ํŽ˜์ด์ง€(post list)๋ฅผ ์—ฐ๋‹ค
        response = self.client.get('/blog/')
        # 1.2 ์ •์ƒ์ ์œผ๋กœ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋œ๋‹ค.
        self.assertEqual(response.status_code, 200)
        # 1.3 ํŽ˜์ด์ง€์˜ ํƒ€์ดํ‹€์— Blog๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์žˆ๋‹ค.
        soup = BeautifulSoup(response.content, 'html.parser')
        self.assertIn('Blog', soup.title.text)
        # # 1.4 NavBar๊ฐ€ ์žˆ๋‹ค
        navbar = soup.nav
        # # 1.5 Blog, About me๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ Nav์— ์žˆ๋‹ค.
        self.assertIn('Blog', navbar.text)
        self.assertIn('About me', navbar.text)

        # 2.1 ๊ฒŒ์‹œ๋ฌผ์ด ํ•˜๋‚˜๋„ ์—†์„ ๋•Œ
        self.assertEqual(Post.objects.count(), 0)
        # 2.2 ๋ฉ”์ธ ์˜์—ญ์— "์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค" ๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ๋‚˜์˜จ๋‹ค.
        main_area = soup.find('div', id='main-area')
        self.assertIn('์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค.', main_area.text)

        # 3.1 ๋งŒ์•ฝ ๊ฒŒ์‹œ๋ฌผ์ด 2๊ฐœ ์žˆ๋‹ค๋ฉด,
        post_001 = Post.objects.create(
            title='์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ ์ž…๋‹ˆ๋‹ค.',
            content='Hello, World. We are the World.',
        )

        post_002 = Post.objects.create(
            title='๋‘๋ฒˆ์งธ ํฌ์ŠคํŠธ ์ž…๋‹ˆ๋‹ค.',
            content='์•ˆ๋…• ์—ฌ๋Ÿฌ๋ถ„, ๋‚˜๋„ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ผ๋ถ€์•ผ.',
        )
        self.assertEqual(Post.objects.count(), 2)

        # 3.2 ํฌ์ŠคํŠธ ๋ชฉ๋ก ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ ๊ณ ์นจํ–ˆ์„ ๋•Œ,
        response = self.client.get('/blog/')
        soup = BeautifulSoup(response.content, 'html.parser')
        # 3.3 ๋ฉ”์ธ ์˜์—ญ์— ํฌ์ŠคํŠธ 2๊ฐœ์˜ ํƒ€์ดํ‹€์ด ์กด์žฌํ•œ๋‹ค
        main_area = soup.find('div', id='main-area')
        self.assertIn(post_001.title, main_area.text)
        self.assertIn(post_002.title, main_area.text)
        # 3.4 "์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค" ๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์—†์–ด์•ผ ํ•œ๋‹ค
        self.assertNotIn('์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค.', main_area.text)
  • assertEqual(A, B)

    • A์™€ B๊ฐ€ ๊ฐ™์•„์•ผ ํ•œ๋‹ค. ๊ฐ™์ง€ ์•Š์œผ๋ฉด python manage.py test ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ๋‹ค.

    • ์œ„์—์„œ๋Š” soup.title.text์™€ 'Blog'๊ฐ€ ๊ฐ™์•„์•ผ ํ•œ๋‹ค๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ํ•˜๋‚˜์”ฉ ํ…Œ์ŠคํŠธ ํ•˜๊ณ  ์žˆ๋‹ค

  • assertIn[assertNotIn](A, B)

    • A๊ฐ€ B์•ˆ์— ํฌํ•จ๋˜[์ง€ ์•Š์•„]์–ด์•ผ ํ•œ๋‹ค๋ผ๋Š” ์ฝ”๋“œ

post_list.html

            <div class="col-md-8 col-lg-9" id="main-area">
                {% if post_list.exists %}
                    {% for p in post_list %}
                    <!-- Blog post-->
                    <div class="card mb-4">
                        {% if p.head_image %}
                            <img class="card-img-top" src="{{ p.head_image.url }}" alt="{{ p.title }}" />
                        {% else %}
                            <img class="card-img-top" src="https://picsum.photos/seed/{{ p.id }}/700/400" alt="{{ p.title }}">
                        {% endif %}
                        <div class="card-body">
                            <div class="small text-muted">January 1, 2021</div>
                            <h2 class="card-title h4">{{ p.title }}</h2>
                            {% if p.hook_text %}
                                <h5 class="text-muted"> {{ p.hook_text }} </h5>
                            {% endif %}
                            <p class="card-text">{{ p.content | truncatewords:50 }}</p>
                            <a class="btn btn-primary" href="{{ p.get_absolute_url }}">Read more โ†’</a>
                        </div>
                        <div class="card-footer text-muted">
                            Posted on {{ p.created_at }} Update at {{ p.updated_at }} by
                            <a href="#">์ž‘์„ฑ์ž๋ช… ์“ธ ์œ„์น˜</a>
                        </div>
                    </div>
                    {% endfor %}
                {% else %}
                    <h1>์•„์ง ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค</h1>
                {% endif $}
  • div์— main-area ๋ผ๋Š” id๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค

  • post_list.exists ๋กœ post_list๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€์— ๋Œ€ํ•œ if๋ฌธ์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค

๋ธ”๋กœ๊ทธ ์ƒ์„ธ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋งŒ๋“ค๊ธฐ

tests.py

 def test_post_detail(self):
        # 1.1 ํฌ์ŠคํŠธ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋‹ค
        # 1.2 ๊ทธ ํฌ์ŠคํŠธ์˜ url์€ `/blog/1/` ์ด๋‹ค
        # 2. ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ƒ์„ธ ํŽ˜์ด์ง€ ํ…Œ์ŠคํŠธ
        # 2.1 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ url๋กœ ์ ‘๊ทผํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ response๊ฐ€ ์˜จ๋‹ค(status code : 200)
        # 2.2 ํฌ์ŠคํŠธ ๋ชฉ๋ก ํŽ˜์ด์ง€์™€ ๋˜‘๊ฐ™์€ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”๊ฐ€ ์žˆ๋‹ค.
        # 2.3 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ œ๋ชฉ์ด ์›น ๋ธŒ๋ผ์šฐ์ € ํƒญ ํƒ€์ดํ‹€์— ๋“ค์–ด์žˆ๋‹ค.
        # 2.4 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ œ๋ชฉ์ด ํฌ์ŠคํŠธ ์˜์—ญ์— ์žˆ๋‹ค.
        # 2.5 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ž‘์„ฑ์ž(author)๊ฐ€ ํฌ์ŠคํŠธ ์˜์—ญ์— ์žˆ๋‹ค(์•„์ง ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†์Œ)
        # 2.6 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ๋‚ด์šฉ(content)์ด ํฌ์ŠคํŠธ ์˜์—ญ์— ์žˆ๋‹ค.
def test_post_detail(self):
    # 1.1 ํฌ์ŠคํŠธ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋‹ค
    post_001 = Post.objects.create(
        title='์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ ์ž…๋‹ˆ๋‹ค.',
        content='Hello, World. We are the World.',
    )
    self.assertEqual(Post.objects.count(), 1)
    # 1.2 ๊ทธ ํฌ์ŠคํŠธ์˜ url์€ `/blog/1/` ์ด๋‹ค
    self.assertEqual(post_001.get_absolute_url(), '/blog/1')
    # 2. ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ƒ์„ธ ํŽ˜์ด์ง€ ํ…Œ์ŠคํŠธ
    # 2.1 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ url๋กœ ์ ‘๊ทผํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ response๊ฐ€ ์˜จ๋‹ค(status code : 200)
    response = self.client.get(post_001.get_absolute_url())
    self.assertEqual(response.status_code, 200)
    soup = BeautifulSoup(response.content, 'html.parser')
    # 2.2 ํฌ์ŠคํŠธ ๋ชฉ๋ก ํŽ˜์ด์ง€์™€ ๋˜‘๊ฐ™์€ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”๊ฐ€ ์žˆ๋‹ค.
    navbar = soup.nav
    self.assertIn('Blog', navbar.text)
    self.assertIn('About me', navbar.text)
    # 2.3 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ œ๋ชฉ์ด ์›น ๋ธŒ๋ผ์šฐ์ € ํƒญ ํƒ€์ดํ‹€์— ๋“ค์–ด์žˆ๋‹ค.
    self.assertIn(post_001.title, soup.title)
    # 2.4 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ œ๋ชฉ์ด ํฌ์ŠคํŠธ ์˜์—ญ์— ์žˆ๋‹ค.
    main_area = soup.find('div', id='main-area')
    post_area = main_area.find('div', id='post-area')
    self.assertIn(post_001.title, post_area.text)
    # 2.5 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ์ž‘์„ฑ์ž(author)๊ฐ€ ํฌ์ŠคํŠธ ์˜์—ญ์— ์žˆ๋‹ค(์•„์ง ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†์Œ)
    # 2.6 ์ฒซ๋ฒˆ์งธ ํฌ์ŠคํŠธ์˜ ๋‚ด์šฉ(content)์ด ํฌ์ŠคํŠธ ์˜์—ญ์— ์žˆ๋‹ค.
    self.assertIn(post_001.content, post_area.text)

Last updated

Was this helpful?