从零入门项目集成Karate和Jacoco,配置测试代码覆盖率

解决问题

在SpringBoot项目中,如何集成Karate测试框架和Jacoco插件。以及编写了feature测试文件,怎么样配置才能看到被测试接口代码的覆盖率。

演示版本及说明

本次讲解,基于SpringBoot2.1.4.RELEASE版本,可根据项目版本灵活更改。下面所有的版本号,可以自行选择,也可以直接使用下文版本。包括项目目录,都可以自行创建。

1、集成Karate测试框架,及通用配置包

在SpringBoot项目的pom.xml中,添加以下配置:

    <dependencies>         <!-- 引入 Web 功能 -->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>          <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-logging</artifactId>             <version>${spring.boot.version}</version>         </dependency>          <dependency>             <groupId>com.intuit.karate</groupId>             <artifactId>karate-junit4</artifactId>             <version>1.3.1</version>             <scope>test</scope>         </dependency>          <dependency>             <groupId>net.masterthought</groupId>             <artifactId>cucumber-reporting</artifactId>             <version>5.3.1</version>             <scope>test</scope>         </dependency>          <dependency>             <groupId>commons-io</groupId>             <artifactId>commons-io</artifactId>             <version>2.7</version>         </dependency>     </dependencies> 

2、集成Jacoco插件及配置生成接口覆盖率文件

