Как написать поддерживаемые, а не хрупкие модульные тесты для графического интерфейса?

16

Я попытался написать модульные тесты пользовательского интерфейса для своих приложений с графическим интерфейсом, и я столкнулся с проблемой, заключающейся в том, что, хотя они хорошо работают, когда я их первоначально пишу, они оказываются хрупкими и ломаются всякий раз, когда меняется дизайн (то есть довольно часто). Я изо всех сил пытаюсь найти набор руководящих принципов, которые привели бы меня к тому, чтобы иметь поддерживаемые модульные тесты для GUI.

На данный момент я обнаружил, что тесты, говорящие «этот компонент должен где-то показывать свои входные данные», хороши (и это очень легко с HTML). Тесты, проверяющие определенное состояние определенной части компонента, обычно являются хрупкими. Тесты, идущие как щелчок-щелчок-щелчок-ожидание, которые пытаются следовать поведению пользователя и базовой бизнес-логике (которая является наиболее важной частью), обычно оказываются хрупкими. Как мне написать хорошие тесты?


Чтобы быть более точным, я хотел бы знать некоторые шаблоны о том, что я мог бы проверить в моем пользовательском интерфейсе, а не точно, как это проверить. Соглашения об именах и фиксированные идентификаторы хороши, но не решают основной проблемы, которая заключается в том, что GUI сильно меняются. Я хотел бы проверить поведение, которое вряд ли изменится. Как найти правильную вещь для тестирования?

mik01aj
источник
1
@MichaelDurrant: Вы редактировали вопрос о тестировании пользовательского интерфейса в целом, а я изначально спрашивал о модульном тестировании. Мне сложно поддерживать интеграционные тесты, и я предпочитаю модульные тесты им, когда это возможно.
mik01aj
2
Я думаю, что это является частью проблемы, хотя, по сути, вы не можете на самом деле протестировать какой-либо интерфейс с помощью модульного тестирования в одиночку, поскольку их смысл в том, чтобы взаимодействовать с чем-то. GUI ничем не отличаются в этом отношении.
JK.
M01, вы можете изменить его обратно. Я считаю, что тесты пользовательского интерфейса обычно являются интегрированными. Тесты, как правило, основаны на данных об исходных данных и приборах, а также на пользовательском интерфейсе, работающем с ними. Если у вас есть настоящие тесты пользовательского интерфейса, которые не полагаются на другие превосходные данные. Однако я обнаружил, что это относительно редко.
Майкл Даррант
2
связанные, но не дубликаты, так как речь идет о тестах графического интерфейса пользователя
k3b

Ответы:

3

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

В качестве примера рассмотрим тест в следующем виде:

Когда пользователь вводит «999» в поле номера телефона и нажимает кнопку «Сохранить», всплывающее сообщение об ошибке должно содержать «недопустимый номер телефона».

Здесь достаточно места для этого теста, чтобы сломаться, когда вы переделываете интерфейс, даже если требования для проверки остаются.

Теперь давайте поместим это в небольшую альтернативную формулировку:

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

Тест такой же, требования те же, но этот вид теста переживет модернизацию вашего пользовательского интерфейса. Очевидно, вам нужно будет изменить код, но код будет изолированным. Даже если у вас есть десять или двадцать таких тестов для страницы вашего профиля и вы переместите свою логику проверки, отображающую сообщения об ошибках, например, из javascript-alerts в jquery-popups, вам нужно только изменить одну часть теста, которая проверяет сообщения об ошибках.

JDT
источник
4

