Урок 4. Автоматизированное тестирование

Этот урок продолжает Урок 3. Мы создали простой чат-сервер и теперь создадим для него несколько автоматических тестов.

Тестирование отображений

Чтобы сервер чата продолжал работать, мы напишем несколько тестов.

Мы напишем набор сквозных тестов с использованием Selenium для управления веб-браузером Chrome. Эти тесты гарантируют, что:

  • когда сообщение чата публикуется, его видят все в одной комнате

  • когда сообщение чата публикуется, оно не видится никому в другой комнате

Установите браузер Chrome, если у вас его еще нет.

Установите chromedriver.

Установите 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')

Наш набор тестов расширяет ChannelsLiveServerTestCase, а не обычные наборы Django для сквозных тестов (StaticLiveServerTestCase или LiveServerTestCase), так что URL-адреса внутри конфигурации маршрутизации каналов, такие как /ws/room/ROOM_NAME / будет работать внутри пакета.

Мы используем sqlite3, который для тестирования запускается как база данных в памяти, и поэтому тесты не будут работать правильно. Нам нужно сообщить нашему проекту, что база данных sqlite3 не должна находиться в памяти для запуска тестов. Отредактируйте файл mysite/settings.py и добавьте аргумент TEST в параметр DATABASES:

# 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'...

Теперь у вас есть проверенный чат-сервер!

Что дальше?

Поздравляем! Вы полностью реализовали сервер чата, сделали его работоспособным, написав его в асинхронном стиле, и написали автоматические тесты, чтобы убедиться, что он не сломается.

Это конец урока. На данный момент вы должны знать достаточно, чтобы запустить собственное приложение, которое использует каналы и начать дурачиться. Когда вам будет нужно освоить новые приемы, вернитесь к остальным разделам Documentation.

Вернуться на верх