본문 바로가기

Django

[Django] 유닛테스트

다양한 취업공고들을 보니까, test 에 대해서 언급한 기업들이 많더라구요

 

그래서 test에 대하여, 그 중 Unit test에 대해 자세히 알아보도록 할게요!

 

시작하겠습니다 :)


 

▶ Manual Test

* 사람이 직접 실행하는 테스트

* 누구나 직관적으로 큰 계획 없이 테스트 실행가능

* 실행속도 ↓ & 인력소모 ↑ & 불안정성 ↑ & 비용 ↑

 

★ 그래서 test 자동화가 중요!!

테스트가 반복적으로 자주 실행될 수 있도록!

빠지는 부분이 없이 실행 될 수 있도록!

 

▶ 시스템 테스트 방법 3가지

 

 

1. UI(User Interface) test / End-To-End test

* 사용자가 실제로 시스템을 사용하는 방식과 가장 동일하게 테스트

* 프론트엔드와 백엔드까지 모든 시스템을 실행시키고 연결해야 할 수 있는 테스트

* 자동화가 가장 까다롭다

* 꼭 필요하지만 적게 사용하는 것이 좋은 test로 전체 test 중 대략 10% 정도 사용

 

2. Intergration test

* API를 로컬에서 실행시킨 후 HTTP 요청을 API 서버에 전송하여 올바른 HTTP 응답이 리턴되는지 확인

* 백엔드 API 시스템만을 실행한 뒤 지금 당장 구현한 특정 기능에 대해서 확인하는 방법

* UI test에 비해 시간이 짧고 간단한 편

* unit test 비해 자동화에 걸리는 공수가 더 크고 실행 속도가 오래 걸림

 * 전체 테스트의 20% 정도를 추천

 

3. Unit test

 

* 테스트를 할 수 있는 가장 작은 단위(unit)를 테스트하는 코드를 작성해서 테스트 하는 것

* 시스템을 테스트한다는 개념보다는 코드를 직접 테스트하는 개념, 즉 코드로 코드를 테스트

* 함수나 메소드를 호출한 뒤 결괏값을 확인하는 코드를 실행

* 실행하기 쉬우며 실행속도가 빠르다

* 함수 단위로 테스트를 하다 보니 전체적인 부분을 테스트하기에는 제한적

* 가장 비중을 높게 두면 좋은 test로 전체테스트의 70%로 구성

 

▶ UNIT TEST

1. 용어

* Testcase : unittest Framwork의 테스트 조직의 기본단위

* Fixture : 테스트를 진행할 때 필요한테스트용 데이터 혹은 설정.

* Assertion : unittest에서 테스트하는 부분이 제대로 됐는지 확인하는 부분

2. 설계 원칙

▶ UNIT TEST 작성해보기

 

1. 아래와 같이 로그인 views.py를 작성했다고 가정한다!

class SignupView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)
            REGEX_EMAIL = re.compile("^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
            REGEX_PASSWORD = re.compile(
                "^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$"
            )

            password = data["password"]

            if User.objects.filter(email=data["email"]).exists():
                return JsonResponse({"MESSAGE": "ALREADY_EXISTED_EMAIL"}, status=400)

            if not REGEX_EMAIL.match(data["email"]):
                return JsonResponse({"MESSAGE": "EMAIL_ERROR"}, status=400)

            if not REGEX_PASSWORD.match(data["password"]):
                return JsonResponse({"MESSAGE": "PASSWORD_ERROR"}, status=400)

            hashed_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
            decoded_password = hashed_password.decode("utf-8")

            User.objects.create(
                name=data["name"],
                email=data["email"],
                password=decoded_password,
            )
            return JsonResponse({"MESSAGE": "SUCCESS"}, status=201)
        except KeyError:
            return JsonResponse({"MESSAGE": "KEY_ERROR"}, status=400)

 

2. 그렇다면 이것에 해당하는 unit test 를 작성하면 아래와 같이 될 것이다!

 

V setUp은 데이터를 넣는 과정이고, tearDown은 넣은 데이터를 삭제하는 과정으로 맨 마지막에 실행된다!

→ 각 test 함수들은 독립적으로 실행 되어야 하기 때문에 데이터 삭제가 중요

 

V 성공 케이스와 실패 케이스를 전부 테스트 해야 하며, 실패 케이스 중 다양한 케이스를 모두 작성해야 한다!

 

★ test 파일의 이름은 test로 시작해야 하며, 함수 이름도 test로 시작해야 한다!

import json

from django.test import TestCase, Client

from .models import User

class SignUpTest(TestCase):
    def setUp(self):
        User.objects.create(
            email="bbb@wecode.com",
            name="파이썬",
            password="abcde12345@",
        )

    def tearDown(self):
        User.objects.all().delete()
        
    def test_signup_success(self):
        client = Client()
        user = {
            "email": "aaa@wecode.com",
            "name": "깔끔한",
            "password": "abcde12345@",
        }
        response = client.post(
            "/users/signup", json.dumps(user), content_type="application/json"
        )

        self.assertEqual(response.status_code, 201)
        self.assertEqual(response.json(), {"MESSAGE": "SUCCESS"})

    def test_duplication_user(self):
        client = Client()
        user = {
            "email": "bbb@wecode.com",
            "name": "탄탄한",
            "password": "abcde12345@@",
        }
        response = client.post(
            "/users/signup", json.dumps(user), content_type="application/json"
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json(), {"MESSAGE": "ALREADY_EXISTED_EMAIL"})

    def test_email_format_error(self):
        client = Client()
        user = {
            "email": "cccwecode.com",
            "name": "파이썬",
            "password": "abcde12345@",
        }
        response = client.post(
            "/users/signup", json.dumps(user), content_type="application/json"
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json(), {"MESSAGE": "EMAIL_ERROR"})
    
    def test_password_format_error(self):
        client = Client()
        user = {
            "email": "ddd@wecode.com",
            "name": "파이썬",
            "password": "abcde12345",
        }
        response = client.post(
            "/users/signup", json.dumps(user), content_type="application/json"
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json(), {"MESSAGE": "PASSWORD_ERROR"})
    
    def test_key_error(self):
        client = Client()
        user = {
            "email": "eee@wecode.com",
            "name": "파이썬",
        }
        response = client.post(
            "/users/signup", json.dumps(user), content_type="application/json"
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json(), {"MESSAGE": "KEY_ERROR"})

3.  마지막으로 아래의 명령어로 test파일을 실행시켜본다.

python manage.py test

 

그렇다면, 25개의 test 파일이 실행됐고, 모두 OK가 됐음을 알 수 있다!

 

▶ UNIT TEST의 중요성

 

위의 실제 views.py 와 tests.py를 확인해보면 알 수 있듯,

test 코드를 구현하는 것이 실제 코드를 구현하는 만큼, 혹은 그 이상의 공수가 든다.

 

그럼에도 불구하고 중요한 이유는,

대부분의 버그는 기본적인 unit test만 구현해도 잡을 수 있는 버그들이다. 

버그가 발견되는 시점을 당길 수 있는 것이다.

 

 

▣ 참조

위코드 "unit test" 세션

책 "깔끔한 파이썬 탄탄한 백엔드"

'Django' 카테고리의 다른 글

[Django] 댓글, 대댓글, 페이지네이션 구현하기  (0) 2021.11.06
[Django] 구글로 이메일 보내기  (0) 2021.10.21