This article introduces the method of integrating spring boot into Cucumber (BDD) and shares it with you. The details are as follows:
1. Create a new springboot project structure as follows:
2. Add pom dependencies
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.chhliu</groupId> <artifactId>spring-boot-cucumber</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-cucumber</name> <description>Demo project for Spring Boot and Cucumber</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <cucumber.version>1.2.4</cucumber.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.7</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>${cucumber.version}</version> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-core</artifactId> <version>${cucumber.version}</version> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-spring</artifactId> <version>${cucumber.version}</version> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>${cucumber.version}</version> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> <executions> <execution> <phase>integration-test</phase> <goals> <goal>java</goal> </goals> <configuration> <classpathScope>test</classpathScope> <mainClass>com.chhliu.test.CucumberTest.java</mainClass> <arguments> <argument>--plugin</argument> <argument>pretty</argument> <argument>--glue</argument> <argument>src/test/resources/</argument> </arguments> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>2. Write service interface and implementation classes
package com.chhliu.service; /** * Simulation login* @author chhliu * */ public interface UserInfoServiceI { boolean login(String username, String password, String confirmPassword); } package com.chhliu.service; import org.springframework.stereotype.Service; @Service("userInfoService") public class UserInfoService implements UserInfoServiceI{ public boolean login(String username, String password, String confirmPassword){ return (username.equals("chhliu") && password.equals("123456") && confirmPassword.equals("123456")); } } 3. Write feature files
#language: zh-CN #"zh-CN": { # "but": "*|But<", # "and": "*|and<|and<|and<|at the same time<", # "then": "*|So<", # "when": "*|When<", # "name": "Chinese simplified", # "native": "Simplified Chinese", # "feature": "Function", # "background": "background", # "scenario": "Scenario_outline": "Scenario Outline|Script Outline", # "examples": "Examples", # "given": "*|If <|If <|If<|If<|If<|If<" # } @bank Function: If I withdraw money at the bank, if I log in successfully and the password entered is correct, then my bank card balance will be displayed. If the balance is 500,000, scenario: Bank withdraw money If: I log in with "chhliu" and the password entered is "123456" When: Confirm password is also "123456", then: The bank card balance will be "500,000" 4. Write test classes
package com.chhliu.test; import org.junit.runner.RunWith; import cucumber.api.CucumberOptions; import cucumber.api.junit.Cucumber; /** * @RunWith(Cucumber.class) This is a runner, which refers to using Cucumber to run tests* @CucumberOptions, which specifies the directory of the feature to be run in our project* @CucumberOptions format, which specifies the report to be generated when we run in our project, and then specifies that the corresponding test report can be found in the target directory* @CucumberOptions glue, used to specify the directory that finds the step definition file when the project runs * * In actual projects, as the project progresses, a test project may consist of multiple feature files, and each feature file may also consist of multiple scenarios. By default, * Each run is to run all scenarios in all features. This may cause the test script to run once under normal circumstances, which takes a very long time to wait for the test results. * However, in actual process, test cases are distinguished by priority. For example, smokeTest, regressionTest, etc. Or sometimes there are particularly small use cases, such as the level is critical, * These use cases require long-term operation to monitor whether the system has no white pages or pages 404. * So we have to distinguish all scenarios, so that when we start the test script, we can run the scenero of which modules we need. At this time, we can use Tags * In Cucumber, Tag directly adds any number of tags with prefixed @ to feature or scenario before the Feature, Scenari or Scenario Outline keywords, and multiple tags are separated by spaces* @author chhliu * */ @RunWith(Cucumber.class) @CucumberOptions(plugin = {"json:target/cucumber.json", "pretty"}, features = "src/test/resources") public class CucumberTest { } 5. Run the test class and improve the undefined steps of the test output
package com.chhliu.test; import javax.annotation.Resource; import org.junit.Assert; import com.chhliu.service.UserInfoServiceI; import cucumber.api.java.zh_cn.If; import cucumber.api.java.zh_cn.When; import cucumber.api.java.zh_cn.So; public class Cucumber integration spring { @Resource(name="userInfoService") private UserInfoServiceI service; private String username; private String password; private String confirmPassword; @If ("^:I log in $ with /"([^/"]*)/"$") public void I log in $ with _(String arg1) throws Throwable { this.username = arg1; } @If ("^: The password entered is /"([^/"]*)/"$") public void The password entered is (String arg1) throws Throwable { this.password = arg1; } @When ("^: The password is also /"([^/"]*)/"$") public void Confirm password is also _(String arg1) throws Throwable { this.confirmPassword = arg1; } @Then ("^:Show the bank card balance is /"([^/"]*)/"$") public void Shows the bank card balance is (String arg1) throws Throwable { boolean isLogin = service.login(username, password, confirmPassword); if(isLogin){ System.out.println("Login successfully! The query balance is as follows: "+arg1); Assert.assertEquals("500000", arg1); } } } 6. Add annotation support to the test steps
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration // If this annotation is not added, the bean will be injected and cannot be injected @SpringBootTest // If this annotation is not added, the bean will not be found public class Cucumber integration spring{ } 7. Test results
2 Scenarios (2 passed)
11 Steps (11 passed)
0m0.091s
8. Integration points
When integrating spring boot with Cucumber, there is a point that needs to be paid attention to, because spring boot advocates de-xmlization, so in the traditional way, Cucumber will read the cucumber.xml configuration file under classpath to initialize the bean. After integrating with spring, this method cannot be used. You need to use the @ContextConfiguration annotation to implement class loading. If the configuration file needs to be loaded, you can use it as follows:
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })If you use annotations to integrate, use the following:
@ContextConfiguration(classes=SpringBootCucumberApplication.class)
Or directly
@ContextConfiguration
Special note: @ContextConfiguration annotation must be added, otherwise the bean injection will fail.
Next, let’s take a look at why this situation is caused from the source code.
The code involved in this part is in the SpringFactory class under the cucumber-spring package. Let's take a look at the following class:
public void start() {// cucumber test start method if (stepClassWithSpringContext != null) {// If the @ContextConfiguration annotation is used, this is not null testContextManager = new CucumberTestContextManager(stepClassWithSpringContext); } else {// Otherwise stepClassWithSpringContext will be null, and it will enter the following branch if (beanFactory == null) { beanFactory = createFallbackContext();// This method is the focus we want to follow} } notifyContextManagerAboutTestClassStarted(); if (beanFactory == null || isNewContextCreated()) { beanFactory = testContextManager.getBeanFactory(); for (Class<?> stepClass : stepClasses) { registerStepClassBeanDefinition(beanFactory, stepClass); } } GlueCodeContext.INSTANCE.start(); } Let's follow the createFallbackContext method:
private ConfigurableListableBeanFactory createFallbackContext() { ConfigurableApplicationContext applicationContext; if (getClass().getClassLoader().getResource("cucumber.xml") != null) {// The <span style="font-family:Arial, Helvetica, sans-serif;">ConfigurableApplicationContext</span> applicationContext = new ClassPathXmlApplicationContext("cucumber.xml"); } else {// If cucumber.xml is not configured, it will new GenericApplicationContext = new GenericApplicationContext(); } applicationContext.registerShutdownHook(); ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); beanFactory.registerScope(GlueCodeScope.NAME, new GlueCodeScope()); for (Class<?> stepClass : stepClasses) { registerStepClassBeanDefinition(beanFactory, stepClass); } return beanFactory; }Finally, let’s talk about the GenericApplicationContext class. This class will be based on the Bean’s Type and then the newInstance instance. However, since other classes are injected into this class, the injected class cannot be initialized through the new instance, so the injection will fail and a null pointer will be reported.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.