Подтвердить что ты не робот

Selenium + JUnit: порядок испытаний/поток?

Я использую Selenium для тестирования html-страниц java-веб-приложений (на самом деле JSP). Для моего веб-приложения требуется поток для доступа к каждой странице (это небольшое веб-приложение для онлайн-игр), например: чтобы перейти на страницу B, вам нужно перейти на страницу A, ввести текст и нажать кнопку, чтобы перейти на страницу B. Очевидно, что у меня уже есть некоторые тесты для проверки правильности работы страницы A.

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

После многих чтений о тестировании за последние несколько дней я не могу найти ничего интересного по этому конкретному вопросу. Поэтому я прошу совета сейчас.

Возможные решения, которые я определил:

  • Определите (в том же тестовом классе) методы тестирования для страницы A, затем проверьте методы тестирования B. Затем закажите выполнение методов тестирования. Но мы знаем, что JUnit (но TestNG) не позволяет упорядочивать выполнение тестовых методов, см. SO question selenium-junit-tests-how-do-i-run-tests-in-a- тест-в-последовательного порядка

  • Группировка всех тестов (для страницы A, стр. B и т.д.) по одному методу тестирования. Но я читал это плохо, см. Вопрос SO: junit-one-test-case-per-method-or-multiple-test-cases-per-method. Неужели это плохо при проведении теста на селен? Я видел, как какой-то код делает это, поэтому я предполагаю, что это может быть не так.

  • Группировка всех тестов (для страницы A, страница B и т.д.) по одному методу тестирования, но используйте класс JUnit ErrorCollector: ErrorCollector позволяет выполнять упорядоченные проверки одним и тем же методом и дает конкретное сообщение об ошибке, если оно не выполняется, но пусть метод (следовательно, проверки) работает до конца. Это решение кажется мне слишком "жестоким".

  • Использовать класс JUnit TestSuite: он запускает тест, указанный в пакете в классах тестирования заказа, определен в пакете. Таким образом, это предполагает наличие независимых тестовых методов для проверки страницы A в тестовом классе (скажем, TestA), затем все тестовые методы для проверки страницы B в тестовом классе (скажем, TestB) и т.д. Затем вставьте те, которые содержатся в наборе тестов, например @SuiteClasses ({TestA.class, TestB.class, TestC.class,...})

  • Используйте класс JUnit TestSuite в сочетании с классом JUnit ErrorCollector. Ну, так как мы можем, вы можете захотеть Групповой тест на страницу в разных классах, а поверх этой групповой страницы проверяет "зоны" с помощью ErrorCollector. Это решение может быть очень полезно, если у вас очень плотная веб-страница или другие причины.

  • Достаточно радикальный: используйте другой инструмент, например TestNG, чтобы иметь доступ к таким функциям, как упорядочение методов тестирования.

Примечание. Я предполагаю, что некоторые из них порекомендуют последнее решение (перенести на TestNG), но я хотел бы услышать другие идеи/мнения, связанные с JUnit. Как и в случае, если я работаю в команде, которая неспособна (по какой-то причине) перейти на другую структуру тестирования, то как бы они справились с этой проблемой при тестировании?

4b9b3361

Ответ 1

Зачем мигрировать? Вы можете использовать JUnit для модульного тестирования и другую инфраструктуру для тестирования более высокого уровня. В вашем случае это своего рода принятие или функциональность или сквозной, не так важно, как вы его называете. Но важно понимать, что эти тесты не являются единицами. Они придерживаются разных правил: они сложнее, работают дольше и реже, они требуют сложной настройки, внешних зависимостей и могут спорадически терпеть неудачу. Почему бы им не использовать другую инфраструктуру (или даже другой язык программирования)?

Возможные варианты:

Если добавить еще одну фреймворк, это не вариант: вы указали больше параметров для JUnit, я мог бы представить =) Я бы поставил весь тест script для потока в одном тестовом методе и организовал бы тестовый код в "Драйверы". Это означает, что ваши сквозные тесты не вызывают методы вашего приложения или Selenium API напрямую, а переносят их в методы компонентов драйвера, которые скрывают сложность API и выглядят как заявления о том, что происходит или что ожидается. Посмотрите на пример:

