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

解决问题

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

演示版本及说明

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

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

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

    

        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-logging
            ${spring.boot.version}
        

        
            com.intuit.karate
            karate-junit4
            1.3.1
            test
        

        
            net.masterthought
            cucumber-reporting
            5.3.1
            test
        

        
            commons-io
            commons-io
            2.7
        
    

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

在pom.xml文件中添加:

    
        
            
                src/test/java
                
                    **/*.java
                
            
        
    

    
        
            coverage
            
                
                    
                        org.apache.maven.plugins
                        maven-surefire-plugin
                        2.22.2
                        
                            

                                demo/DemoTestParallel.java
                            

                            -Dfile.encoding=UTF-8 ${argLine}
                        
                    
                    
                        org.jacoco
                        jacoco-maven-plugin
                        0.7.9
                        
                            
                                default-prepare-agent
                                
                                    prepare-agent
                                
                            
                            
                                default-report
                                test
                                
                                    report
                                
                            
                        
                    
                
            
        
    

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 jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] {"json"}, true);
        List 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 {

    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 search(HttpServletRequest request) {
        Map 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,配置测试代码覆盖率插图1

6、启动命令

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

7、总结

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

文章来源于互联网:从零入门项目集成Karate和Jacoco,配置测试代码覆盖率

THE END
分享
二维码