iPark Project
테스트 코드 작성
- 내가 프로젝트를 진행하면서 맡았던
user app
대다수와 park app
의 공원 검색, 공원 토글 리스트, 인기순 공원 나열에 대한 테스트 코드를 작성하였다.
- 이번 사용자 피드백 기간에는 기존에 작성했던 테스트 코드를 좀 더 보완하는 방향으로 해
최대한 많은 케이스를 아우를 수 있도록
작성해보았다.
- 테스트 코드를 작성하며 문제없이 돌아갔던 코드지만 잘못된 부분을 찾아 수정하며 테스트 코드가 왜 중요한지 알 수 있는 작업이었다고 생각한다.
user app
- user app에서 내가 작성한 테스트 코드는 아래와 같다.
회원가입
, 로그인
, 회원정보 수정 및 탈퇴
, 아이디 찾기
, 계정관리
, 비밀번호 변경
- 모든 테스트 코드를 작성해보니 1000줄이 넘어 중요한 부분만 몇 가지 짚도록 하겠다.
회원가입
- 회원가입을 하려면 사용자가 데이터를 입력해야 하며, 이중에서
ForeignKey
를 통해 가져온 region
을 setUpTestData
를 통해 만들어 테스트 클래스가 끝날 때까지 데이터를 유지시키는 방식을 택했다.
@classmethod
def setUpTestData(cls):
region_data = ["강남구", "강서구", "강북구", "은평구", "동작구", "중구"]
for region in region_data:
cls.region = RegionModel.objects.create(region_name=region)
cls.region.save()
- 회원가입은 매번 데이터를 넣어주어 db에 저장하는 것이므로 따로 사용자에 대한 db는 생성하지 않았다. 따라서 아래와 같이 매번 테스트 케이스를 돌릴 때마다 사용자의 데이터를 넣어주었다.
class UserRegistrationTest(APITestCase):
@classmethod
def setUpTestData(cls):
region_data = ["강남구", "강서구", "강북구", "은평구", "동작구", "중구"]
for region in region_data:
cls.region = RegionModel.objects.create(region_name=region)
cls.region.save()
def test_registration_all_data(self):
url = reverse("user_view")
user_data = {
"username" : "user10",
"password" : "1010abc!",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010",
"region" : 2
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["username"], "user10")
self.assertEqual(response.data["region"], 2)
- 회원가입에서 눈여겨 봐야 할 부분은
사용자가 입력한 데이터의 유효성을 검증
하는 것이다. 따라서 아래와 같은 패턴을 정하고 각 항목이 해당하는 부분을 테스트 코드로 작성해 보았다.
- (___)가 아예 없을 경우
- (___)의 자릿수가 모자랄 경우
- (___)가 중복되는 경우
- 이 중 대부분은 기본 validator로 걸러졌고, 나머지는 custom validator로 걸러 이에 맞는 에러 메세지와 status code를 제대로 받고 있는지 확인하였다.
def test_registration_no_username(self):
url = reverse("user_view")
user_data = {
"username" : "",
"password" : "1010abc!",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010",
"region" : 2
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["username"][0], "이 필드는 blank일 수 없습니다.")
def test_registration_wrong_username(self):
url = reverse("user_view")
user_data = {
"username" : "user",
"password" : "1010abc!",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010",
"region" : 2
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["username"][0], "username의 길이는 6자리 이상이어야 합니다.")
def test_registration_same_username(self):
url = reverse("user_view")
user_data = {
"username" : "user10",
"password" : "1010abc!",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010",
"region" : 2
}
user_data_2 = {
"username" : "user10",
"password" : "2020abc!",
"fullname" : "user20",
"email" : "user20@gmail.com",
"phone" : "010-2020-2020",
"region" : 2
}
response = self.client.post(url, user_data)
response_2 = self.client.post(url, user_data_2)
self.assertEqual(response.status_code, 201)
self.assertEqual(response_2.status_code, 400)
self.assertEqual(response_2.data["username"][0], "user의 사용자 계정은/는 이미 존재합니다.")
로그인
- 로그인 테스트 코드는
create_user
를 사용해 로그인을 할 사용자 db를 간단하게 만들고, 이를 로그인시켜 db에 로그인할 사용자의 데이터가 있는지 확인하는 방식으로 진행했다.
JWT
를 사용하고 있어 로그인을 시도하면 토큰들이 나오는데, 이것을 비교해보고 싶었으나 매번 값이 달라져
어쩔 수 없이 status_code만 확인하였다.
class UserLoginTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.user_data = {"username" : "user10", "password" : "1010abc!"}
cls.user = UserModel.objects.create_user("user10", "1010abc!")
def test_login_all_data(self):
url = reverse("ipark_token")
user_data = {
"username" : "user10",
"password" : "1010abc!"
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 200)
def test_login_no_username(self):
url = reverse("ipark_token")
user_data = {
"username" : "",
"password" : "1010abc!"
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["username"][0], "이 필드는 blank일 수 없습니다.")
def test_login_no_password(self):
url = reverse("ipark_token")
user_data = {
"username" : "user1010",
"password" : ""
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["password"][0], "이 필드는 blank일 수 없습니다.")
회원정보 수정 및 회원탈퇴
- 회원정보의 수정 및 탈퇴를 진행하기 위해
setUpTestData
를 통해 사용자 db를 만들어 테스트 클래스가 끝날 때까지 db를 유지하는 방식을 사용했다.
- 사용자의 기본 정보들은 모델을 사용해 db를 생성하는 것처럼 진행하면 되며,
foreignKey
인 region
은 아래와 같이 작성해 값이 잘 들어가지는 것을 볼 수 있다.
client
의 경우, cls를 사용하는 setUpTestData
안에서는 사용하지 못하는 것으로 알고 있어 그냥 setUp
을 만들어 사용했었다. 하지만 아래과 같이 APIClient()
를 임포트해 사용하게 되면 작성할 수 있다는 것을 알게 되었다.
@classmethod
def setUpTestData(cls):
region_data = ["강남구", "강서구", "강북구", "은평구", "동작구", "중구"]
for region in region_data:
cls.region = RegionModel.objects.create(region_name=region)
cls.region.save()
cls.user = UserModel.objects.create(
username="user10",
password=make_password("1010abc!"),
fullname="user10",
email="user10@gmail.com",
phone="010-1010-1010",
region=RegionModel.objects.get(id=3))
cls.user_1 = UserModel.objects.create(
username="user30",
password=make_password("3030abc!"),
fullname="user30",
email="user30@gmail.com",
phone="010-3030-3030",
region=RegionModel.objects.get(id=3))
cls.client = APIClient()
cls.login_data = {"username": "user10", "password" : "1010abc!"}
cls.access_token = cls.client.post(reverse("ipark_token"), cls.login_data).data["access"]
- 회원정보 수정은 아래의 조건을 염두에 두고 테스트를 하였다.
- 회원정보를 수정할 때, 두 가지의 경우로 나누어진다.
1. 비밀번호를 제외한 정보들만 변경할 때
2. 비밀번호까지 전부 다 변경할 떄
- 회원정보를 변경할 때 partial=True로 인해 변경하고 싶은 정보만 변경할 수 있다.
- 빈 값을 넣으면 기본 validator에 의해 걸러지기 때문에 원래 가지고 있던 값을 넣어줘야 한다.
* region의 경우, 프론트에서 기본값이 강남구로 되어 있기 때문에 없을 경우는 따로 테스트하지 않음
- 프론트에서 데이터를 보낼 때도 아예 위의 두 가지 케이스로 나누어 보내기 때문에 특별한 예외 상황은 발생할 수 없다고 생각한다.
- 회원정보 수정의 모든 코드는 위에서 진행했던 방식과 똑같은 흐름을 가지고 있다. 하지만 회원정보 수정을 하면서 코드의 잘못된 부분을 수정하였고, 그 부분을 작성해 보겠다.
def test_modify_same_password(self):
url = reverse("user_view")
data_for_change = {
"username" : "user10",
"password" : "1010abc!",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010",
"region" : 4
}
response = self.client.put(
path=url,
data=data_for_change,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["password"], "현재 사용중인 비밀번호와 동일한 비밀번호는 입력할 수 없습니다.")
- 위의 코드를 작성하며 처음에 에러 메세지가 제대로 오지 않아 serializer를 확인해 보았고, 코드는 문제없이 돌아갔지만 의미가 잘못된 것을 볼 수 있었다. 따라서 아래와 같이 수정하였다.
- 주석에 작성되어 있는 것과 같이 작성되어 있었는데(아래 조건문 부분은 생략), 코드의 의미는
validator를 통해 검증된 데이터와 validator를 통해 검증된 데이터
를 비교하는 것이었다.
- 제대로 된 코드였다면
사용자의 db에 있는 값인 instance와 사용자가 수정하기 위해 입력한 데이터인 validated_data
를 비교해야 된다. 따라서 그 부분을 수정할 수 있었다.
def update(self, instance, validated_data):
for key, value in validated_data.items():
if key == "password":
user = instance
if check_password(value, user.password):
raise serializers.ValidationError(
detail={"password": "현재 사용중인 비밀번호와 동일한 비밀번호는 입력할 수 없습니다."})
else:
instance.set_password(value)
continue
setattr(instance, key, value)
instance.save()
return instance
- 회원탈퇴의 경우, 탈퇴는 항상 성공적으로 진행되었지만
테스트 코드 안에서 테스트 db에서 사용자가 삭제되는 것을 어떻게 확인해야할지 몰라
status_code만 작성하였다.
def test_delete_user(self):
url = reverse("user_view")
response = self.client.delete(url, HTTP_AUTHORIZATION=f"Bearer {self.access_token}")
self.assertEqual(response.data["message"], "회원탈퇴 성공")
아이디 찾기
- 아이디를 찾기 위해 필요한 값은
이메일
과 핸드폰 번호
이다.
- 따라서 값의 유무와 더불어 데이터의 유효성까지 같이 검증해 보았다.
class SearchUsernameTest(APITestCase):
@classmethod
def setUpTestData(cls):
user_data = {
"username" : "user10",
"password" : "1010abc!",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010"
}
cls.user = UserModel.objects.create(**user_data)
def test_search_username(self):
url = reverse("myid_view")
data = {
"email" : "user10@gmail.com",
"phone" : "010-1010-1010"
}
response = self.client.post(url, data)
self.assertEqual(response.data["username"], "user10")
def test_search_username_no_email(self):
url = reverse("myid_view")
data = {
"email" : "",
"phone" : "010-1010-1010"
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["message"], "사용자가 존재하지 않습니다")
def test_search_username_no_email(self):
url = reverse("myid_view")
data = {
"email" : "user10@gmail.",
"phone" : "010-1010-1010"
}
data_2 = {
"email" : "user10",
"phone" : "010-1010-1010"
}
response = self.client.post(url, data)
response_2 = self.client.post(url, data_2)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["message"], "사용자가 존재하지 않습니다")
self.assertEqual(response_2.status_code, 404)
self.assertEqual(response_2.data["message"], "사용자가 존재하지 않습니다")
def test_search_username_no_phone(self):
url = reverse("myid_view")
data = {
"email" : "user10@gmail.com",
"phone" : ""
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["message"], "사용자가 존재하지 않습니다")
비밀번호 변경
- 비밀번호 변경은 로그인 창에서 사용자가 비밀번호를 기억하지 못할 때 사용할 수 있는 기능이다. 따라서
로그인은 필요하지 않다.
- 비밀번호를 변경하기 위해서는 2 가지의 단계를 밟아야 한다.
- 비밀번호 변경 자격 확인
- 변경할 비밀번호 입력
- 테스트 db를 만들 때 중요한 점은
비밀번호를 해싱
해야 하는 것이다.
- django의 특성 상 비밀번호를 해싱하지 않으면 일반 문자열로 저장되어 로그인이 되지 않을 뿐더러 비밀번호 비교에 사용되는
check_password
를 통과하지 못한다.
class AlterPasswordTest(APITestCase):
@classmethod
def setUpTestData(cls):
user_data = {
"username" : "user10",
"fullname" : "user10",
"email" : "user10@gmail.com",
"phone" : "010-1010-1010"
}
cls.user = UserModel.objects.create(**user_data)
cls.user.set_password("1010abc!")
cls.user.save()
def test_post_user_info(self):
url = reverse("alter_password_view")
user_data = {
"username" : "user10",
"email" : "user10@gmail.com"
}
response = self.client.post(url, user_data)
self.assertEqual(response.data["username"], "user10")
def test_post_wrong_user_info(self):
url = reverse("alter_password_view")
user_data = {
"username" : "user30",
"email" : "user30@gmail.com"
}
response = self.client.post(url, user_data)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["message"], "존재하지 않는 사용자입니다.")
def test_post_wrong_username_or_email(self):
url = reverse("alter_password_view")
user_data = {
"username" : "user30",
"email" : "user10@gmail.com"
}
user_data_2 = {
"username" : "user10",
"email" : "user30@gmail.com"
}
user_data_3 = {
"username" : "user10",
"email" : "user10@gmail"
}
response = self.client.post(url, user_data)
response_2 = self.client.post(url, user_data_2)
response_3 = self.client.post(url, user_data_3)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["message"], "존재하지 않는 사용자입니다.")
self.assertEqual(response_2.status_code, 404)
self.assertEqual(response_2.data["message"], "존재하지 않는 사용자입니다.")
self.assertEqual(response_3.status_code, 400)
self.assertEqual(response_3.data["message"], "이메일 형식에 맞게 작성해주세요.")
def test_post_no_username_or_email(self):
url = reverse("alter_password_view")
user_data = {
"username" : "",
"email" : "user10@gmail.com"
}
user_data_2 = {
"username" : "user10",
"email" : ""
}
response = self.client.post(url, user_data)
response_2 = self.client.post(url, user_data_2)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["message"], "아이디 또는 이메일 값을 제대로 입력해주세요.")
self.assertEqual(response_2.status_code, 400)
self.assertEqual(response_2.data["message"], "아이디 또는 이메일 값을 제대로 입력해주세요.")
def test_alter_password(self):
url = reverse("alter_password_view")
password_data = {
"username" : "user10",
"email" : "user10@gmail.com",
"new_password" : "abcde10!",
"rewrite_password" : "abcde10!"
}
response = self.client.put(url, password_data)
self.assertEqual(response.data["message"], "비밀번호 변경이 완료되었습니다! 다시 로그인해주세요.")
def test_alter_password_not_same_password(self):
url = reverse("alter_password_view")
password_data = {
"username" : "user10",
"email" : "user10@gmail.com",
"new_password" : "abcde10!",
"rewrite_password" : "abcde20!"
}
password_data_2 = {
"username" : "user10",
"email" : "user10@gmail.com",
"new_password" : "abcde10!",
"rewrite_password" : ""
}
password_data_3 = {
"username" : "user10",
"email" : "user10@gmail.com",
"new_password" : "",
"rewrite_password" : "abcde10!"
}
response = self.client.put(url, password_data)
response_2 = self.client.put(url, password_data_2)
response_3 = self.client.put(url, password_data_3)
self.assertEqual(response.data["message"], "두 비밀번호가 일치하지 않습니다.")
self.assertEqual(response_2.data["message"], "비밀번호를 제대로 입력해주세요.")
self.assertEqual(response_3.data["message"], "비밀번호를 제대로 입력해주세요.")
def test_alter_wrong_password(self):
url = reverse("alter_password_view")
password_data = {
"username" : "user10",
"email" : "user10@gmail.com",
"new_password" : "ab10!",
"rewrite_password" : "ab10!"
}
response = self.client.put(url, password_data)
self.assertEqual(response.data["message"], "비밀번호를 양식에 맞게 작성해주세요.")
def test_alter_same_password(self):
url = reverse("alter_password_view")
password_data = {
"username" : "user10",
"email" : "user10@gmail.com",
"new_password" : "1010abc!",
"rewrite_password" : "1010abc!"
}
response = self.client.put(url, password_data)
self.assertEqual(response.data["message"], "현재 사용중인 비밀번호와 동일한 비밀번호는 입력할 수 없습니다.")
계정관리 페이지 접근 권한 확인
- 사용자가 자신의 정보를 수정하기 위해서는 권한을 인증해야 한다.
- 프론트에서는 사용자의 username을 payload에서 꺼내 값을 보여주기 때문에 사용자가 해당 값을 수정하지 않는 이상 username에 대한 에러는 발생하지 않는다.
class UserVerifyTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.user = UserModel.objects.create(
username="user10",
password=make_password("1010abc!"),
fullname="user10",
email="user10@gmail.com",
phone="010-1010-1010"
)
cls.client = APIClient()
cls.login_data = {"username": "user10", "password" : "1010abc!"}
cls.access_token = cls.client.post(reverse("ipark_token"), cls.login_data).data["access"]
def test_user_verify(self):
url = reverse("user_verification_view")
data = {
"username" : "user10",
"password" : "1010abc!"
}
response = self.client.post(
path=url,
data=data,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["email"], "user10@gmail.com")
self.assertEqual(response.data["phone"], "010-1010-1010")
def test_user_verify_with_wrong_info(self):
url = reverse("user_verification_view")
data = {
"username" : "user20",
"password" : "1010abc!"
}
data_2 = {
"username" : "user10",
"password" : "1011abc!"
}
response = self.client.post(
path=url,
data=data,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
response_2 = self.client.post(
path=url,
data=data_2,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
self.assertEqual(response.status_code, 404)
self.assertEqual(response.data["message"], "비밀번호가 일치하지 않습니다.")
self.assertEqual(response_2.status_code, 404)
self.assertEqual(response_2.data["message"], "비밀번호가 일치하지 않습니다.")
def test_user_verify_with_wrong_info(self):
url = reverse("user_verification_view")
data = {
"username" : "",
"password" : "1010abc!"
}
data_2 = {
"username" : "user10",
"password" : ""
}
response = self.client.post(
path=url,
data=data,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
response_2 = self.client.post(
path=url,
data=data_2,
HTTP_AUTHORIZATION=f"Bearer {self.access_token}"
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data["message"], "아이디를 제대로 입력해주세요.")
self.assertEqual(response_2.status_code, 400)
self.assertEqual(response_2.data["message"], "비밀번호 값을 제대로 입력해주세요.")
park app
- park app에서 내가 작성한 테스트 코드는 아래와 같다.
공원 검색
, 토글 공원 리스트
, 인기순 공원 나열
공원 검색
- 공원 검색은 총 네 가지의 경우로 이루어진다.
- 공원 이름 검색
- 공원 옵션 검색
- 공원 지역 검색
- 공원 옵션 & 공원 지역 검색
- 해당 테스트 db 작성에 엄청난 시간이 들었다. 공원의 경우, 공원과 공원 옵션이
ManyToMany
관계로 이루어져 있기 때문이다.
- 맨 처음 db를 짤 때는
bulk_create
기능을 사용해 공원 테스트 db를 작성했었다. 하지만 이렇게 만들게 되면 공원 옵션을 한 번에 여러 개 생성할 수 있어 좋지만 정작 공원의 테스트 db를 불러와보면 옵션 값이 None
으로 불러와졌다.
- 따라서 아래와 같이
through table
을 사용하는 방식으로 공원 db를 작성하였다.
def create_park(self, park_data, option_list):
self.options = ["조경", "운동", "놀이공원", "역사", "학습테마", "교양", "편익", "주차장"]
for option in self.options:
self.option_model = OptionModel.objects.create(option_name=option)
self.option_model.save()
self.park = ParkModel.objects.create(**park_data)
for option in option_list:
ParkOptionModel.objects.create(park=self.park, option=OptionModel.objects.get(id=option))
self.park.save()
@classmethod
def setUpTestData(cls):
park_data_1 = {
"park_name": "서울대공원",
"zone": "과천시",
"image": "1",
"check_count": "5"
}
option_list_1 = [1, 2, 3]
park_data_2 = {
"park_name": "남산공원",
"zone": "중구",
"image": "2",
"check_count": "10"
}
option_list_2 = [1, 2, 4, 5, 7, 8]
park_data_3 = {
"park_name": "간데메공원",
"zone": "동대문구",
"image": "3",
"check_count": "1"
}
option_list_3 = [1, 2, 7]
cls.create_park(cls, park_data_1, option_list_1)
cls.create_park(cls, park_data_2, option_list_2)
cls.create_park(cls, park_data_3, option_list_3)
- 위에서 나열한 네 가지 방식을 성공과 실패 케이스를 나눠 작성해보았다.
def test_option_find(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=1¶m=2¶m=3")
response_2 = self.client.get(f"{url}?param=4¶m=8")
response_3 = self.client.get(f"{url}?param=7")
self.assertEqual(response.data[0]["park_name"], "서울대공원")
self.assertEqual(response.data[1]["park_name"], "남산공원")
self.assertEqual(response.data[2]["park_name"], "간데메공원")
self.assertEqual(response_2.data[0]["park_name"], "남산공원")
self.assertEqual(response_3.data[0]["park_name"], "남산공원")
self.assertEqual(response_3.data[1]["park_name"], "간데메공원")
def test_option_find_fail(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=9")
response_1 = self.client.get(f"{url}?param=6")
self.assertEqual(response.status_code, 404)
self.assertEqual(response_1.data["message"], "공원을 찾을 수 없습니다.")
def test_zone_find(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=과천시")
response_2 = self.client.get(f"{url}?param=중구")
response_3 = self.client.get(f"{url}?param=동대문구")
self.assertEqual(response.data[0]["park_name"], "서울대공원")
self.assertEqual(response_2.data[0]["park_name"], "남산공원")
self.assertEqual(response_3.data[0]["park_name"], "간데메공원")
def test_zone_find_fail(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=강남구")
response_2 = self.client.get(f"{url}?param=은평구")
response_3 = self.client.get(f"{url}?param=강서구")
self.assertEqual(response.status_code, 404)
self.assertEqual(response_2.status_code, 404)
self.assertEqual(response_3.data["message"], "공원을 찾을 수 없습니다.")
def test_option_and_zone_find(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=2¶m=과천시")
response_2 = self.client.get(f"{url}?param=4¶m=중구")
response_3 = self.client.get(f"{url}?param=1¶m=7¶m=동대문구")
self.assertEqual(response.data[0]["park_name"], "서울대공원")
self.assertEqual(response_2.data[0]["park_name"], "남산공원")
self.assertEqual(response_3.data[0]["park_name"], "간데메공원")
def test_option_and_zone_find_fail(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=6¶m=과천시")
response_2 = self.client.get(f"{url}?param=3¶m=중구")
response_3 = self.client.get(f"{url}?param=3¶m=6¶m=동대문구")
self.assertEqual(response.status_code, 404)
self.assertEqual(response_2.status_code, 404)
self.assertEqual(response_3.data["message"], "공원을 찾을 수 없습니다.")
def test_park_by_name(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=서")
response_2 = self.client.get(f"{url}?param=원")
self.assertEqual(response.data[0]["park_name"], "서울대공원")
self.assertEqual(response_2.data[0]["park_name"], "서울대공원")
self.assertEqual(response_2.data[1]["park_name"], "남산공원")
self.assertEqual(response_2.data[2]["park_name"], "간데메공원")
def test_park_by_name_fail(self):
url = reverse("park_search")
response = self.client.get(f"{url}?param=탑골공원")
response_2 = self.client.get(f"{url}?param=상도근린공원")
self.assertEqual(response.status_code, 404)
self.assertEqual(response_2.data["message"], "공원을 찾을 수 없습니다.")
토글 공원 리스트
- 내가 만든 프로젝트의 오른쪽 상단에는 공원의 전체 리스트를 볼 수 있도록
토글
을 만들어 놓았다.
- 맨 처음 프로젝트를 만들었을 당시에는 그저 가나다 순으로 공원들을 전부 나열했었는데, 사용자 피드백에서 효율적이지 못하다는 피드백을 받았고 이를
공원 맨 앞글자 초성
을 사용해 공원을 분리하는 방식으로 수정했다.
- 위의 방식을 테스트하는 것으로 코드를 리팩토링하였고, 해당 테스트 코드에서 참조하는 view는 get 요청으로 공원 데이터를 불러다 프론트에 전송하기만 하므로 실패하는 케이스가 존재하지 않는다.
- 따라서 아래와 같이 간단히 작성해보았다.
class ToggleParkListTest(APITestCase):
def create_park(self, park_data):
self.park = ParkModel.objects.create(**park_data)
self.park.save()
@classmethod
def setUpTestData(cls):
park_data_1 = {
"park_name": "서울대공원",
"zone": "과천시",
"addr_dong": "ㄱ",
"image": "1",
"check_count": "5"
}
park_data_2 = {
"park_name": "남산공원",
"zone": "중구",
"addr_dong": "ㅈ",
"image": "2",
"check_count": "10"
}
park_data_3 = {
"park_name": "간데메공원",
"zone": "동대문구",
"addr_dong": "ㄷ",
"image": "3",
"check_count": "1"
}
park_data_4 = {
"park_name": "효창공원",
"zone": "용산구",
"addr_dong": "ㅇ",
"image": "4",
"check_count": "0"
}
cls.create_park(cls, park_data_1)
cls.create_park(cls, park_data_2)
cls.create_park(cls, park_data_3)
cls.create_park(cls, park_data_4)
def test_get_toggle_list(self):
url = reverse("toggle_park")
response = self.client.post(url,{"data": "ㄱ"})
response_2 = self.client.post(url,{"data": "ㅈ"})
response_3 = self.client.post(url,{"data": "ㄷ"})
response_4 = self.client.post(url,{"data": "ㅇ"})
self.assertEqual(response.data[0]["park_name"], "서울대공원")
self.assertEqual(response_2.data[0]["park_name"], "남산공원")
self.assertEqual(response_3.data[0]["park_name"], "간데메공원")
self.assertEqual(response_4.data[0]["park_name"], "효창공원")
인기순 공원 나열
- 사용자들이 조회를 많이 한 공원이 맨 앞으로 나오게 해 공원을 나열하는 기능에 대한 테스트 코드이다.
- 해당 부분도 단순한 get 요청이고 별다른 조건이 걸려있지 않았다. 특징을 꼽으라면
조회수가 0
인 공원들은 나열하지 않는다는 것이다.
- 하지만 이 부분도
filter(check_count__gte=1).order_by("-check_count")
를 사용해 단순하게 필터링하는 방식이라 에러가 날 부분이 없다.
- 테스트 db에서 조회수가 0인 효창공원은 assertEqual에 존재하지 않는 것을 볼 수 있다.
class ParkPopularityTest(APITestCase):
def create_park(self, park_data):
self.park = ParkModel.objects.create(**park_data)
self.park.save()
@classmethod
def setUpTestData(cls):
park_data_1 = {
"park_name": "서울대공원",
"zone": "과천시",
"image": "1",
"check_count": "5"
}
park_data_2 = {
"park_name": "남산공원",
"zone": "중구",
"image": "2",
"check_count": "10"
}
park_data_3 = {
"park_name": "간데메공원",
"zone": "동대문구",
"image": "3",
"check_count": "1"
}
park_data_4 = {
"park_name": "효창공원",
"zone": "용산구",
"image": "4",
"check_count": "0"
}
cls.create_park(cls, park_data_1)
cls.create_park(cls, park_data_2)
cls.create_park(cls, park_data_3)
cls.create_park(cls, park_data_4)
def test_popularity_park(self):
url = reverse("park_popularity")
response = self.client.get(url)
self.assertEqual(response.data[0]["park_name"], "남산공원")
self.assertEqual(response.data[1]["park_name"], "서울대공원")
self.assertEqual(response.data[2]["park_name"], "간데메공원")