[Python #15] [Django #8] 구현된 기능, 테스트 해보자!

in mini.topia4 years ago

pixabay

간단한 기능이면 대충 테스트해 보고 넘어가겠지만 경우의 수가 많은 케이스는 테스트를 거치고 다음으로 넘어가야 할 것 같다. QA가 자기가 만든 기능을 테스트 안 하는 것도 무책임한 거 같고...

다행히 python에서 unittest라는 라이브러리를 제공하고, django도 이를 기반으로 test 관련 라이브러리를 제공한다. app 디렉토리에 친절하게 tests.py 파일도 자동으로 생성해 주는 클라스. (물론 빈 파일).
여기에 테스트 코드를 넣어보자.

케이스 조합

가장 손이 많이 가는 건 테스트 케이스 조합이다.
tags, titles, texts 세 가지가 모두 list이고, 입력 텍스트 제한이 별로 없다. 태그에 영어 소문자만 입력 가능한 것만 제외하면 말이다.
대충 봐도 테스트 케이스 조합이 50개는 나올 것 같다. 검색 기능 하나에 50개 케이스는 좀 아닌 것 같아 아래와 같이 최소한의 값으로 추려본다.

Namevalue1value2value3value4
TAGS결과 없는 값빈 리스트list 크기 1인 정상값빈 스트링
TITLES결과 없는 값빈 리스트list 크기 2인 정상값특수문자와 숫자
TEXTS결과 없는 값빈 리스트list 크기 3인 정상값빈 스트링

PICT의 IF 문으로 좀 더 필터링하면 적당한 조합이 나올 것 같다. 예전 글 [IT] Pairwise 테스트는 MS PICT 로 조합하자~ 👇

TAGS: no_results,,valid_1,empty
TITLES: no_results,,valid_2,special and num
TEXTS: no_results,,valid_3,empty

if [tags] = "no_results" then [titles] = "no_results" and [texts] = "no_results" else [titles] <> "no_results" and [texts] <> "no_results";
if [tags] = "empty" then [texts] <> "empty";
if [texts] = "empty" then [tags] <> "empty";

최적의 케이스가 12개 케이스가 추출된다. (정말 최적인지는 알 길이 없다 ㅎㅎ)👇

코드 작성

unittest의 기본 사용법만 익히고 넘어가자.

  1. 우선 tests.py에 SearchTest 클래스를 만들고 TestCase를 상속받는다.
  2. 테스트 케이스 함수명은 test로 시작한다. 이를 기준으로 unittest가 실행된다고 한다.
from django.test import TestCase
from .services import Search


class SearchTest(TestCase):

    def test_01_empty_tag(self):
        query = {
            'tags': [''],
            'titles': ['#2'],
            'texts': ['사랑', 'Pairwise', '区块链']
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_02_valid_title(self):
        query = {
            'tags': [],
            'titles': ['#2'],
            'texts': ['']
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_03_valid_all(self):
        query = {
            'tags': ['kr'],
            'titles': ['#2', '검색'],
            'texts': ['사랑', 'pairwise', '블록체인']
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_04_valid_body(self):
        query = {
            'tags': [],
            'titles': [],
            'texts': ['pixabay', '区块链', 'Pairwise']
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_05_valid_tag_and_title(self):
        query = {
            'tags': ['cn'],
            'titles': ['#2'],
            'texts': []
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_06_valid_all(self):
        query = {
            'tags': [''],
            'titles': [],
            'texts': []
        }
        s = Search(query)
        self.assertTrue(s.search_posts())

    def test_07_valid_title(self):
        query = {
            'tags': [],
            'titles': ['selenium', 'java #2'],
            'texts': []
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_08_no_results(self):
        query = {
            'tags': ['nononononono'],
            'titles': ['ttttt'],
            'texts': ['aaaaaa']
        }
        s = Search(query)
        self.assertFalse(s.search_posts())

    def test_09_valid_tags_and_titles(self):
        query = {
            'tags': ['kr'],
            'titles': ['#2', '검색'],
            'texts': ['']
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_10_valid_tags_and_titles(self):
        query = {
            'tags': [''],
            'titles': ['#2', '검색'],
            'texts': []
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_11_valid_tags(self):
        query = {
            'tags': ['kr'],
            'titles': [],
            'texts': ['']
        }
        s = Search(query)
        self.assert_text_contains(query, s.search_posts())

    def test_12_empty_list_all(self):
        query = {
            'tags': [],
            'titles': [],
            'texts': []
        }
        s = Search(query)
        self.assertTrue(s.search_posts())

    def assert_text_contains(self, query: dict, blogs: list):
        self.assertTrue(blogs)
        all_q = query['tags'] + query['titles'] + query['texts']
        for blog in blogs:
            comm = blog['comment']
            cont = comm['json_metadata'] + comm['body'] + comm['title']
            any_one = any(query.lower() in cont.lower() for query in all_q)
            self.assertTrue(any_one)



테스트 실행

python workspace/my_app/manage.py test my_app으로 실행하면 테스트가 실행된다. 처음엔 이슈가 많이 나와 현타가 좀 왔지만.... 다행히 지금은 전부 OK가 떨어진다.

기능 수정이 있을 때마다 실행하는 용도로 사용하면 딱 좋을 것 같다.
좀 더 지켜보면서 케이스를 더 추가해야겠다. (또한 data-driven 테스트도 연구해 봐야겠다)


[Cookie 😅]
Python 3.7.4
Django 2.2.4
steem-python 1.0.1
goorm IDE 1.3

참고한 글:
https://docs.python.org/ko/3/library/unittest.html
https://docs.djangoproject.com/ko/3.1/topics/testing/
https://docs.djangoproject.com/ko/3.1/intro/tutorial05/