Django

TDD (Test-Driven Development)

UserDonghu 2023. 11. 16. 15:21

단위 테스트 (unit test) : 개별 함수나 메서드 같은 코드의 가장 작은 단위가 예상대로 동작하는지 검증하는 테스트.

- Python 에서 unittest 모듈을 이용해서 단위 테스트를 작성하고 실행할 수 있음

import unittest # 모듈 import

def add(x, y): # 더하는 함수
    return x + y

class TestAdd(unittest.TestCase): # 단위 테스트
    def test_add(self):
        self.assertEqual(add(1, 2), 3) # add(1,2)가 3과 같은지 확인

if __name__ == '__main__':
    unittest.main()
    
# 결과가 OK로 나옴

 

import unittest

def add(x, y):
    return x + y

class TestAdd(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(10, 2), 13)
        self.assertEqual(add(10, 20), 31)


if __name__ == '__main__':
    unittest.main()
    
# 결과가 Failed로 나옴. 에러가 여러개 있어도 하나만 출력



unittest의 다양한 메서드

import unittest


class TestAdd(unittest.TestCase):
    def test_add(self):
        print(dir(self)) # 이걸로 어떤 메서드가 있는지 확인
        self.assertEqual(1 + 2, 3) # 같은지 판별
        self.assertTrue(10 == 10) # True인지 판별
        self.assertFalse(1 == 10) # False인지 판별
        self.assertGreater(10, 1) # 앞에 것이 뒤에 것보다 큰지
        self.assertLess(1, 10) # 앞에 것이 뒤에 것보다 작은지
        self.assertIn(1, [1, 2, 3, 4, 5]) # 포함하고 있는지
        self.assertIsInstance('a', str) # 인스턴스인지

if __name__ == '__main__':
    unittest.main()

 

 

여러가지 함수를 만들어서 test 해보기

import unittest

# 구현한 함수
def add(x, y):
    return x + y

def sub(x, y):
    return x - y

def mul(x, y):
    return x * y


# 테스트 코드
class TestAdd(unittest.TestCase):
    def test_add(self):
        print('더하기 테스트')
        self.assertEqual(add(1, 2), 3)

    def test_sub(self):
        print('빼기 테스트')
        self.assertEqual(sub(3, 1), 2)

    def test_mul(self):
        print('곱하기 테스트')
        self.assertEqual(mul(3, 4), 12)

    def donghu(self):
        '''
        테스트 이름을 마음대로 정할 수 있는지 체크
        'test_'로 시작하지 않으면 테스트로 인식하지 않음
        '''
        self.assertEqual(5, 4)

    def test_donghu(self):
        '''
        테스트 이름을 마음대로 정할 수 있는지 체크
        '''
        self.assertEqual(5, 5)

if __name__ == '__main__':
    unittest.main()

TDD : 코드를 작성하기 전 테스트를 먼저 작성하고 그 테스트를 통과하도록 코드를 구현하는 개발 방법론

- 코드의 품질 향상시키고 버그를 줄이고, 빠르게 배포 가능

- 개발자는 요구사항 명세에 대해 보다 잘 이해할 수 있으며, SW가 원하는대로 동작하는지 확인 가능

 

Django TDD

각 APP의 tests.py 파일에 테스트 코드를 작성하고, python manage.py test 명령어로 테스트

 

blog / tests.py

from django.test import TestCase

class Test(TestCase):
    def test_something(self):
        self.assertEqual(True, True)
        
# python manage.py test로 실행하면 OK 출력

 

blog / tests.py

from django.test import TestCase

class Test(TestCase):
    def test_something_one(self):
        self.assertEqual(True, False)

    def test_something_two(self):
        self.assertEqual(True, True)
        
# python manage.py test blog.tests.Test.test_someting_one 으로 지정해서 테스트 가능
# Failed 출력

 

 

기본 세팅

blog / tests.py

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

class Test(TestCase):
    def setUp(self):
        # 가상의 client를 만들어서 사용. 이때 DB는 비어있는 채로 시작
        self.client = Client()
        # setUp()에서는 주로 테스트를 진행하기 전 아래와 같이 데이터를 생성
        # self.post_001 = Post.objects.create(
        #     title='첫 번째 포스트입니다.',
        #     content='Hello World. We are the world.',
        # )
    
    def test_name(self):
        pass

 

 

테스트 코드 작성 예시

blog / tests.py

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

