[Channels] 공식문서 번역 📘 - 7: 튜토리얼 (4) - 테스팅

Eunsung Lim·2021년 1월 15일
2
post-thumbnail

Channels는 Django를 확장해 웹소켓과 같이 HTTP가 아닌 프로토콜을 핸들링할 수 있게 돕고 비동기적인 처리를 가능하게 해주는 ASGI의 구현체로, 장고를 이용한 실시간 채팅 구현 등에 활용할 수 있습니다. 이 글은 채널즈의 공식 문서를 최대한 원어를 살려 번역한 글입니다. 다소 의역하거나 생략한 부분이 있을 수 있음을 너그러이 양해해주시고, 잘못을 자유롭게 지적해주시면 감사하겠습니다.

튜토리얼 파트 4: 자동화된 테스팅

튜토리얼 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')

이 테스트 클래스는 장고에서 엔드투엔드 테스트용으로 보통 사용하는 StaticLiveServerTestCaseLiveServerTestCase가 아니라 ChannelsLiveServerTestCase를 상속받아 채널 라우팅 설정 안의 /ws/room/ROOM_NAME/와 같은 URL이 잘 작동하도록 합니다.


우리는 여기서 테스팅 시에 인메모리 DB로 작동하는 sqlite3를 사용하려 하므로 테스트가 제대로 동작하지 않을 것입니다. 따라서 본 프로젝트에서 sqlite3 데이터베이스가 테스트시에 인메모리 방식이 아니도록 설정해야 합니다. mysite/settings.pyDATABASES 설정에 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'...

채팅 서버 테스팅까지 성공하였습니다!


이 다음엔?

축하드립니다. 잘 동작하는 채팅 서버를 구현하고, 비동기적으로 작성해 성능을 높였고, 후에 실패하지 않도록 자동화된 테스트도 작성했습니다.

튜토리얼은 이것으로 끝입니다. 이제 본인의 앱에서 채널즈를 시작할 수 있을 정도가 되셨을 것입니다. 앞으로는 필요한 때에 이 다음에 이어지는 문서들로 돌아오시면 됩니다!

profile
Strong belief in connecting the dots. 찬찬히 배우고 있는 학생 개발자입니다.

0개의 댓글