@Test 
public void sniperWinsAnAuctionByBiddingHigher() throws Exception {
    auction.startSellingItem();

    application.startBiddingIn(auction);
    auction.hasReceivedJoinRequestFrom(ApplicationRunner.SNIPER_XMPP_ID);

    auction.reportPrice(1000, 98, "other bidder");
    application.hasShownSniperIsBidding(auction, 1000, 1098);

    auction.hasReceivedBid(1098, ApplicationRunner.SNIPER_XMPP_ID);

    auction.reportPrice(1098, 97, ApplicationRunner.SNIPER_XMPP_ID);
    application.hasShownSniperIsWinning(auction, 1098);

    auction.announceClosed();
    application.hasShownSniperHasWonAuction(auction, 1098);
} 

Отрывок из "Растущее объектно-ориентированное программное обеспечение, управляемое тестами. Книга действительно замечательная, и я очень рекомендую ее прочитать.

Это реальный сквозной тест, в котором используется реальное соединение XMPP, сервер java-трафика Openfire и инфраструктура GUI-тестирования WindowLicker Swing. Но все это, если выгрузили компоненты драйвера. И в вашем тесте вы просто видите, как разные актеры общаются. И он упорядочен: после того, как приложение начало торгов, мы проверяем, что сервер аукциона получил запрос на соединение, затем мы даем команду серверу аукционов сообщать о новой цене и проверять, что это отражено в пользовательском интерфейсе и так далее. Весь код доступен на github.

Пример в github сложный, потому что приложение не так тривиально, как это обычно бывает с примерами книг. Но эта книга дает его постепенно, и я смог построить все приложение с нуля после руководства книги. Фактически, это единственная книга, которую я когда-либо читал о TDD и автоматическом тестировании разработчиков, которая дает такой полный и полный пример. И я читал их довольно много. Но обратите внимание, что подход Driver не делает ваш блок тестов. Это просто позволяет вам скрыть сложность. И он может (и должен) использоваться с другими фреймами. Они просто дают вам дополнительные возможности для разделения ваших тестов на последовательные шаги, если вам нужно; написать читаемые пользователем тестовые примеры; для экстернализации тестовых данных в CSV, таблицах Excel, XML файлах или базе данных, чтобы время ожидания ваших тестов; для интеграции с внешними системами, сервлетом и контейнерами DI; определять и запуска отдельно тестовых групп; для предоставления более удобных отчетов и т.д.

И о создании всей вашей тестовой единицы. Это невозможно для чего-либо, кроме чего-то вроде библиотек утилиты для математики, обработки строк и т.д. Если у вас есть приложение, полностью протестированное на модуле, это означает, что вы проверяете не все приложения, или вы не понимаете, какие тесты являются единицами, а какие нет. Первый случай может быть в порядке, но все, что не охвачено, должно быть проверено и проверено вручную разработчиками, тестерами, пользователями или кем бы то ни было. Это довольно часто, но лучше быть сознательным решением, а не случайным. Почему вы не можете unit test все?

