테스트 주도 개발 적용하기

Posted on 2021-08-25 by GKSRUDTN99
Django로 웹사이트 만들기 장고

일종의 개발 방식 도는 개발 패턴을 의미함.

무언가를 개발할 때 바로 개발부터 하는 것이 아니라,
개발하려는 항목에 대한 점검 사항을 테스트 코드로 만들고 그 테스트를 통과 시키는 방식으로 개발하는 것.

테스트 주도 개발을 왜 사용하는가?

테스트 주도 개발이 아니라, 그때그때 만들고 싶은 요소를 떠올리고 구현을 먼저 하면,
프로그램이 복잡해 질수록 추가한 기능들 사이에 상호 연관성이 점점 늘어난다.
이렇게 늘어난 상호 연관성 때문에 웹 브라우저에서 확인하는 과정을 건너뛰게 되는 경우가 많아지는데,
그러다 문제가 발생했을 때 그 문제가 너무 많은 요소들과 얽혀있다면 손을 대기 힘들어진다.

여기서 주목할 점은, 프로그램이 복잡해지며 테스트를 제대로 진행하지 못했기 때문에 발생한 문제이다.
그렇다면, 개발을 한 단계 진행할 때마다 테스트를 진행한다면 위의 문제는 해결될 것이다.
하지만, 매번 테스트를 진행하는 것은 어렵고 복잡한 일이기 때문에,
개발한 코드가 테스트를 만족하는지 자동으로 확인하면서 개발을 진행할 수 있으면 어떨까?

그렇게 등장한 개념이 바로 테스트 주도 개발이다.

테스트 주도 개발 과정

1. 테스트 코드 작성

만들고 싶은 기능을 점검할 코드를 작성한다.
아직 기능을 구현하지 않았으므로 테스트 결과는 당연히 실패한다.

2. 기능 구현

테스트 코드를 만족시킬 수 있게 기능을 구현한다. 테스트 통과를 최우선으로 생각하고 작업한다.

3. 리팩토링

기능의 성능을 향상시키거나, 재사용성이 좋거나, 가독성이 좋은 코드로 개선한다.
테스트 코드로 다시 기능을 점검한다.

테스트 코드 사용 연습

1. tests.py 수정하기

TestCase 클래스를 상속받고 'Test'로 시작하는 이름을 가진 클래스를 하나 정의하고,
test로 시작하는 이름으로 함수를 정의한다.
위의 사항은 테스트 코드를 작성할 때의 기본 규칙이다.

# blog/tests.py

from django.test import TestCase

class TestView(TestCase):
    def test_post_list(self):
        self.assertEqual(2,3)

2. test 실행하기

terminal에서 python manage.py test를 실행하면,
테스트를 수행한다. 위의 코드는 2,3이 같은 지를 테스트하므로, 테스트에 실패할 것이다.

위의 방식대로 테스트코드를 작성하고, 테스트를 수행한다.

beautifulsoup4

웹 개발을 하는 과정에서는 개발자가 구현한 요소들이 웹 브라우저에 의도한 대로 잘 나타나는 지를 확인할 필요가 있다.
이를 위해 HTML로 나타나는 페이지의 요소를 쉽게 다룰 수 있는 도구가 필요한 데,
beatuifulsoup4가 그 역할을 할 수 있다.
pip install beatuifulsoup4를 통해 설치한다.

테스트 코드 작성방법

1. TestCase 내에서 기본적으로 설정되어야 하는 내용이 있으면 setUp()함수에서 정의를 하면 된다.

#blog/tests.py
from django.test import TestCase, Client

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

2. 테스트할 내용들을 주석으로 작성해본다.

