Side effect 를 방지하는 테스트 코드 작성
테스트 코드 작성의 중요성을 깨닫고 난 후부터 신경을 써서 테스트를 작성하고 있습니다.
오늘은 Django로 테스트를 작성하면서 Side effect가 발생했던 경험을 공유해보려고 합니다.
우선 제가 늘 지키는 테스트의 원칙 중 하나는 아래와 같습니다.
- 테스트는 서로 의존적이지 않아야 한다.
- 즉, 테스트 케이스를 수정하여도 다른 테스트 케이스의 실행은 보장되어야 한다.
사건은 코드를 리팩토링 하는 일이 생기면서 테스트 케이스 또한 같이 수정하는 작업에서 시작됩니다. 리팩토링도 마무리 짓고 테스트에 대한 수정도 끝나 테스트를 돌리는 순간이었습니다.
분명히 수정했던 부분과는 연관이 되지 않은 다른 테스트 케이스의 결과가 깨지기 시작했습니다. 😕
도저히 코드상으로는 관련이 없는 부분이었기에 "수정한 테스트 케이스가 잘못 됐나"에 대해서만 집착을 하였습니다. 하지만 아무리 봐도 수정한 부분에는 문제가 없어 보였습니다. 😯
그러다, 수정한 부분에 사용된 변수들을 검색하기 시작합니다. 아뿔싸! 무언가 이상한? 코드의 흔적을 보게 됩니다.
Django 에서 테스트를 작성할 때 필요한 미리 데이터를 생성하기 위한 함수로는 두 가지가 있습니다.
- setUpTestData() → Test class 1개마다 초기 test 데이터 세트를 만들어 줍니다.
- setUp() → Test case(메소드) 1개마다 호출되어, 초기 test 데이터 세트를 만들어 줍니다.
일반적으로는 SetUpTestData가 훨씬 효율적이고 빠르기 때문에 이를 주로 사용하였습니다.
바로 문제는 여기에서 시작되었습니다.
다음은 문제가 되었던 코드 입니다.
class LeffeViewSetTest(APITestCase):
@classmethod
def setUpTestData(cls):
...
cls.leffe_history = LeffeHistory.objects.create(...)
def test_leffe_history(self):
# 💥 사이드 이펙트를 유발
self.leffe_history = LeffeHistory.objects.create(...)
# 🌈 다음과 같이 copy 하여 사용 OR local 변수 사용
leffe_history = self.code_usage_history.copy()
# OR
leffe_history2 = LeffeHistory.objects.create(...)
cls의 variable 인 leffe_history를 여러 곳에서 데이터를 변조하고 있었습니다. SetUpTestData()의 특성상 테스트 시작 시 1번만 실행되기에 해당 데이터를 변조하게 된다면, 그 데이터를 참조하는 곳에서 값이 변동된 상태로 있기 때문에 Side effect가 발생하게 됩니다.
위와 같은 경우에는 variable을 copy() 하여 사용하거나, local varible을 선언하여 테스트를 작성해야 합니다.
정말 소소한 부분이지만 이로 인하여 생각했던 것과는 정 반대의 결과로 이어지기 때문에 주의해야할 포인트 같습니다. 테스트 경험이 없으신 분들은 더더욱 해당 에러를 인지하기 어려울 것이라는 생각도 듭니다.
개인적으로는 변조가 자주 일어나는 데이터의 경우에는 setUp()을 활용하여 메소드마다 동일한 데이터를 보장받을 수 있도록 해주는 것이 좋다고 생각합니다. (다양한 경우가 있으니 절대적이진 않습니다, 특히 setUP()은 속도면에서 상대적으로 느립니다)
테스트를 작성하면서 겪었던 문제점에 대해서 공유해보았습니다. Side effect를 방지하기 위해 더 좋은 테스트 작성법을 아시는 분은 댓글로 남겨주시면 감사하겠습니다 :)
'SW개발 > Django' 카테고리의 다른 글
[Django]get_or_create() 란? (0) | 2022.08.01 |
---|---|
[Django]DateField의 auto_now_add 옵션, 정확히 알아보기 (0) | 2022.07.31 |
[Django]GenericForeignKey의 문제점 (feat. 안티패턴의 지름길) (0) | 2021.11.23 |
[Django]1.11에서 3.2로 버전 업그레이드 방법 (마이그레이션) (0) | 2021.11.23 |
[Django]silk로 프로파일링 하기 (0) | 2021.08.20 |