Существует множество определений модульных тестов, и это приводит к священной войне). Я предпочитаю следующее: "Unit test - это тест для единицы программы в изоляции". Некоторые люди говорят: "Эй, единица - мое приложение! Я тестирую логин и это простая функция блока". Но есть и прагматика, которая скрывается в изоляции. Почему нам нужно отличать модульные тесты от других? Потому что это наша первая защитная сетка. Они должны быть быстрыми. Вы часто выполняете (например, git), и вы запускаете их, по крайней мере, перед каждой фиксацией. Но представьте себе, что для "единичных" тестов требуется 5 минут. Вы будете либо запускать их реже, либо вы будете совершать реже, или вы будете запускать только один тестовый пример или даже один тестовый метод в то время, или вы будете ожидать, что каждые 2 минуты для завершения тестов за 5 минут. Через 5 минут вы отправитесь в Coding Horror, где вы проведете следующие 2 часа =) И модульные тесты никогда не должны прерываться спорадически. Если они это сделают - вы им не доверяете. Следовательно, изоляция: вы должны изолировать медлительность и источники спорадических сбоев от ваших модульных тестов. Следовательно, изоляция означает, что модульные тесты не должны использоваться:

  • Файловая система
  • Сеть, сокеты, RMI и т.д.
  • GUI
  • Многопоточность
  • Внешние библиотеки ожидают тестовую структуру и поддерживают простые библиотеки, такие как Hamcrest

И модульные тесты должны быть локальными. Вы хотите, чтобы один или несколько тестов терпели неудачу, когда вы сделали дефект в течение 2 минут после кодирования, а не половину всего пакета. Это означает, что вы очень ограничены в тестировании поведения с учетом состояния в модульных тестах. Вы не должны устанавливать тестовую настройку, которая делает 5 состояний переходами для достижения предварительных условий. Поскольку сбой в первом переходе приведет к по меньшей мере 4 тестам для следующих переходов и еще одному тесту, который вы сейчас пишете для 6-го перехода. И любое нетривиальное приложение имеет довольно много потоков и состояний в нем. Таким образом, это не может быть проверено на модуле. По той же причине модульные тесты не должны использовать сменное разделяемое состояние в базе данных, статических полях, контексте Spring или любом другом. Именно по этой причине JUnit создает новый экземпляр тестового класса для каждого тестового метода.

Итак, вы видите, что вы не можете полностью unit test веб-приложение, независимо от того, как вы его перекодируете. Потому что у него есть потоки, JSP, контейнер сервлетов и, вероятно, больше. Конечно, вы можете просто игнорировать это определение, но это чертовски полезно). Если вы согласны с тем, что отличительные модульные тесты из других тестов полезны, и это определение помогает достичь этого, тогда вы пойдете на другую структуру или по крайней мере другой подход для тесты, которые не являются единицами, вы создадите отдельные сюиты для отдельных видов тестов и так далее.

Надеюсь, это поможет)

Ответ 2

Вы можете реализовать свой собственный Runner, который, возможно, перенесет другие теги Runner и сортирует в соответствии с определенными критериями yo9u. Это если вам это действительно нужно.

Но я не думаю, что это так необходимо. Я понимаю, что если страница А не работает, то пропуск в B тоже не будет работать. Итак, сначала вы хотите запустить тест A, а затем запустить тест A- > B. Но действительно ли это имеет смысл? Если, например, тест A- > B запускается первым и терпит неудачу, потому что он не может прибыть на страницу. Другой тест, который проверяет, что тест A тоже сбой. Таким образом, оба теста потерпят неудачу, и идентификатор не зависит от порядка тестирования.

Если, однако, вы имеете в виду, что хотите использовать тест A в качестве настроенной операции теста B, это очень плохая практика. Вы можете использовать логику теста A как начало теста B, но вы не должны сочетаться между двумя тестами. Одна очевидная причина заключается в том, что это очень сложно отлаживать. Для отладки теста A- > B вам необходимо выполнить тесты A и A- > B, что означает, что вам, вероятно, придется выполнять все тесты (по крайней мере, в течение одного тестового примера).

Ответ 3

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

Пример страницы 1, введите 2 ввода, нажмите "Enter", проверьте страницу 2, загруженную чем-то.

Затем вы получите это в файле функций:

Given Page 1
   Then I enter text1
   Then I enter text2
   Then I click button
   Then I see page 2

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

...
@Given("^Page 1$")
public void iLoadPage1() {
  WebDriver driver = new ....
  driver.go('URL');
}

@Given("I enter (.*)$")
public void iEnterTest(String txt) {
  ...
}

...

Ответ 4

JUnit не предназначен для тестирования уровня потока/интеграции.

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