from django.test import TestCase, Client

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

    def test_post_list(self):
        # 1.1 포스트 목록 페이지를 가져온다.
        # 1.2 정상적으로 페이지가 로드된다.
        # 1.3 페이지 타이틀은 'Blog'이다.
        # 1.4 네비게이션 바가 있다.
        # 1.5 Blog, About Me라는 문구가 내비게이션 바에 있다.

        # 2.1 메인 영역에 게시물이 하나도 없다면
        # 2.2 '아직 게시물이 없습니다'라는 문구가 보인다.

        # 3.1 게시물이 2개 있다면
        # 3.2 포스트 목록 페이지를 새로고침했을 때
        # 3.3 메인 영역에 포스트 2개의 타이틀이 존재한다.
        # 3.4 '아직 게시물이 없습니다'라는 문구는 더 이상 보이지 않는다.

3. 테스트 코드 작성하기

TestCase를 이용한 방식은 실제 데이터베이스는 건드리지 않고 가상의 데이터베이스를 새로 만들어 테스트한다.

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):
        # 1.1 포스트 목록 페이지를 가져온다.
        response = self.client.get('/blog/')
        # 1.2 정상적으로 페이지가 로드된다.
        self.assertEqual(response.status_code, 200)
        # 1.3 페이지 타이틀은 'Blog'이다.
        soup = BeautifulSoup(response.content, 'html.parser')
        self.assertEqual(soup.title.text, 'Blog')
        # 1.4 네비게이션 바가 있다.
        navbar = soup.nav
        # 1.5 Blog, About Me라는 문구가 내비게이션 바에 있다.
        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='1등이 전부는 아니잖아요!',
        )
        self.assertEqual(Post.objects.count(), 2)

        # 3.2 포스트 목록 페이지를 새로고침했을 때
        response = self.clinet.get('/blog/')
        soup = BeautifulSoup(response.content, 'html.parser')
        self.assertEqual(response.status_code, 200)
        # 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)
  1. 장고 테스트에서 client는 테스트를 위한 가상의 사용자이다.
    self.client.get('/blog/')는 사용자가 웹 브라우저에 127.0.0.1:8000/blog/를 입력했다고 가정하고 그때 열리는 웹 페이지를 response에 저장한다.

  2. self.assertEqual(a,b)는 a와 b의 값이 같을 때만 ok한다.

  3. self.client.get('/blog')를 통해 웹 페이지를 정상적으로 가져왔다면,
    response.status_code에는 200이 들어있다.

  4. 불러온 내용은 HTML로 되어 있는데, 이 HTML 요소들에 접근하기 위해 Beautifulsoup로 읽어들이고,
    html.parser명령어로 파싱한 결과를 soup에 담는다.
    soup.title.text와 같은 방식으로 title의 text에 접근할 수 있다.

    ✔︎ innerHTML을 리턴하는 것 같다.

  5. self.assertIn(a, b)는 a가 b의 내용 안에 포함되어 있을 때만 ok한다.

  6. Post.objects.count()로 Post 모델의 레코드가 몇개 있는지 확인할 수 있다.

    ✔︎ 테스트 케이스는 새 데이터 베이스를 만들어 실행하므로, 초기 count는 항상 0이다.

  7. soup.find('div', id='main-area')와 같은 방식으로 main-area라는 id를 가진 div 태그를 읽어올 수 있다.

  8. Post.objects.create(title='제목', content='내용')과 같은 형식으로 Post에 새로운 레코드를 생성할 수 있다.

  9. 새로고침은 self.client.get()을 한번 더 수행하고, status_code가 200임을 확인하면 된다.

  10. self.assertNotIn(a, b)는 a가 b의 내용에 포함되지 않았을 때만 ok한다.

테스트 코드는 언제 사용하면 좋을까?

테스트를 많이 하면 할수록 실수를 방지할 수 있고 견고한 프로그램을 만들 수 있다.
하지만, 테스트 코드를 어디까지 만들어야 하는지는 개발자의 판단이 필요하다.
모든 기능에 대해 테스트하면 좋겠지만, 이 또한 시간과 노력이 들어가는 일이므로,
개발을 하면서 '이건 나중에 문제가 될 수 있겠다'는 부분에 대해 테스트를 만드는 것이 좋다.