[Django/TDD - 테스트 주도 개발]

SooYeon Yeon·2022년 7월 2일
2

Django

목록 보기
16/20

장고 REST Framework 이용

  1. 테스트 코드 구축
  2. 테스트 코드에 맞게 기능 구현
  • 단위테스트 : 가장 작은 단위로 테스트(form, view, template 등으로 나눠져 있는 데 이걸 하나하나 따로 테스트 하는게 단위테스트)
  • 통합테스트 : (우리 플젝과 다른 DB나 캐시서버나 이런걸 통합해 테스트하는 것)

장고에서 테스트 하며 개발하는 방법

단위 테스트

  • 앱 아래에다가 tests 패키지 생성

views,model,settings,urls,html,form

사용자가 url로 장고에 요청

      url에 매핑되어 있는 함수가 실행

 함수는 연산 후 html 반환

TestCase 할 때는 해당 import 해주어야함

33from django.test import TestCase

Test_urls

tests 패키지에 test_urls.py

from django.test import TestCase
from django.urls import resolve
from board.views import create

class TestUrls(TestCase):
    def test_create_url_is_resolved(self):
        url = resolve('/board/create') # resolve함수는 urls.py에 있는 해당 url을 찾아서 해당하는 객체를 반환해옴
        self.assertEqual(url.func, create)

urls.py

path('board/create', board.views.create),

views.py

from django.shortcuts import render

# Create your views here.
def create(request):
    return render(request, 'board/create.html')

터미널에 해당 명령어로 test code 실행 (test가 들어간 것 실행해줌)

python .\manage.py test

특정 test case 실행하고싶으면

python .\manage.py test board.test.test_views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~s.TestViews.test_posts.~

test_views

from django.contrib.auth.models import User
from django.test import TestCase, Client

class TestUrls(TestCase):
    def setUp(self): # 필요한 객체, 데이터를 생성하는 메소드
        self.client = Client() # Client객체. 요청을 보낼 수 있게 해줌
        self.user = User.objects.create_user(username='user01', password='qwer1234!')

    def test_post_create_GET_with_login(self):
        self.client.login(username='user01', password='qwer1234!')
        response = self.client.get('/board/create')

        self.assertEqual(response.status_code, 200)  # 정상적으로 받아갔다면 사용자 상태코드는 200이 되어야 함
        self.assertTemplateUsed(response, 'board/create.html')

    def test_post_create_GET_without_login(self):
        # 로그인 페이지가 뜨는 지 확인
        response = self.client.get('/board/create')
        self.assertEqual(response.status_code, 302)  # redirect 시킬때 302가 뜸
        # self.assertTemplateUsed(response, 'accounts/login.html')

    def test_post_create_POST_without_login(self):
        # 로그인 페이지가 뜨는 지 확인
        response = self.client.post('/board/create')
        self.assertEqual(response.status_code, 302)  # redirect 시킬때 302가 뜸

    def test_post_create_POST_with_login(self):
        self.client.login(username='user01', password='qwer1234!')
        response = self.client.post('/board/create', data={'title': 'title3', 'contents': 'contents1'})

        post = Post.objects.get(title='title3')
        self.assertEqual(post.title, 'title3')
        self.assertEqual(response.status_code, 302)

Post Model 생성

from django.contrib.auth.models import User
from django.db import models

# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    contents = models.TextField()
    writer = models.ForeignKey(User, on_delete=models.CASCADE)

마이그레이션

POST에 대한 CRUD TEST

from django.contrib.auth.models import User

from board.models import Post
from django.test import TestCase

class TestModels(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username='user01', password='qwer1234!')
        self.post = Post.objects.create(title='title1',contents='contents1',writer=self.user)

    def test_post_model_create(self):
        post = Post()
        post.title = 'title2'
        post.contents = 'contents'
        post.writer = self.user
        post.save()

        post = Post.objects.get(title='title2')
        self.assertEqual(post.title, 'title2')

    def test_post_model_read(self):
        post = Post.objects.get(id=1)
        self.assertEqual(post.title, 'title1')
    def test_post_model_update(self):
        # 조회하고 바꾸고, 다시 조회했을 때 바뀐 내용으로 바꿔져있어야 함
        post = Post.objects.get(id=1)
        post.title = 'title3'
        post.save()

        post = Post.objects.get(id=1)
        self.assertEqual(post.title, 'title3')
    def test_post_model_delete(self):
        post = Post.objects.get(id=1)
        post.delete()
        self.assertFalse(Post.objects.filter(id=1).exists())

에러 메시지 띄우기

title 길이가 5 미만이면 에러 메시지 띄우기

test 코드

def test_post_create_POST_with_check_title_length(self):
        self.client.login(username='user01', password='qwer1234!')
        response = self.client.post('/board/create', data={'title': 'ti', 'contents': 'contents1'})
        #self.assertEqual(response.url, '/error')
        messages = list(response.context['messages'])
        self.assertEqual(len(messages), 1) # 현재 에러 메시지 한개 담겨있어야함
        self.assertEqual(str(messages[0]),'제목은 다섯글자 이상이어야 합니다.')  # 알맞은 에러 메시지 담겨있어야함
        self.assertEqual(response.status_code, 400) # 400으로 바꿔서 온 지 확인