(Да, это плохая практика для модульных тестов, но мы не говорим об модульных тестах там, даже если они работают с jUnit).

Использование другого инструмента (огурец, фитнес, TestNG, что угодно) также является хорошим решением, но в проекте есть слишком много инструментов для тестирования.

Ответ 5

Другим вариантом, который может работать, является применение параметра JUnit Parameterization для вашего теста. Мое настоящее понимание того, что параметры для реализации всегда выполняются в том порядке, в котором они предоставляются.

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

Чтобы убедиться, что вы используете одну и ту же ссылку WebDriver, вероятно, это должно быть статическое объявление @BeforeClass/@AfterClass. С этим вы можете иметь параметры цепи друг от друга, эффективно тестируя, что "Из предыдущего теста я нахожусь на странице X. Пока здесь я выполню задачу Y. В конце этого теста я буду на странице Z, или в состоянии А".

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

Мне было любопытно, поэтому я попробовал. Он действует так, как я думал, что будет, если мы предположим, что мы можем рассматривать приложение как статический ресурс по отношению к тесту.

package demo.testing;

import java.util.List;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;

@RunWith(Parameterized.class)
public class SequentialParams {

    private static SystemState state;

    @BeforeClass
    public static void validateBeforeState() {
        state = new SystemState();

        Assert.assertFalse(state.one);
        Assert.assertFalse(state.two);
        Assert.assertFalse(state.three);
        Assert.assertFalse(state.four);
    }

    @Parameters
    public static Object buildParameters() {
        Runnable reset = new Runnable() {

            public void run() {
                state.one = false;
                state.two = false;
                state.three = false;
                state.four = false;
            }
        };
        Runnable oneToTrue = new Runnable() {

            public void run() {
                state.one = true;
            }
        };
        Runnable twoToTrue = new Runnable() {

            public void run() {
                state.two = true;
            }
        };
        Runnable threeToTrue = new Runnable() {

            public void run() {
                state.three = true;
            }
        };
        Runnable fourToTrue = new Runnable() {

            public void run() {
                state.four = true;

            }
        };


        Predicate<SystemState> oneIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.one;
            }
        };
        Predicate<SystemState> twoIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.two;
            }
        };
        Predicate<SystemState> threeIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.three;
            }
        };
        Predicate<SystemState> fourIsTrue = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return input.four;
            }
        };

        Predicate<SystemState> oneIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.one;
            }
        };
        Predicate<SystemState> twoIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.two;
            }
        };
        Predicate<SystemState> threeIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.three;
            }
        };
        Predicate<SystemState> fourIsFalse = new Predicate<SequentialParams.SystemState>() {
            public boolean apply(SystemState input) {
                return !input.four;
            }
        };
        List<Object[]> params = Lists.newArrayList();

        params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse), oneToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsFalse, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsFalse, fourIsFalse), twoToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsFalse, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsTrue, threeIsFalse, fourIsFalse), threeToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsFalse), fourToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue)});

        params.add(new Object[]{ Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue), reset, Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse)});

        params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse), threeToTrue, Predicates.and(oneIsFalse, twoIsFalse, threeIsTrue, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsTrue, fourIsFalse), oneToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsFalse)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsFalse), fourToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsTrue)});
        params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsTrue), twoToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue)});


        return params;
    }

    Predicate<SystemState> verifyStartState;
    Runnable changeState;
    Predicate<SystemState> verifyEndState;

    public SequentialParams(Predicate<SystemState> pre, Runnable task, Predicate<SystemState> post) {
      verifyStartState = pre;
      changeState = task;
      verifyEndState = post;
    }

    @Test
    public void perform() {
        Assert.assertTrue(verifyStartState.apply(state));
       changeState.run();
       Assert.assertTrue(verifyEndState.apply(state));
    }


    private static class SystemState {
        public boolean one = false;
        public boolean two = false;
        public boolean three = false;
        public boolean four = false;

    }

}