class Test(TestCase):
    def setUp(self):
        '''
        테스트를 할 때에는 DB가 초기화 되어 있다고 가정하고,
        client도 새로 생성하여 테스트를 진행.
        '''
        print('-- blog app 테스트 시작 --')
        self.client = Client() # Client 새로 생성
        self.user_donghu = User.objects.create_user( # 유저 생성
            username='donghu',
            password='nopassword'
        )

    def test_post_list(self):
    
        post_001 = Post.objects.create(
            title = '첫 번째 포스트입니다.',
            content = 'Hello World. We are the world.',
            author = self.user_donghu,
        )
        post_002 = Post.objects.create(
            title = '두 번째 포스트입니다.',
            content = 'Hello World. We are the world.',
            author = self.user_donghu,
        )


        print('-- 1차 테스트 시작 --')
        # 테스트 목적 또는 시너리오, 기타 설명등을 주석으로 작성
        
        print('-- 접속 확인 --')
        # 1. 접속
        # 1.1 포스트 목록 페이지를 가져옴
        response = self.client.get('/blog/') # '/blog/'의 response를 가져옴
        self.assertEqual(response.status_code, 200) # response가 정상연결(200)인지 확인

        # 1.2 정상 접속이 되면 페이지 타이틀에 'Blog'라는 문구 출력
        # self.assertTemplateUsed(response, 'blog/post_list.html') # response의 template 파일 확인 가능
        soup = BeautifulSoup(response.content, 'html.parser') # html 문서 파싱
        self.assertEqual(soup.title.text, 'Blog') # title의 text가 'Blog'인지 확인
        
        print('-- 상속 확인 --')
        # 2. 상속
        # 2.1 페이지 상속이 제대로 되었다면 nav태그가 있고, footer태그가 있어야 함
        # 2.2 nav태그 내부에는 Home, About, Blog라는 문구(메뉴)가 있어야 함
        navbar = soup.nav
        self.assertIn('Home', navbar.text) # navbar에 'Home'이 있는지 확인
        self.assertIn('About', navbar.text)
        self.assertIn('Blog', navbar.text)

        # footer = soup.footer
        # self.assertIn('Instagram', footer.text)
        # self.assertIn('Facebook', footer.text)
        # self.assertIn('Twitter', footer.text)
        
        print('-- 포스트 목록 확인 --')
        # 3. 포스트 목록
        # 3.1 포스트 목록이 하나도 없다면 '아직 게시물이 없습니다.'라는 문구가 나와야 함
        # 3.2 포스트가 2개 있다면, 포스트의 개수만큼 h2태그가 있어야 함
        if Post.objects.count() == 0: # Post의 개수가 0개면
            print('게시물이 없는 경우')
            self.assertIn('아직 게시물이 없습니다.', soup.body.text)
        else:
            print('게시물이 있는 경우')
            print(len(soup.body.select('h2'))) # h2의 개수
            self.assertGreater(len(soup.body.select('h2')), 1) # h2의 개수가 1보다 큰지 확인

        print('-- 포스트 내용 확인 --')
        # 4. 포스트 내용
        # 4.1 포스트가 1개 있다면 해당 포스트의 제목(title)이 포스트 영역에 있어야 함
        # 4.2 포스트가 1개 있다면 해당 포스트의 작성자(author)가 포스트 영역에 있어야 함
        # 4.3 포스트가 1개 있다면 해당 포스트의 내용(content)이 포스트 영역에 있어야 함
        # main_area = soup.find('div', id='main') # id가 'main'인 div를 선택
        # self.assertIn(self.post_001.title, main_area.text) # post_001의 title이 main_area의 text에 있는지 확인

    def test_post_detail(self):
        post_001 = Post.objects.create(
            title = '첫 번째 포스트입니다.',
            content = 'Hello World. We are the world.',
            author = self.user_donghu,
        )
        # 1. 접속
        # 1.1 포스트 목록 페이지를 가져옴
        response = self.client.get('/blog/1/')
        self.assertEqual(response.status_code, 200)

        # 1.2 정상 접속이 되면 페이지 타이틀에 'Document'라는 문구출력
        # self.assertTemplateUsed(response, 'blog/post_list.html')
        soup = BeautifulSoup(response.content, 'html.parser')
        self.assertEqual(soup.title.text, 'Document')
        
        print('-- 상속 확인 --')
        # 2. 상속
        # 2.1 페이지 상속이 제대로 되었다면 nav태그가 있고, footer태그가 있어야 함
        # 2.2 nav태그 내부에는 Home, About, Blog라는 문구(메뉴)가 있어야 함
        navbar = soup.nav
        self.assertIn('Home', navbar.text)
        self.assertIn('About', navbar.text)
        self.assertIn('Blog', navbar.text)

        # 3. 포스트 내용
        # 3.1 포스트가 1개 있다면 해당 포스트의 제목(title)이 포스트 영역에 있어야 함
        self.assertIn(post_001.title, soup.body.text)
        # 3.2 포스트가 1개 있다면 해당 포스트의 내용(content)이 포스트 영역에 있어야 함
        self.assertIn(post_001.content, soup.body.text)
        # 3.3 포스트가 1개 있다면 해당 포스트의 작성자(author)가 포스트 영역에 있어야 함
        self.assertIn(post_001.author.username, soup.body.text)