Статьи

Самые большие ошибки, которые делают разработчики Django при использовании салата

Этот пост является первым в серии публикаций о передовых методах использования Lettuce , среды тестирования для Django.

Когда я впервые выпустил Lettuce , фреймворк для написания автоматических тестов в Django с пользовательскими историями , я понятия не имел, что он станет настолько широко используемым. Было поистине удивительно видеть, как он расширился от Бразилии до США, Китая и многих других стран. Это даже было переведено на 15 языков.

Тем не менее, за последние 6 месяцев я наблюдал распространенное использование, которого разработчики должны избегать по указанным ниже причинам.

Шаги из определения шага

Как и Cucumber, Lettuce поддерживает вызов других шагов из определения шага. Это может быть очень удобной функциональностью, но может легко стать источником кода, который трудно поддерживать.

Так почему же эта функциональность доступна? Хотя Lettuce является инструментом тестирования, step.behave_as был патчем, который был включен в кодовую базу без полного охвата тестированием . step.behave_as заставляет шаг вызывать многие другие, анализируя текст и вызывая их синхронно и последовательно .

Некоторым людям нравится использовать эту функциональность, чтобы их сценарий выглядел более скудным, и это нормально. Фактическая проблема заключается в том, что этот рабочий процесс является неоптимальным, поэтому я бы посоветовал использовать эту функцию с осторожностью.

Пример использования step.behave_as (пожалуйста, избегайте того же) В качестве примера давайте рассмотрим следующую функцию и соответствующие определения шагов:

Feature: exemplify why step.behave_as can be a not-so-good option

  Scenario: Login should work
    Given I am at the login page
    When I type the username "root"
    And I type the password "123"
    And I try to perform the login
    Then it works and I am redirected to the admin page

  Scenario: Get in the user management admin page
    Given I log in as "admin" with password "123"
    When I click to manage users
    Then I am pointed out to the user management page

определяется как:

from lettuce import *
from lettuce.django import django_url
from splinter.browser import Browser

PAGES = {
    "the login page": "/login",
    "the user management page": "/manage/users"
}

def page_name_is_valid(name):
    assert PAGES.has_key(name), \
        'the page "%s" is not mapped in the PAGES dictionary, ' \
        'check if you misspelled it or add into it' % name
    return True

@before.each_scenario
def prepare_browser(scenario):
    world.browser = Browser()

@step(ur'I am at (.*)')
def i_am_at_some_url(step, name):
    assert page_name_is_valid(name)

    full_url = django_url(PAGES[name])
    world.browser.visit(full_url)

@step(ur'I type the (\S+) "(.*)"')
def i_type_some_value_into_a_field(step, field, value):
    browser.fill(field, value)

@step(ur'I try to perform the login')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#submit-login").click()

@step(ur'it works and I am redirected to (.*)')
def it_works_and_im_redirected_to(step, name):
    assert page_name_is_valid(name)

    current_url = world.browser.url
    full_url = django_url(PAGES[name])

    assert current_url == full_url, \
        'the current url is "%s" but should be "%s"' % (current_url, full_url)

@step(ur'I log in as "(\w+)" with password "(.*)"')
def i_log_in_as(step, username, password):
    step.behave_as(ur'''
        Given I am at the login page
        When I type the username "%s"
        And I type the password "%s"
        And I try to perform the login
        Then it works and I am redirected to the admin page
    ''' % (username, password))

@step(ur'I click to manage users')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#manage-users").click()

@step(ur'I am pointed out to (.*)')
def im_pointed_out_to(step, name):
    step.then("it works and I am redirected to %s" % name)

Так что … это выглядит мило, почему это плохо?

  1. У  реализации step.behave_as есть  проблемы .

  2. если вам нужно пропустить параметры к целевым шагам, вам нужно будет объединить или интерполировать строки, которые легко превратятся в беспорядок.

  3. если строка, которую вы передаете в качестве параметра, содержит опечатки, отладка будет проблематичной.

  4. внутренне в кодовой базе Lettuce каждый отдельный шаг построен из объекта, который связан с родительским сценарием, и метаданных, таких как, где он определен. Текущая   реализация step.behave_as неправильно переопределяет эти аспекты, что приводит к сумасшествию при отладке.

  5. как только вы жестко закодируете строки в своих определениях шагов, кодовая база вашего теста станет трудно масштабируемой для большего числа разработчиков, и, следовательно, будет трудно поддерживать .XXX


Вот как работает салат, если вы не используете step.behave_as:


Обратите внимание на два дополнительных шага, когда вы используете его
:

Решение: рефакторинг общих определений шагов в методы @ world.absorb

Lettuce предоставляет @ world.absorb , удобный декоратор, для хранения полезных и универсальных функций в глобальной области тестирования. @ World.absorb декоратор буквально поглощает декорированную функцию в мире помощник и может быть использован сразу же в любом других файлах питон.

Этот декоратор был создан именно для того, чтобы усилить рефакторинг определений шагов и помощников ландшафта, не требуя от разработчика делать слишком много импортов с разных путей, а также избегать относительного импорта. Давайте посмотрим, как будет выглядеть первый пример при использовании @ world.absorb .

from lettuce import *
from lettuce.django import django_url
from splinter.browser import Browser

PAGES = {
    "the login page": "/login",
    "the user management page": "/manage/users"
}

def page_name_is_valid(name):
    assert PAGES.has_key(name), \
        'the page "%s" is not mapped in the PAGES dictionary, ' \
        'check if you misspelled it or add into it' % name
    return True

@world.absorb
def go_to_page(name):
    assert page_name_is_valid(name)

    full_url = django_url(PAGES[name])
    world.browser.visit(full_url)

@world.absorb
def assert_current_page_is(name):
    assert page_name_is_valid(name)

    current_url = world.browser.url
    full_url = django_url(PAGES[name])

    assert current_url == full_url, \
        'the current url is "%s" but should be "%s"' % (current_url, full_url)

@before.each_scenario
def prepare_browser(scenario):
    world.browser = Browser()

@step(ur'I am at (.*)')
def i_am_at_some_url(step, name):
    world.go_to_page(name)

@step(ur'I type the (\S+) "(.*)"')
def i_type_some_value_into_a_field(step, field, value):
    browser.fill(field, value)

@step(ur'I try to perform the login')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#submit-login").click()

@step(ur'it works and I am redirected to (.*)')
def it_works_and_im_redirected_to(step, the_expected_page):
    world.assert_current_page_is(the_expected_page)

@step(ur'I log in as "(\w+)" with password "(.*)"')
def i_log_in_as(step, username, password):
    world.go_to_page("the login page")
    browser.fill("username", username)
    browser.fill("password", password)
    browser.find_by_css("button#submit-login").click()
    world.assert_current_page_is("the admin page")

@step(ur'I click to manage users')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#manage-users").click()

@step(ur'I am pointed out to (.*)')
def im_pointed_out_to(step, expected_page):
    world.assert_current_page_is(the_expected_page)

Определение шага def i_log_in_asтеперь вызывает помощников, доступных в worldпомощнике.

Вывод

Вы можете легко заметить, что в приведенном выше примере это **@world.absorb**обеспечивает лучшую ремонтопригодность и более четкие определения шагов.

  1. Жестко закодированные строки потребовали бы ручного обновления, когда любое связанное определение шага изменило свое регулярное выражение.

  2. Определения шагов, состоящие из нескольких строк, теперь просто пропускают параметры в однострочные вызовы функций.

  3. Если в жестко запрограммированной строке есть опечатки, синтаксическая ошибка не возникнет, но тест не пройдёт с вводящим в заблуждение сообщением об ошибке.