시작 세팅
대략적인 설명만 하자면 공모전을 위해 장고를 공부하게 되었고, 도커를 활용하여 장고 환경을 세팅하였다.
docker-compose run --rm app sh -c "python manage.py startapp core" 를 통해 core 앱을 만들수 있는데 그 이후 해줘야하는 작업이 있다.
장고는 settings.py에 있는 INSTALLED_APPS 만 마이그레이션, URL 매핑, 모델 등록 등에서 인식한다고 한다
따라서 추가한 core를 INSTALLED_APPS에 추가해줘야한다
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'core', # core 앱을 추가
]Race Condition 해소
Docker-Compose 로 service와 db 두개의 컨테이너를 관리할때
service 컨테이너에서 db에 접근할때 아직 db는 준비가 안된 상태일수도 있다

따라서 wait_for_db.py 를 통해 타이밍을 맞추어 줘야한다. 다시 말해 wait_for_db를 service 컨테이너에서 계속 실행을 해야한다는 말인데 그러기 위해서는 커스텀 명령어 지정을 해줘야한다
그리고 Django에서 커스텀 명령어를 실행하려면 반드시 management/commands 경로 아래에 파일이 위치해야한다
title: 꺠알 상식
Python에서 ***arg(별하나)**는 **가변인자**, **\**opt(별두개)**는 **키워드인자**를 받을수 있다는 표현이다
#### **✅ 실제 사용 예시**
```bash
`python manage.py custom_command 123 --verbose=True`
```
- `123` → `args`에 들어감 (`args = (123,)`)
- `--verbose=True` → `opt`에 들어감 (`opt = {'verbose': True}`)@patch('core.management.commands.wait_for_db.Command.check') # ✅ Command.check()를 Mock 처리
class CommandTests(SimpleTestCase):
"""Test commands"""
def test_wait_for_db_ready(self, patched_check): # ✅ patched_check는 Mock 객체
"""Test waiting for database if database ready"""
patched_check.return_value = True # ✅ check()가 항상 True를 반환하도록 설정
@patch() 데코레이터가 자동으로 Command.check를 Mock 객체로 바꿔주고, 이를 테스트 메서드(test_wait_for_db_ready)의 인자로 넘겨주기 때문에
patched_check이 Mock객체가 되고 Mock 객체 내부의 return_value를 통해 호출시 True를 반환하게 해줄수 있다
테스트 설명
return_value
@patch('core.management.commands.wait_for_db.Command.check') # ✅ Command.check()를 Mock으로 대체
class CommandTests(SimpleTestCase):
"""Test commands"""
def test_wait_for_db_ready(self, patched_check):
"""Test waiting for database if database ready"""
patched_check.return_value = True # ✅ check()가 항상 True를 반환하도록 설정
call_command('wait_for_db') # ✅ wait_for_db 실행 (내부적으로 check() 호출)
patched_check.assert_called_once_with(databases=['default']) # ✅ check()가 단 한 번 호출되었고, 올바른 인자로 호출되었는지 확인
기존 Command.check는 실제 데이터베이스 를 체크를하여 작동준비가 되면 True를 반환하게끔 한다.
테스트에서는 이 check을 Mock 객체로 받아, 실제 데이터베이스를 참고하게 하지 않고 미리
patched_check.return_value = True를 통해 준비가 되었다는 가정을 깔고 wait_for_db를 호출하였다. (호출시 해당 값 반환)
그리고 assert함수를 통해 wait_for_db가 호출되면 databases=['defailt'] 가 세팅될것이므로 이를 통해 테스트를 하는 것이다
side_effect
@patch('time.sleep') # ✅ time.sleep()을 Mock으로 대체 (테스트 속도 향상을 위해)
def test_wait_for_db_delay(self, patched_sleep, patched_check):
"""Test waiting for database when getting Operational Error"""
patched_check.side_effect = [Psycopg2Error] * 2 + \
[OperationalError] * 3 + \
[True] # ✅ 2번 Psycopg2Error, 3번 OperationalError, 마지막으로 True 반환
call_command('wait_for_db') # ✅ wait_for_db 실행 (내부적으로 check() 반복 호출)
self.assertEqual(patched_check.call_count, 6) # ✅ check()가 총 6번 호출되었는지 검증
patched_check.assert_called_with(databases=['default']) # ✅ check()가 올바른 인자로 호출되었는지 확인
side_effect는 Mock객체가 호출될 때 실행할 동작의 순서를 지정하는 속성이다. 위 예시의 경우 2번의 Psycopg2에러, 3번의 Operational 에러 그리고 True를 반환하게 하여 5번동안 준비가 안되다가 6번째에 준비가 된 시나리오를 만든것이다
그리고 time.sleep같은 경우 Mock을 하게되면 따로 함수에 작성하지 않아도 테스트에서 sleep은 즉시반환되거나 사용자가 지정한 방식대로 조절할수 있다.