Это распространенная проблема. Я бы обратил внимание на:

  • Как вы называете элементы

    Используйте идентификатор или класс CSS для идентификации элементов. Избегайте использования CSS ID, когда объект уникален. Рассмотрим структуру, которую вы используете, например, в Ruby on Rails nameатрибут назначается автоматически и может (не интуитивно) быть лучше, чем использование идентификатора или класса css

  • Как вы определяете элементы.

    Избегайте позиционных идентификаторов, например, table/tr/td/tdв пользу форм, таких как td[id="main_vehicle"или td[class='alternates']. При необходимости рассмотрите возможность использования атрибутов данных. Еще лучше попытаться избежать тегов компоновки, таких как в <td>целом, поэтому для вышеперечисленного вы можете либо добавить диапазон и использовать его, например, <span id="main_vehicle">или селектор подстановочного знака, например, *[id="main_vehicle"]где *теперь может быть div, span, td и т. Д.

  • Использование специфичных для теста атрибутов данных , которые используются только для проверки и тестирования.

  • Избегайте ненужной квалификации для элементов. Вы можете использовать следующее:

    body.main div#vehicles > form#vehicle input#primary_vehicle_name

    Однако для этого необходимо, чтобы поле ввода оставалось в форме с точным идентификатором транспортного средства и на странице с телом, имеющим класс main, и div с идентификатором транспортных средств, который имеет непосредственный дочерний элемент формы с идентификатором средство передвижения. Любые изменения в любой из этой структуры и тест-паузы. В этом случае вы можете обнаружить, что

    input#primary_vehicle_name

    достаточно, чтобы однозначно идентифицировать элемент.

  • Избегайте тестов, которые ссылаются на видимый текст. Текст на странице, который отображается пользователю, обычно изменяется со временем, так как сайт поддерживается и обновляется, поэтому используйте идентификаторы, такие как css id и css class или атрибуты данных. Такие элементы, как form, inputи selectиспользуемые в формах также хорошие части идентифицирующих элементов, как правило , в сочетании с идентификатором или классом, например , li.vehicleили input#first-vehicle Вы можете также добавить свои собственные идентификаторы, например <div data-vehicle='dodge'>. Таким образом, вы можете избежать использования идентификаторов элементов или классов, которые могут быть изменены разработчиками и дизайнерами. Со временем я обнаружил, что лучше просто работать с разработчиками и дизайнерами и прийти к соглашению по поводу названий и областей применения. Это трудно.

  • Как сохраняются фиксированные данные.

    Подобно идентификации фактических элементов, старайтесь избегать встроенного жестко закодированного селектора, определяющего значения в пользу объектов страницы - небольших фрагментов текста, которые хранятся в переменных или методах и, таким образом, могут использоваться повторно и также поддерживаться централизованно. Примеры переменных JavaScript, следующих этому шаблону для жестко закодированных значений:

    storedVars["eqv_auto_year"] = "2015";
    storedVars["eqv_auto_make_1"] = "ALFA ROMEO";
    storedVars["eqv_auto_make_2"] = "HONDA";`  
    

    Больше на странице объектов в селене вики и селен документы

  • Общение с разработчиками.

    Независимо от технического подхода с точки зрения «разработчики вносят изменения и нарушают автоматизацию контроля качества», это проблема рабочего процесса. Вы должны убедиться, что: все - одна команда; разработчик запускает такие же интегрированные тесты; стандарты согласованы и соблюдаются обеими группами; определение выполненного включает в себя запуск и, возможно, обновление тестов пользовательского интерфейса; разработчики и тестировщик рассказывают о планах тестирования и посещают обработку заявок (если проводят Agile) и обсуждают тестирование пользовательского интерфейса как часть подготовки. Вы должны убедиться, что любой подход и стратегия, которую вы используете для именования, согласованы с разработчиками приложений. Если вы не попадаете на ту же страницу, вам понравится конфликт по именованию объектов. Некоторые примеры методов объекта страницы, которые я недавно создал для проекта ruby:

    def css_email_opt_in_true
      'auto_policy[email_opt_in][value=1]'
    end 
    
    def css_phone_opt_in
      '*[name="auto_policy[phone_opt_in]"]'
    end 
    
    def css_phone_opt_in_true
      'input[name=phone_opt_in][value=true]'
    end 
    
    def css_credit_rating
      'auto_policy[credit_rating]'
    end
    

    Вот те же объекты страницы, что и в переменных javascript:

    storedVars["css_email_opt_in"] = "css=*[name='auto_policy[email_opt_in]']";
    storedVars["css_phone_opt_in"]="css=*[name='auto_policy[phone_opt_in]']";
    storedVars["css_phone_opt_in_true"]="css=input[name='phone_opt_in'][value=true]";
    storedVars["css_credit_rating"]="css=select[name='auto_policy[credit_rating]']";
    
Майкл Даррант
источник
2
Это полезные советы, и я на самом деле уже следую большинству из них. Моя проблема заключалась в том, что я пишу тесты для какой-то кнопки, а затем эта кнопка удаляется, в то время как то же действие выполняется из другого места. Или кнопка остается там, но метка меняется, и кнопка выполняет также некоторые дополнительные действия.
mik01aj
1
Ах, это приятно знать. Да, для этого я бы сфокусировался на рабочих процессах и организационном взаимодействии с разработчиками
Майкл Даррант
1
Я также публикую информацию для тех, кто найдет ваш вопрос и, возможно, не будет иметь в своем распоряжении всех частей, которые вы имеете или даже не знаете о них.
Майкл Даррант
1
Я расширил свою последнюю точку зрения на основе ваших отзывов.
Майкл Даррант
1
И я обновил разделы об именовании и идентификации элементов и о том, как не использовать видимый текст.
Майкл Даррант
3

Причина, по которой люди сначала разрабатывали такие вещи, как MVC, MVP и Presenter, и подобные шаблоны проектирования, заключалась в том, чтобы отделить бизнес-логику от пользовательского интерфейса.

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

С другой стороны, тестирование бизнес-логики может быть выполнено в модульных тестах. И это ответ на ваш вопрос. Проверьте все в модели, и если вы можете и хотите, вы также можете проверить код контроллера.


GUI сильно меняются

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

BЈовић
источник
2
Я думаю, что это хороший момент. Может быть, я просто делаю MVC неправильно :) Кстати, я считаю, что изменение требований неизбежно, когда вы разрабатываете веб-платформу для большого количества пользователей. Вы не знаете, как будут вести себя ваши пользователи, пока они не начнут использовать графический интерфейс.
mik01aj
2

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

Для сравнения:

Модульный тест

Оригинал : validateEmail()должен выдать InvalidDataисключение. Что правильно отражено в вашем модульном тесте.

Изменение : validateEmail()должно выдать InvalidEmailисключение. Теперь ваш тест неверен, вы обновляете его, и все снова становится зеленым.

Тест GUI

Оригинал : при вводе неверного адреса электронной почты появится всплывающее окно с ошибкой, содержащее «Введены неверные данные». Правильно определяется вашими тестами.

Изменение : при вводе недействительного адреса электронной почты будет отображаться внутренняя ошибка, содержащая сообщение «Введен неверный адрес электронной почты». Теперь ваш тест неверен, вы обновляете его, и все снова становится зеленым.


Помните, что вы тестируете входы и выходы - какое-то четко определенное поведение. Независимо от того, является ли это тест GUI или модульный тест или интеграционный тест и т. Д.

Джесс Телфорд
источник