Статьи

Интеграция огурца с игрой

Приложение по умолчанию, сгенерированное Play, имеет примеры модульных и интеграционных тестов, однако оно не демонстрирует, как интегрироваться в какие-либо платформы Behavior Driven Development (BDD). Ценность структуры BDD, такой как Cucumber , чрезвычайно высока при попытке добиться тесного сотрудничества между различными заинтересованными сторонами, такими как разработчики и бизнес-аналитики. Этот пост покажет вам, как начать работать с Cucumber and Play.

  1. Предполагая, что вы уже настроили приложение Vanilla Play, первое, что вам нужно сделать, это добавить зависимости для Cucumber. Перейдите к файлу build.sbt и добавьте зависимости для Cucumber. В этом посте мы будем использовать cucumber-jvm вместе с средством запуска cucumber-junit .

    name := "sample-with-cucumber"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache
      "info.cukes" % "cucumber-java" % "1.1.5" % "test",
      "info.cukes" % "cucumber-junit" % "1.1.5" % "test"
     )     
    
    play.Project.playJavaSettings
    
    
  2. Давайте создадим каталог, который будет содержать наши файлы функций. Я обычно помещаю свои функции в каталог функций в корне моего приложения.

    $ mkdir features
    
  3. Чтобы этот каталог был выбран, нам нужно добавить его в путь к классам при запуске тестов. Это изменение, которое мы должны внести в файл build.sbt .

    name := "sample-with-cucumber"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache
      "info.cukes" % "cucumber-java" % "1.1.5" % "test",
      "info.cukes" % "cucumber-junit" % "1.1.5" % "test"
     )     
    
    play.Project.playJavaSettings
    
    unmanagedResourceDirectories in Test <+= baseDirectory( _ / "features" )
    
  4. Для простоты давайте создадим очень простой файл функций. Все, что нужно сделать, это перейти на целевую страницу и заявить, что заголовок страницы равен тексту «Огурец».

    Создайте файл features / example.feature, содержащий следующее содержимое.

    Feature: Testing Cucumber Integration
    
      Scenario: Cucumber Integration
        Given I have setup Play
        When I go to the landing page
        Then the title should be "Cucumber"
    
  5. Чтобы запустить это, нам нужен класс, аннотированный аннотацией бегуна Cucumber JUnit. Поэтому создайте класс с именем test / RunCucumber.java со следующим содержимым. Флаг «pretty» не обязателен, но я предпочитаю этот вывод.  

    import cucumber.api.junit.Cucumber;
    import org.junit.runner.RunWith;
    
    @RunWith(Cucumber.class)
    @Cucumber.Options(format = {"pretty"})
    public class RunCucumber {
    }
    
  6. Now let’s checkpoint what we have done so far. If everything is working, we should be able to run Cucumber and it should complain that we are missing step definitions.

    $ play test
    
    ...
    You can implement missing steps with the snippets below:
    
    @Given("^I have setup Play$")
    public void I_have_setup_Play() throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
    
    @When("^I go to the landing page$")
    public void I_go_to_the_landing_page() throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
    
    @Then("^the title should be \"([^\"]*)\"$")
    public void the_title_should_be(String arg1) throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
    
    
  7. To get the scenario to pass, we need to first put a startup hook in place to initialize our server and test browser. Cucumber does provide a @Before hook but this is executed before each and every scenario. We only want to initialize our server and browser once before the very first scenario, so we need to add some state to manage this ourselves.

    Create a test/GlobalHooks.java class and add the following contents.

    import cucumber.api.java.Before;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class GlobalHooks {
        public static int PORT = 3333;
        public static TestBrowser TEST_BROWSER;
        private static TestServer TEST_SERVER;
        private static boolean initialised = false;
    
        @Before
        public void before() {
            if (!initialised) {
                TEST_SERVER = testServer(PORT, fakeApplication(inMemoryDatabase()));
                TEST_BROWSER = testBrowser(HTMLUNIT, PORT);
                start(TEST_SERVER);
                initialised = true;
            }
        }
    }
    

    There’s also no shutdown hook in Cucumber, so we need to add the clean up of the server and browser into the JVM shutdown hook.

    import cucumber.api.java.Before;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class GlobalHooks {
        public static int PORT = 3333;
        public static TestBrowser TEST_BROWSER;
        private static TestServer TEST_SERVER;
        private static boolean initialised = false;
    
        @Before
        public void before() {
            if (!initialised) {
                TEST_SERVER = testServer(PORT, fakeApplication(inMemoryDatabase()));
                TEST_BROWSER = testBrowser(HTMLUNIT, PORT);
                start(TEST_SERVER);
                initialised = true;
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        TEST_BROWSER.quit();
                        TEST_SERVER.stop();
                    }
                });
            }
        }
    }
    
  8. Now we should be able to implement our steps. So create a test/Steps.java file. For the moment, we will just statically reference our TEST_BROWSER. There is a better way to inject this dependency in, which I will cover later.

    import cucumber.api.java.en.Given;
    import cucumber.api.java.en.Then;
    import cucumber.api.java.en.When;
    import static org.fest.assertions.Assertions.assertThat;
    
    public class Steps {
        @Given("^I have setup Play$")
        public void I_have_setup_Play() throws Throwable {
        }
    
        @When("^I go to the landing page$")
        public void I_go_to_the_landing_page() throws Throwable {
            GlobalHooks.TEST_BROWSER.goTo("http://localhost:" + GlobalHooks.PORT);
        }
    
        @Then("^the title should be \"([^\"]*)\"$")
        public void the_title_should_be(String title) throws Throwable {
            assertThat(GlobalHooks.TEST_BROWSER.title()).isEqualTo(title);
        }
    }
    
  9. We should now be in a position to rerun this. The steps should be found however the title of the page will not be «Cucumber» resulting in a failure.

    $ play test
    
    ...
    0m[error] Test Scenario: Cucumber Integration failed: expected:<'[Cucumber]'> but was:<'[Welcome to Play]'>
    ...
    
  10. Let’s correct the title of the page and rerun the test.

    Navigate to app/views/index.scala.html. Update the title of the page to be «Cucumber»

    @(message: String)
    
    @main("Cucumber") {
        @play20.welcome(message, style = "Java")
    }
    
  11. Rerun the test and it should succeed.

    1 Scenarios (1 passed)
    3 Steps (3 passed)
    0m3.544s
    
  12. The last series of changes will be around integrating with Guice so we can inject our dependencies into the Steps. This will remove the need to directly reference the test browser.

    Go back into the build.sbt file and add the dependencies for guice as well as cucumber-guice, which provides the binding for guice into cucumber.

    name := "sample-with-cucumber"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache,
      "com.google.inject" % "guice" % "3.0" % "test",
      "info.cukes" % "cucumber-guice" % "1.1.5" % "test",
      "info.cukes" % "cucumber-java" % "1.1.5" % "test",
      "info.cukes" % "cucumber-junit" % "1.1.5" % "test"
     )     
    
    play.Project.playJavaSettings
    
    unmanagedResourceDirectories in Test <+= baseDirectory( _ / "features" )
    
  13. Define a test/CucumberModule.java class which will setup our bindings.

    import com.google.inject.AbstractModule;
    import com.google.inject.name.Names;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class CucumberModule extends AbstractModule {
        private static int PORT = 3333;
        private TestServer testServer = testServer(PORT, fakeApplication(inMemoryDatabase()));
        private TestBrowser testBrowser = testBrowser(HTMLUNIT, PORT);
    
        @Override
        protected void configure() {
            bind(TestBrowser.class).toInstance(testBrowser);
            bind(TestServer.class).toInstance(testServer);
            bind(Integer.class).annotatedWith(Names.named("PORT")).toInstance(PORT);
        }
    }
    

    Now change test/GlobalHooks.java to be dependency injected.

    import com.google.inject.Inject;
    import cucumber.api.java.Before;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class GlobalHooks {
        @Inject
        private TestBrowser testBrowser;
        @Inject
        private TestServer testServer;
        private static boolean initialised = false;
    
        @Before
        public void before() {
            if (!initialised) {
                start(testServer);
                initialised = true;
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        testBrowser.quit();
                        testServer.stop();
                    }
                });
            }
        }
    }
    

    Change test/Steps.java to be dependency injected.

    import com.google.inject.Inject;
    import com.google.inject.name.Named;
    import cucumber.api.java.en.Given;
    import cucumber.api.java.en.Then;
    import cucumber.api.java.en.When;
    import play.test.TestBrowser;
    import static org.fest.assertions.Assertions.assertThat;
    
    public class Steps {
        @Inject
        private TestBrowser testBrowser;
    
        @Inject
        @Named("PORT")
        private Integer port;
    
        @Given("^I have setup Play$")
        public void I_have_setup_Play() throws Throwable {
        }
    
        @When("^I go to the landing page$")
        public void I_go_to_the_landing_page() throws Throwable {
            testBrowser.goTo("http://localhost:" + port);
        }
    
        @Then("^the title should be \"([^\"]*)\"$")
        public void the_title_should_be(String title) throws Throwable {
            assertThat(testBrowser.title()).isEqualTo(title);
        }
    }
    
  14. Lastly add a features.cucumber-guice.properties file to refer to our module. So this file should have the following contents.

    guiceModule=CucumberModule
    
  15. Now if we rerun everything, then the test should still pass which will prove that everything has been setup correctly.

    $ play test
    
    Feature: Testing Cucumber Integration
    
      Scenario: Cucumber Integration        # example.feature:3
        Given I have setup Play             # Steps.I_have_setup_Play()
        When I go to the landing page       # Steps.I_go_to_the_landing_page()
        Then the title should be "Cucumber" # Steps.the_title_should_be(String)
    
    1 Scenarios (1 passed)
    3 Steps (3 passed)
    0m2.092s
    

So this posting has taken you through the steps required to integrate Cucumber and Play. It also has shown you how to integrate Guice into your Cucumber Steps to keep your code cleaner. An example of this sample application can be found here.