在pom.xml文件中添加:

    <build>         <testResources>             <testResource>                 <directory>src/test/java</directory>                 <excludes>                     <exclude>**/*.java</exclude>                 </excludes>             </testResource>         </testResources>     </build>      <profiles>         <profile>             <id>coverage</id>             <build>                 <plugins>                     <plugin>                         <groupId>org.apache.maven.plugins</groupId>                         <artifactId>maven-surefire-plugin</artifactId>                         <version>2.22.2</version>                         <configuration>                             <includes>                                 <!-- ** 这个很重要 指定为下面创建的入口启动类 -->                                 <include>demo/DemoTestParallel.java</include>                             </includes>                             <!-- 这里报红不用管 -->                             <argLine>-Dfile.encoding=UTF-8 ${argLine}</argLine>                         </configuration>                     </plugin>                     <plugin>                         <groupId>org.jacoco</groupId>                         <artifactId>jacoco-maven-plugin</artifactId>                         <version>0.7.9</version>                         <executions>                             <execution>                                 <id>default-prepare-agent</id>                                 <goals>                                     <goal>prepare-agent</goal>                                 </goals>                             </execution>                             <execution>                                 <id>default-report</id>                                 <phase>test</phase>                                 <goals>                                     <goal>report</goal>                                 </goals>                             </execution>                         </executions>                     </plugin>                 </plugins>             </build>         </profile>     </profiles> 

3、创建启动测试和配置的相关类

1、创建基础启动类

下面的步骤是示例使用,具体使用的时候结合实际创建。
src/test/java中创建test包,在其中创建下面四个类:

1.1 ServerStart类:

public class ServerStart {      private static final Logger logger = LoggerFactory.getLogger(ServerStart.class);      private ConfigurableApplicationContext context;     private MonitorThread monitor;     private int port = 0;      public void start(String[] args, boolean wait) throws Exception {         if (wait) {             try {                 logger.info("attempting to stop server if it is already running");                 new ServerStop().stopServer();             } catch (Exception e) {                 logger.info("failed to stop server (was probably not up): {}", e.getMessage());             }         }         // Application 改为自己项目的启动类         context = Application.run(args);         ServerStartedInitializingBean ss = context.getBean(ServerStartedInitializingBean.class);         port = ss.getLocalPort();         logger.info("started server on port: {}", port);         if (wait) {             int stopPort = port + 1;             logger.info("will use stop port as {}", stopPort);             monitor = new MonitorThread(stopPort, () -> context.close());             monitor.start();             monitor.join();         }     }      public int getPort() {         return port;     }      @Test     public void startServer() throws Exception {         start(new String[]{}, true);     }  } 

1.2 ServerStop类:

public class ServerStop { 	@Test 	public void stopServer() { 		MonitorThread.stop(8081); 	} } 

1.3 MonitorThread类:

public class MonitorThread extends Thread {      private static final Logger logger = LoggerFactory.getLogger(MonitorThread.class);      private Stoppable stoppable;     private ServerSocket socket;      public MonitorThread(int port, Stoppable stoppable) {         this.stoppable = stoppable;         setDaemon(true);         setName("stop-monitor-" + port);         try {             socket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));         } catch (Exception e) {             throw new RuntimeException(e);         }     }      @Override     public void run() {         logger.info("starting thread: {}", getName());         Socket accept;         try {             accept = socket.accept();             BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));             reader.readLine();             logger.info("shutting down thread: {}", getName());             stoppable.stop();             accept.close();             socket.close();         } catch (Exception e) {             throw new RuntimeException(e);         }     }     public static void stop(int port) {          try {             Socket s = new Socket(InetAddress.getByName("127.0.0.1"), port);             OutputStream out = s.getOutputStream();             logger.info("sending stop request to monitor thread on port: {}", port);             out.write(("rn").getBytes());             out.flush();             s.close();         } catch (Exception e) {             throw new RuntimeException(e);         }     } } 

1.4 Stoppable接口:

public interface Stoppable {     void stop() throws Exception; } 

2、创建测试启动配置类

在项目的src/test/java下建一个demo的包,在包中创建下面两个类:

2.1 TestBase类:

@RunWith(Karate.class) public abstract class TestBase {          private static ServerStart server;          public static int startServer() throws Exception {         if (server == null) { // keep spring boot side alive for all tests including package 'mock'             server = new ServerStart();             server.start(new String[]{"--server.port=0"}, false);         }         System.setProperty("demo.server.port", server.getPort() + "");         return server.getPort();             }          @BeforeClass     public static void beforeClass() throws Exception {         startServer();     }      } 

2.2 DemoTestParallel类:

// 该类是测试启动类,也是配置类,需要将该类配置在pom文件里 public class DemoTestParallel {      @BeforeClass     public static void beforeClass() throws Exception {         TestBase.beforeClass();     }      @Test     public void testParallel() {         // 配置想要测试的feature文件所在目录,可自行更改         Results results = Runner.path("classpath:demo")                 .outputCucumberJson(true)                 // 配置测试环境,根据实际修改,也可以不改                 .karateEnv("demo")                 .parallel(5);         generateReport(results.getReportDir());         assertTrue(results.getErrorMessages(), results.getFailCount() == 0);     }      public static void generateReport(String karateOutputPath) {         Collection<File> jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] {"json"}, true);         List<String> jsonPaths = new ArrayList<>(jsonFiles.size());         jsonFiles.forEach(file -> jsonPaths.add(file.getAbsolutePath()));         Configuration config = new Configuration(new File("target"), "demo");         ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config);         reportBuilder.generateReports();     }  } 

3、创建karate配置文件

src/test/java包下,创建karate-config.js文件,这个文件是为了设置全局配置信息的,可以设置全局uri,port,env等,因为这是示例,所以简单配置一下uri和port。

function fn() {     var port = '8080';     var config = { demoBaseUrl: 'http://127.0.0.1:' + port };     return config; } 

4、创建服务启动初始化类

src/main/java/com/karate/config下创建一个初始化类,如果目录没有,自己创建目录。

@Component public class ServerStartedInitializingBean implements ApplicationRunner, ApplicationListener<WebServerInitializedEvent> {      private static final Logger logger = LoggerFactory.getLogger(ServerStartedInitializingBean.class);      private int localPort;      public int getLocalPort() {         return localPort;     }      @Override     public void run(ApplicationArguments aa) throws Exception {         logger.info("server started with args: {}", Arrays.toString(aa.getSourceArgs()));     }      @Override     public void onApplicationEvent(WebServerInitializedEvent event) {         localPort = event.getWebServer().getPort();         logger.info("after runtime init, local server port: {}", localPort);     } } 

4、测试接口并生成覆盖率文件

4.1、创建测试接口

src/main/java/com/karae中创建controller包,创建下面的类:

@RestController public class KarateController {      @GetMapping("/search")     public Map<String, String[]> search(HttpServletRequest request) {         Map<String, String[]> parameterMap = request.getParameterMap();         if (parameterMap == null || parameterMap.size() == 0) {             return null;         }         return request.getParameterMap();     } } 

4.2、创建feature测试文件

src/test/java/demo/karate包中,创建karate_test.feature文件

Feature: karate test controller Background:     * url demoBaseUrl    Scenario: karate test     # create a test     Given path 'search'     And params ({ name: 'Scooby' })     When method get     Then status 200     And match response == { "name": ["Scooby"] } 

5、验证文件是否缺少

下面的图片是演示的项目,可以对比一下,有没有类或文件没有创建。
从零入门项目集成Karate和Jacoco,配置测试代码覆盖率
从零入门项目集成Karate和Jacoco,配置测试代码覆盖率

6、启动命令

历尽千辛万苦,终于到了要验收成果的时候,祝大家好运!
在控制台中输入mvn clean test -Pcoverage。如果使用的是idea直接在软件左下方的Terminal中或者右边maven配置中输入即可。其他软件,自行找到控制台,目录定位到项目下,执行命令即可。命令执行完,在target目录下,会有一个site的文件夹,打开找到index.html,到浏览器运行即可看到代码覆盖率。
看一下最终的效果截图:
从零入门项目集成Karate和Jacoco,配置测试代码覆盖率
从零入门项目集成Karate和Jacoco,配置测试代码覆盖率
从零入门项目集成Karate和Jacoco,配置测试代码覆盖率

7、总结

在网上找了很久都没有很好的解答,所以自己摸索并记录下来,也希望能帮到更多的人,一起进步!
本次演示,完成了SpringBoot集成Karate测试框架和Jacoco插件。从零开始,一步步实现了对接口代码的测试,以及最终生成被测试代码的覆盖率。演示基于Karate的官网教程,还是比较规范的引入。
后续会继续写一些Karate的语法和场景示例,解决实际项目中测试遇到的问题,例如如何模拟数据库方法或者调用其他服务器接口的方法。

发表评论

相关文章