pixabay
간단한 기능이면 대충 테스트해 보고 넘어가겠지만 경우의 수가 많은 케이스는 테스트를 거치고 다음으로 넘어가야 할 것 같다. QA가 자기가 만든 기능을 테스트 안 하는 것도 무책임한 거 같고...
다행히 python에서 unittest라는 라이브러리를 제공하고, django도 이를 기반으로 test 관련 라이브러리를 제공한다. app 디렉토리에 친절하게 tests.py
파일도 자동으로 생성해 주는 클라스. (물론 빈 파일).
여기에 테스트 코드를 넣어보자.
케이스 조합
가장 손이 많이 가는 건 테스트 케이스 조합이다.
tags, titles, texts 세 가지가 모두 list이고, 입력 텍스트 제한이 별로 없다. 태그에 영어 소문자만 입력 가능한 것만 제외하면 말이다.
대충 봐도 테스트 케이스 조합이 50개는 나올 것 같다. 검색 기능 하나에 50개 케이스는 좀 아닌 것 같아 아래와 같이 최소한의 값으로 추려본다.
Name | value1 | value2 | value3 | value4 |
---|---|---|---|---|
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의 기본 사용법만 익히고 넘어가자.
- 우선
tests.py
에 SearchTest 클래스를 만들고 TestCase를 상속받는다. - 테스트 케이스 함수명은 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/