Model Listener или Модель обратных вызовов (callback) — это модель обработки событий, которая базируется на концепции слушателей. В двух словах, слушатель — это класс, который ничего не делает до момента пока не произойдет ожидаемое им событие, и только после того как событие произошло — выполняет заданные действия. Источник события, в свою очередь, содержит список слушателей и когда порождает событие, оповещает всех подписанных на это событие слушателей о том, что данное событие произошло посредством специальных методов.
Это модель отлично подойдет для любых целей обработки результатов тестов, где необходимо получать уведомления в контрольных точках выполнения теста: создание отчетов о выполнении, логирование или снятие скриншотов.
В JUnit4 для того, чтобы реализовать реакцию на определенные события во время выполнения теста (например, сделать скриншот, если тест завершился ошибкой), нужно унаследоваться от класса RunListener
и переопределить соответствующие методы.
import org.junit.internal.AssumptionViolatedException; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; public class SeleniumRunListener extends RunListener { /** * Вызывается перед запуском всех тестов. * @param description описание класса, который будет запущен */ @Override public void testRunStarted(Description description) throws Exception { System.out.println("Before tests run: " + description); } /** * Вызывается когда все тесты завершены * @param result результат выполнения тестов */ @Override public void testRunFinished(Result result) throws Exception { System.out.println("Result of the test run:"); System.out.println("Run time: " + result.getRunTime() + " ms"); System.out.println("Run count: " + result.getRunCount()); System.out.println("Failure count: " + result.getFailureCount()); System.out.println("Ignored count: " + result.getIgnoreCount()); } /** * Вызывается перед запуском каждого теста. * @param description описание теста, который собирается запуститься * (обычно имя класса и метода) */ @Override public void testStarted(Description description) throws Exception { System.out.println("Test starts: " + description); } /** * Вызывается после завершения каждого теста, * несмотря на результат выполнения. * @param description описание теста, который завершился */ @Override public void testFinished(Description description) throws Exception { System.out.println("Test finished: " + description); System.out.println("--------------------------------------"); } /** * Вызывается когда тест завершается неудачей. * @param failure описывает тест, который завершился ошибкой * и полученное исключение. */ @Override public void testFailure(Failure failure) throws Exception { System.out.println("Test failed with: " + failure.getException()); } /** * Вызывается когда не выполняется условие в классе Assume * * @param failure описывает тест, который не был выполнен * и исключение {@link AssumptionViolatedException} */ @Override public void testAssumptionFailure(Failure failure) { System.out.println("Test assumes: " + failure.getException()); } /** * Вызывается когда тест не будет запущен, * в основном потому что стоит аннотация @Ignore * * @param description описание теста который не будет запущен */ @Override public void testIgnored(Description description) throws Exception { System.out.println("Test ignored: " + description); System.out.println("--------------------------------------"); } }
Для того, чтобы JUnit4 стал использовать Ваш listener необходимо установить его с помощью класса Runner
import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.InitializationError; public class SeleniumRunner extends BlockJUnit4ClassRunner { private SeleniumRunListener seleniumRunListener; public SeleniumRunner(Class klass) throws InitializationError { super(klass); seleniumRunListener = new SeleniumRunListener(); } public void run(final RunNotifier notifier) { notifier.addListener(seleniumRunListener); super.run(notifier); } }
и затем указать переопределенный runner в суперклассе тестов с помощью аннотации @RunWith:
import static org.junit.Assert.*; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; @RunWith(SeleniumRunner.class) public class Example { public WebDriver driver; /** * Вызывается перед запуском каждого теста. * Инициализируем здесь драйвер. */ @Before public void setUp(){ driver = new FirefoxDriver(); } /** * Тест, который должен выполниться успешно */ @Test public void testSuccess1Expected() { driver.get("http://internetka.in.ua"); System.out.println("Page title: " + driver.getTitle()); } /** * Останавливаем выполнение теста, * потому что url не пройдет проверку */ @Test public void testAssumptionFailedExpected() { driver.get("http://internetka.in.ua"); Assume.assumeTrue(driver.getCurrentUrl() .equals("http://www.google.com")); // other code } /** * Тест должен завершиться с ошибкой, * получив {@link NoSuchElementException} */ @Test public void testFailedExpected() { driver.get("http://internetka.in.ua"); driver.findElement(By.id("some-not-exist-id")); } /** * Тест не будет запущен */ @Ignore("Test ignored by user") @Test public void ignoredTestMethod() { assertTrue("ignored test method is executed", false); } /** * Вызывается после выполнения каждого теста. * Завершаем здесь работу драйвера */ @After public void tearDown(){ driver.quit(); } }
В итоге при запуске теста в консоль вызовется следующее:
Test starts: testSuccess1Expected(test.Example) Page title: Блог вебразработчика - Функциональное тестирование и продвижение сайтов Test finished: testSuccess1Expected(test.Example) -------------------------------------- Test starts: testFailedExpected(test.Example) Test failed with: org.openqa.selenium.NoSuchElementException: Unable to locate element: {"method":"id","selector":"some-not-exist-id"} Command duration or timeout: 18 milliseconds For documentation on this error, please visit: http://seleniumhq.org/exceptions /no_such_element.html Build info: version: '2.24.1', revision: '17205', time: '2012-06-19 15:28:49' System info: os.name: 'Windows 7', os.arch: 'x86', os.version: '6.1', java.version: '1.7.0_05' Driver info: driver.version: RemoteWebDriver Session ID: cb46034e-7fe0-47ab-93f6-be1d71e4da3d Test finished: testFailedExpected(test.Example) -------------------------------------- Test ignored: ignoredTestMethod(test.Example) ----------------------------------------- Test starts: testAssumptionFailedExpected(test.Example) Test assumes: org.junit.internal.AssumptionViolatedException: got: <false>, expected: is <true> Test finished: testAssumptionFailedExpected(test.Example) -------------------------------------- Result of the test run: Run time: 55556 ms Run count: 3 Failure count: 1
P.S.
Если тесты запускаются с помощью JUnitCore
, можно просто зарегистрировать SeleniumRunListener
в JUnitCore
не создавая дополнительных классов:
public void main(String... args) { JUnitCore core= new JUnitCore(); core.addListener(new SeleniumRunListener()); core.run(Example.class); }
hey what is your fb page
I don’t think it’s a good idea to post it here. If you would like to contact me, please leave your real email address with a comment (it’ll be not visible for others) and I’ll send you a message.
Обратил внимание на одну вещь в вашем примере (в коде SeleniumRunner) :
В случае, если мы используем аннотацию @RunWith сразу для нескольких классов, то при прогоне тестов количество listener’ов будет увеличиваться пропорционально количеству классов (при выполнении инструкции notifier.addListener(seleniumRunListener); ) Я для себя решил эту проблему, сделав поле seleniumRunListener статическим (и убрав его из конструктора), а также добавил в код метода run(RunNotifier notifier) инструкцию notifier.removeListener(seleniumListener); Получилось так:
P.S. Спасибо за интересный блог!
Спасибо за интересный комментарий)))
Я бы только изменила немного метод run, чтобы не вызывать заведомо Exception:
Что касается статического seleniumRunListener, то будьте осторожны при организации одновременного запуска нескольких тестов (для этого скорее всего его придется все же сделать объектом внутри класса).
Добрый день! Могли бы вы подсказать, как можно подключить runner (@RunWith(SeleniumRunner.class)), к параметризованному тесту (@RunWith(Parameterized.class)) ?
Добрый день, одновременно использовать два раннера для теста нельзя, поэтому вижу два возможных решения:
1. Написать собственный runner, в котором будет реализован одновременно весь требуемый функционал (в данном случае лучшее решение).
2. Если возможно, то функционал одного из раннеров вынести, например, в базовый класс тестов.
Спасибо!