Channels는 Django를 확장해 웹소켓과 같이 HTTP가 아닌 프로토콜을 핸들링할 수 있게 돕고 비동기적인 처리를 가능하게 해주는 ASGI의 구현체로, 장고를 이용한 실시간 채팅 구현 등에 활용할 수 있습니다. 이 글은 채널즈의 공식 문서를 최대한 원어를 살려 번역한 글입니다. 다소 의역하거나 생략한 부분이 있을 수 있음을 너그러이 양해해주시고, 잘못을 자유롭게 지적해주시면 감사하겠습니다.
튜토리얼 3에서 이어집니다.
역자 주: 이 튜토리얼은 테스트 코드 작성법을 다루고 있습니다. 단순히 기능 구현만을 원하시는 분들은 컨슈머로 넘어가셔도 괜찮습니다.
채팅 서버가 계속 잘 동작할지 보장하려면 테스트 코드를 작성해야 할 필요가 있습니다.
엔드투엔드 테스트 케이스를 만들기 위해 크롬 웹 브라우저를 제어하는 Selenium
을 사용할겁니다. 테스트해야 할 대상은 다음과 같습니다:
크롬 웹브라우저가 없으시면 지금 설치해주세요.
이어서 크롬드라이버를 설치해주세요.
그 후, 다음 커맨드로 Selenium
을 설치합니다:
$ python3 -m pip install selenium
chat/tests.py
라는 이름으로 파일을 하나 만듭니다. 이제 앱 디렉토리는 다음과 같습니다:
chat/
__init__.py
consumers.py
routing.py
templates/
chat/
index.html
room.html
tests.py
urls.py
views.py
다음 코드를 chat/tests.py
에 붙여넣으세요:
# chat/tests.py
from channels.testing import ChannelsLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
class ChatTests(ChannelsLiveServerTestCase):
serve_static = True # emulate StaticLiveServerTestCase
@classmethod
def setUpClass(cls):
super().setUpClass()
try:
# NOTE: Requires "chromedriver" binary to be installed in $PATH
cls.driver = webdriver.Chrome()
except:
super().tearDownClass()
raise
@classmethod
def tearDownClass(cls):
cls.driver.quit()
super().tearDownClass()
def test_when_chat_message_posted_then_seen_by_everyone_in_same_room(self):
try:
self._enter_chat_room('room_1')
self._open_new_window()
self._enter_chat_room('room_1')
self._switch_to_window(0)
self._post_message('hello')
WebDriverWait(self.driver, 2).until(lambda _:
'hello' in self._chat_log_value,
'Message was not received by window 1 from window 1')
self._switch_to_window(1)
WebDriverWait(self.driver, 2).until(lambda _:
'hello' in self._chat_log_value,
'Message was not received by window 2 from window 1')
finally:
self._close_all_new_windows()
def test_when_chat_message_posted_then_not_seen_by_anyone_in_different_room(self):
try:
self._enter_chat_room('room_1')
self._open_new_window()
self._enter_chat_room('room_2')
self._switch_to_window(0)
self._post_message('hello')
WebDriverWait(self.driver, 2).until(lambda _:
'hello' in self._chat_log_value,
'Message was not received by window 1 from window 1')
self._switch_to_window(1)
self._post_message('world')
WebDriverWait(self.driver, 2).until(lambda _:
'world' in self._chat_log_value,
'Message was not received by window 2 from window 2')
self.assertTrue('hello' not in self._chat_log_value,
'Message was improperly received by window 2 from window 1')
finally:
self._close_all_new_windows()
# === Utility ===
def _enter_chat_room(self, room_name):
self.driver.get(self.live_server_url + '/chat/')
ActionChains(self.driver).send_keys(room_name + '\n').perform()
WebDriverWait(self.driver, 2).until(lambda _:
room_name in self.driver.current_url)
def _open_new_window(self):
self.driver.execute_script('window.open("about:blank", "_blank");')
self.driver.switch_to_window(self.driver.window_handles[-1])
def _close_all_new_windows(self):
while len(self.driver.window_handles) > 1:
self.driver.switch_to_window(self.driver.window_handles[-1])
self.driver.execute_script('window.close();')
if len(self.driver.window_handles) == 1:
self.driver.switch_to_window(self.driver.window_handles[0])
def _switch_to_window(self, window_index):
self.driver.switch_to_window(self.driver.window_handles[window_index])
def _post_message(self, message):
ActionChains(self.driver).send_keys(message + '\n').perform()
@property
def _chat_log_value(self):
return self.driver.find_element_by_css_selector('#chat-log').get_property('value')
이 테스트 클래스는 장고에서 엔드투엔드 테스트용으로 보통 사용하는 StaticLiveServerTestCase
나 LiveServerTestCase
가 아니라 ChannelsLiveServerTestCase
를 상속받아 채널 라우팅 설정 안의 /ws/room/ROOM_NAME/
와 같은 URL이 잘 작동하도록 합니다.
우리는 여기서 테스팅 시에 인메모리 DB로 작동하는 sqlite3
를 사용하려 하므로 테스트가 제대로 동작하지 않을 것입니다. 따라서 본 프로젝트에서 sqlite3
데이터베이스가 테스트시에 인메모리 방식이 아니도록 설정해야 합니다. mysite/settings.py
의 DATABASES
설정에 TEST
인자를 추가합니다:
# mysite/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'TEST': {
'NAME': os.path.join(BASE_DIR, 'db_test.sqlite3')
}
}
}
테스트를 실행하려면 다음 커맨드를 입력합니다:
$ python3 manage.py test chat.tests
그러면 콘솔에 다음과 같이 출력됩니다:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 5.014s
OK
Destroying test database for alias 'default'...
채팅 서버 테스팅까지 성공하였습니다!
축하드립니다. 잘 동작하는 채팅 서버를 구현하고, 비동기적으로 작성해 성능을 높였고, 후에 실패하지 않도록 자동화된 테스트도 작성했습니다.
튜토리얼은 이것으로 끝입니다. 이제 본인의 앱에서 채널즈를 시작할 수 있을 정도가 되셨을 것입니다. 앞으로는 필요한 때에 이 다음에 이어지는 문서들로 돌아오시면 됩니다!