1. 테스트코드란 무엇인가?
- 테스트 코드는 소프트웨어의 기능과 동작틀 테스트하는 데 사용되는 코드
- 소프트웨어의 결함을 찾아내고 수정하는 과정에서 매우 중요
- V모델의 따라 테스트 단위테스트, 통합테스트, 시스템 테스트, 인수 테스트가 있다.
단위 테스트(Unit Testing): 소프트웨어의 가장 작은 실행 단위인 '단위'를 검증하는 테스트
통합테스트(Integration Testing): 복수의 단위가 서로 올바르게 작동하는지 확인하는 테스트
시스템 테스트(System Testing): 소프트웨어 시스템이 명세서에 기술된 요구사항을 충족하는지 확인하는 종합적인 테스트
인수 테스트(Acceptance Testing): 실제 사용 환경에서 소프트웨어가 사용자의 요구사항을 만족하는지 확인하는 테스트
- 테스트 코드의 작성은 인수테스트와 통합테스트에서 주로 이루어진다.
2. 테스트를 왜 해야 하는가?
- 내가 무엇을 만들고 있는지 정확히 인지
테스트 코드 작성을 통해 요구사항의 기능적인 항목들을 저리하고 코너 케이스를 찾게 되며, 이는 문서의 역할을 수행
- 리팩토링을 진행할 때 부담 줄여주기
테스트 코드가 있다면 코드 수정 후에도 기능이 정상적으로 작동하는지 검증할 수 있다.
- 결합도와 의존성이 낮은 코드를 지향
테스트 코드 작성을 통해 의존성이 높은 부분을 개선하면 프로젝트의 코드 품질이 향상됨.
3. 테스트의 장단점
1) 장점
- 코드 품질 향상, 회귀 테스트, 문서화, 리팩토링 지원
2) 단점
- 개발 시간 증가, 불완전한 테스트, 오버 엔지니어링, 유지 보수 비용, 학습 곡선
단점을 생각하면 결국 개발의 비용문제다.
테스트를 잘 하는 사람이 된다면 단점을 상쇄하고 코드의 품질은 개선되며 장기적인 관점에서 서비스를 오래 지속 되게 하는 데 도움이 될 수 있다.
추가적으로 완벽한 테스트는 없다. 늘 변수는 있고 생각지도 못한 부분이 나온다. 오히려 좋다. 결국 개선점을 빨리 찾는 것이다.
3. Django에서의 단위테스트(Unit Tests)와
Django에서 테스트는 일반적으로 애플리케이션의 tests.py 파일에서 진행된다.
테스트 명령어:python manage.py test appname
- 여기서 다룰 단위 테스트는 모델 테스트, 뷰 테스트, 폼 테스트, Api 호출 테스트이다.
- Django에서는 기본적으로 test를 위해 django.test라이브러리를 지원한다.
1) 모델 테스트 예시
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
- 테스트 명령어를 입력하면
- polls 앱에서 테스트를 찾음
- django.test.TestCase클래스의 서브 클래스를 찾음
- 테스트 목적으로 특별한 테스트 데이터 생성
- 이름이 test로 시작하는 것들을 찾음(test의 이름은 "test"로 시작해야 함)
- .was_published_recentl()를 통해 테스트용 인스턴스 생성
- assertIs() 메서드를 사용하여 .was_published_recently()가 Flase가 나오길 바란다고 함.
- 테스트 결과가 True라면 어떤 테스트가 실패했는지와 실패가 발생한 행을 알려줌
2) 뷰 테스트 예시
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])
def test_past_question(self):
question = create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse("polls:index"))
self.assertQuerySetEqual(
response.context["latest_question_list"],
[question],
)
...
- 질문 생성 함수인 create_question은 테스트 과정 중 설문을 생성하는 부분에서 반복 사용
- self.client는 Django의 테스트 클라이언트 객체
- assertEqual: 두 값을 비교하여, 같은 경우에만 테스트를 통과하도록함
- assertContains: 응답 본문에 특정 텍스트가 포함되어 있는지 확인
- assertQuerySetEqual:: 주어진 리스트와 동일한지 비교
3) 폼테스트
from django import forms
from .forms import MyForm
class MyFormTest(TestCase):
def test_valid_form(self):
form_data = {'field1': 'value1', 'field2': 'value2'}
form = MyForm(data=form_data)
self.assertTrue(form.is_valid())
def test_invalid_form(self):
form_data = {'field1': '', 'field2': 'value2'}
form = MyForm(data=form_data)
self.assertFalse(form.is_valid())
4) mock 객체를 활요한 외부 api 호출 테스트
from django.test import TestCase
from unittest.mock import patch
from .views import my_view
class MyViewTest(TestCase):
@patch('myapp.views.get_external_data')
def test_my_view(self, mock_get_external_data):
# 목 객체에 반환 값을 설정
mock_get_external_data.return_value = {'key': 'value'}
# 뷰를 호출
response = self.client.get('/my-view-url/')
# 반환값 검증
self.assertEqual(response.status_code, 200)
self.assertIn('key', response.context)
self.assertEqual(response.context['key'], 'value')
4. 통합테스트를 위한 Selenium
1) Seleninm을 사용하는 이유
- 웹 브라우저에서의 실제 사용자 상호작용 모방
- 자동화된 브라우저 테스트
- 다양한 브라우저와 환경 지원
- 프론트엔드와 백엔드 통합
- 비동기적 동작의 테스트
- 디버깅 용이성
2) 통합 테스트 예시
from django.test import TestCase
from unittest.mock import patch
from myapp.utils import fetch_data_from_service
class FetchDataServiceTest(TestCase):
@patch('myapp.utils.requests.get')
def test_fetch_data(self, mock_get):
# Mock 객체에 대한 반환 값 설정
mock_get.return_value.json.return_value = {
'key': 'value'
}
# 함수 호출
result = fetch_data_from_service('https://example.com/data')
# 반환된 데이터 검증
self.assertEqual(result, {'key': 'value'})
# Mock 객체가 적절한 URL로 호출되었는지 확인
mock_get.assert_called_with('https://example.com/data')
5. 추가적으로 고려해볼만한 테스트 도구
- pytest와 pytest-django: Python에서 널리 사용되는 강력한 테스트 도구
- DRF: DRF(Django REST framework)는 테스트 도구가 따로 있다.
- Factory Boy와 Faker: 테스트 데이터를 생성하는데 유용함
- Mocking(unittest.mock 또는 response): 외부 API 호출이나 Django 외부의 다른 서비스에 대한 의존성을 가진경오 목(Mock)을 사용할 수 있습니다.
- Postman 또는 Insomniz: 자동화된 API 테스트 스위트를 구축하는 기능도 제공함
- HTTPPie: 커맨드 라인에서 사용할 수 있는 HTTP 클라이언트 툴로, API의 간단한 테스트와 디버깅에 유용
- celery를 사용할경우: celery는 자체 테스트 유틸리티를 제공
6. 시스템 테스트와 인수 테스트는 어떻게?
- 시스템 테스트는 전체적인 기능을 테스트하며, django.test.TestCase를 사용하여 시스템 전반에 걸친 테스트를 수행할 수 있다.
- 인수테스트에는 종종 Selenium을 사용하여 요구사항을 만족하는지 검증
7. 결론
Django에서 테스트 하기 위해 사용한 예시들은 정답이 아니다.
우선 요구사항을 빠르게 파악해 어떤 기능을 만들어야 하는지
그 기능을 수행하기 위해서 어떤 데이터들이 필요한지
그 기능을 구현하기 위해서 어떤 모듈이 필요한지
등등을 우선적으로 결정을하고
이에 맞춰 필요한 라이브러리, 기술들을 활용하여 테스트 코드를 작성해 나가는 것이 순서이다.
Referenece
- 소프트웨어의 테스트에 대해 담긴 글
https://yozm.wishket.com/magazine/detail/1964/ - django 테스트 관련 공식문서 페이지
https://docs.djangoproject.com/ko/4.2/intro/tutorial05/
'IT > Python' 카테고리의 다른 글
Django, 서드파티 앱에 대해서 (0) | 2024.04.08 |
---|---|
Django Rest Framework(DRF)를 이용한 API 뷰 구현 (0) | 2024.04.08 |
Django와 라즈베리파이 이용한 실시간 영상 스트리밍 서버 구축(MQTT 프로토콜) (0) | 2024.04.07 |
Django, MVT 패턴외에 다른 패턴이 사용 가능할까? (0) | 2024.04.07 |
Django, gunicorn + nginx를 사용하여 안정적으로 배포(Ubuntu) (1) | 2024.04.07 |