views.py

from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect

# Create your views here.
from board.forms import PostForm
from board.models import Post
from django.contrib import messages

@login_required(login_url='accounts/login')
def create(request):
    if request.method == 'GET':
        postForm = PostForm()
        context = {'postForm':postForm}
        return render(request, 'board/create.html',context)
    elif request.method == 'POST':
        postForm = PostForm(request.POST)
        context = {
            'postForm' : postForm,
            'has_error' : False
        }

        post = Post()
        post.title = request.POST.get('title')
        if len(post.title)<5:
            messages.add_message(request, messages.ERROR,"제목은 다섯글자 이상이어야 합니다.") # 에러 메시지 담기, info나 원인같은 등급도 있음
            context['has_error']=True # 에러가 있으면 has_error을 False로 만듦
        post.contents = request.POST.get('contents')
        post.writer = request.user

        if context['has_error']:
            return render(request, 'board/create.html',context, status=400) # 에러처리(400, 클라이언트 잘못)를 해서 넘김

        post.save()
        return redirect('/board/read/'+str(post.id))

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    {% csrf_token %}
    {{ postForm }}
    <button>작성</button>

    {% if messages %}
        {% for message in messages%}
            {% if message.tags == 'error' %}
                {{ message }}
            {% endif %}
        {% endfor %}
    {% endif %}
</form>
</body>
</html>

작성자가 유저, 작성자가 다른유저, 작성자가 로그인X일때

class TestUrls(TestCase):
    def setUp(self):  # 필요한 객체, 데이터를 생성하는 메소드
        self.client = Client()  # Client객체. 요청을 보낼 수 있게 해줌
        self.user01 = User.objects.create_user(username='user01', password='qwer1234!')
        self.user02 = User.objects.create_user(username='user02', password='qwer1234!')

        self.post = Post.objects.create(title='title1', contents='contents1',writer=self.user01)

    def test_post_read_GET_with_writer(self):
        self.client.login(username='user01', password='qwer1234!')
        response = self.client.get('/board/read/1')
        self.assertTemplateUsed(response, 'board/read.html')
        self.assertInHTML('<button>수정</button>', response.content.decode()) # 해당글자가 HTML에 있는지

    def test_post_read_GET_without_writer(self):
        self.client.login(username='user02', password='qwer1234!')
        response = self.client.get('/board/read/1')
        self.assertTemplateUsed(response, 'board/read.html')
        self.assertNotIn('<button>수정</button>',response.content.decode())

    def test_post_read_GET_with_other_writer(self):
        response = self.client.get('/board/read/1')
        self.assertTemplateUsed(response, 'board/read.html')
        self.assertNotIn('<button>수정</button>', response.content.decode())

나머지 views나 templates은 writer면 수정삭제 뜨게 하는 코드로

유닛 테스트

셀레니움 - 가벼운 경량 프로그램사용 보통 ChromeDriver 사용

ChromeDriver 설치 후 C드라이브에 풀기

pip install selenium

웹페이지 띄우고 간단 조작

import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome('c:/chromedriver.exe')
driver.get('https://nid.naver.com/nidlogin.login')
id_input_tag = driver.find_element(By.ID, 'id')
id_input_tag.send_keys('qwer1234')
id_input_tag = driver.find_element(By.ID, 'pw')
id_input_tag.send_keys('qqqqq')
time.sleep(4)
login_btn_tag = driver.find_element(By.CLASS_NAME, 'btn_login ')
login_btn_tag.click()

# pw_input_tag.send_keys(Keys.RETURN) # 엔터 입력

#driver.quit() # x 해도 백그라운드에 남아있을 수 있으니 해당 명령어로 종료해주어야 함

특정 태그 찾을 때

  1. id 속성으로 찾기 (id 값은 하나만 있게 되어있음)
  2. id 속성이 없을 때도 있음. 그럴 때는 클래스나 태그로 찾는 방법 있음
  3. 개발자도구 코드 오른쪽 클릭 copy full XPath로 찾을 수도 있음

네이버 - 엔터의 글자 가져오기

import time

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome('c:/chromedriver.exe')
driver.get('https://www.naver.com/')
enter_btn = driver.find_element(By.XPATH, '/html/body/div[2]/div[3]/div[2]/div[3]/div/div[1]/div[2]/div/div/ul/li[1]/a')
enter_btn.click()

text_tag = driver.find_element(By.XPATH,'/html/body/div[2]/div[3]/div[2]/div[3]/div/div[2]/div[1]/div[1]/div/a[2]/strong')
print(text_tag.text)

#driver.quit() # x 해도 백그라운드에 남아있을 수 있으니 해당 명령어로 종료해주어야 함

0개의 댓글