spring boot项目搭建gitlab CI CD持续集成环境之搭建单元测试与集成测试基础框架

回系列博客主目录及代码地址 spring boot项目基于docker搭建gitlab CI CD持续集成环境

本人主要使用maven来构建springboot项目,所以本文将会介绍如何使用maven来分离构建springboot单元测试与集成测试,并配置分离构建集成测试和单元测试,使用Jcoco插件统计集成测试覆盖率和单元测试覆盖率;同时会简单提及spring test框架中我们会经常使用到的几类写spring测试的注解。

了解Maven 构建生命周期(lifecycle)

这里只是简单介绍,想详细了解Maven构建lifecycle的朋友可以查看官网:Maven生命周期

Maven包含三套独立的构建声明周期:clean、default和site,default主要负责项目的构建部署,clean负责构建之前的项目清理工作,site负责生成项目站点文档

每一个lifecycle又包含多个phases(阶段), 例如clean包含pre-clean, clean, post-clean三个阶段,default主要包含的phases:

Phase负责的职能
validatevalidate the project is correct and all necessary information is available
compilecompile the source code of the project
testtest the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
packagetake the compiled code and package it in its distributable format, such as a JAR.
verifyrun any checks on results of integration tests to ensure quality criteria are met
installinstall the package into the local repository, for use as a dependency in other projects locally
deploydone in the build environment, copies the final package to the remote repository for sharing with other developers and projects

例如当我们执行命令mvn clean deploy, Maven首先会清楚上一次的构建结果(target文件夹下),然后将会执行install以及之前所有的phases:
Maven will first validate the project, then will try to compile the sources, run those against the tests, package the binaries (e.g. jar), run integration tests against that package, verify the integration tests, install the verified package to the local repository. 关于Maven所有的phase可以查看:Maven Lifecycle Phases Reference

用过Maven的朋友都知道,所有的构建工作其实都是由插件(Plugin)来完成的。一个Phase包含多个多个Plugin Goals,换言之,插件就是通过声明goals来绑定phase来完成特定的构建工作的。例如:build-helper-maven-plugin,改插件可以将其他文件夹下的source code添加到maven构建目录下。

<execution>
  <id>add-integration-test-sources</id>
   <phase>generate-test-sources</phase>
   <goals>
       <goal>add-test-source</goal>
   </goals>
   <configuration>
       <!-- Configures the source directory of our integration tests -->
       <sources>
           <source>src/int/java</source>
       </sources>
   </configuration>
</execution>

如上,在generate-test-sources这个phase将会执行goal:add-test-source,也就是说在准备测试源代码的时候不仅会添加maven默认测试目录/src/test/java下的code,另外会将/src/int/java的code也添加进去作为编译源代码。

Spring Test常用注解

这里仅介绍常用的几个spring test注解,详细请点击:spring test reference

Spring Boot帮我们开发者省去了很多配置的工作,换言之,Spring boot帮我们做好了大部分的配置工作,这得益于其约定优于配置的思想以及自动加载(Auto Congigure)的Feature。然而在写测试的时候其实我们只关注要测试的部分,例如我想测试一个controller,其实我希望的是spring只是假装spring mvc的部分context,并不需要加载所有的starter。

  • @SpringBootTest - 将会自动加载包括Jpa repository, Servcie, Spring MVC以及集成的第三方所有的bean,该注解适合写从web层到DB层的集成测试
  • @DataJpaTest - 只加载 spring data Jpa层,适合写连接数据库测试
  • @WebMvcTest - 只加载 spring MVC层,适合写controller API测试
  • @ActiveProfiles - 指定需要加载的增量配置文件,例如 @ActiveProfiles(“test”), 将会加载application-test.yml文件
  • @Import - 将没有被自动加载的bean加到spring test context中,例如在写repository测试时,我希望将审计(Audit)功能加到context中:@Import({AuditAwareImpl.class})

当然还有很多注解,以上所介绍的只是为下面框架搭建服务,想了解更多的朋友查官网咯,嘿嘿~

搭建测试框架

项目目录结构

项目基于 spring boot + mysql,使用mysql服务,测试使用内嵌的H2数据库。放心项目非常简单,废话不多说,来吧~

新建spring boot项目

  • 使用你的IDE新建一个maven based spring boot项目
  • pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!--third lib-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.6</version>
    </dependency>

    <!-- Testing -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  • application.yml
server:
  port: 7090

spring:
  application:
    name: spring-boot-test-ci
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/kelvin?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: mysql
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: none
        show_sql: true
        format_sql: true
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
logging:
  level:
    org.hibernate.search: DEBUG
    org.springframework.security: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: DEBUG
  • 两个实体
@Entity
@Table(name="M_AUTHOR")
@Data
@NoArgsConstructor
@ToString(callSuper = true)
@Builder
@AllArgsConstructor
public class Author extends Auditable<String> {
    @Id
    @GeneratedValue(
            strategy = GenerationType.IDENTITY
    )
    private Long id;
    @Column(name = "NAME", nullable = false, length = 32)
    private String name;
    @Column(name = "AGE")
    private Integer age;
    @OneToMany(mappedBy = "author", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Post> posts = new ArrayList<>();

    private String gender;
}

@Entity
@Table(name = "M_POST")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true, exclude = "author")
public class Post extends Auditable<String> {

    public Post(String title, String body, Author author) {
        this.title = title;
        this.body = body;
        this.author = author;
    }

    @Id
    @GeneratedValue(
            strategy = GenerationType.IDENTITY
    )
    private Long id;
    @Column(name = "TITLE")
    private String title;
    @Column(name = "body")
    private String body;
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "AUTHOR_ID")
    @JsonBackReference("author")
    private Author author;
}

Auditable该类是一个审计基础类,继承该类的实体会自动添加create_by, create_date, update_by, update_date 四个column。审计功能是spring data jpa提供的,主要用于跟踪记录实体的操作(操作对象以及操作时间),不需要我们自己在代码中手动udpate,不了解的朋友可以忽略这个,或者去百度一下相关资料看看就明白了。

  • repository/service/controller
@Service("authorService")
public class AuthorServiceImpl implements AuthorService {
    @Autowired
    AuthorRepository authorRepository;

    @Override
    public List<Author> findAuthorByName(String name) {
        List<Author> authors = authorRepository.findAuthorsByName(name);
        return checkAuthorPosts(authors);
    }

    private List<Author> checkAuthorPosts(List<Author> authors) {
        List<Author> results = new ArrayList<>();
        for (Author author : authors) {
            List<Post> posts = author.getPosts();
            for (Post post : posts) {
                if (post.getTitle().contains("My Home2")) {
                    results.add(author);
                }
            }
        }
        return results;
    }

}

@RestController()
public class AuthorController {

    @Autowired
    AuthorService authorService;

    @RequestMapping("/author/{name}")
    public ResponseDto<List<Author> > getAuthorByName(@PathVariable("name") String name) {
        List<Author> authors = authorService.findAuthorByName(name);
        ResponseDto<List<Author> > responseDto = new ResponseDto<>();
        responseDto.setSuccess(true);
        responseDto.setData(authors);
        return  responseDto;
    }
}

部分没有列出来的代码,在文章最后有我的github地址。其实有了实体,service、repository、controller 你可以自己写,能使得项目连接DB,跑起来就OK了。
初始化项目以后,启动项目,访问url,如: localhost:7090/author/kelvin 能够拿到你期望的结果,下一步就可以搭建测试框架了。

测试基础配置

测试目录结构

spring boot测试包括web mvc层测试,Jpa层测试,int测试(int测试在这里是指加载所有spring context的测试)以及单元测试,web mvc层测试,Jpa层测试,int测试都需要加载spring context, 属于集成测试。Maven测试目录在src/test/java, src/test/resources

  • 测试配置文件 application-test.yml
server:
  port: 7090

spring:
  application:
    name: spring-boot-test-ci
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb;MODE=MYSQL
    username: sa
    password: sa
    platform: h2
    schema: classpath:db/schema-h2.sql
    data: classpath:db/data-h2.sql
  h2:
    console:
      enabled: true
      path: /h2
      settings:
        web-allow-others: false
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: none
        show_sql: true
        format_sql: true
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
logging:
  level:
    org.hibernate.search: DEBUG
    org.springframework.security: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: DEBUG

使用H2作为内嵌测试数据库,schema: classpath:db/schema-h2.sql, 基础初始化DB data:classpath:db/data-h2.sql

  • schema-h2.sql
-- auto-generated definition
create table m_author
(
    id               bigint auto_increment
        primary key,
    create_by        varchar(255) null,
    create_date      datetime(3)  null,
    last_update_by   varchar(255) null,
    last_update_date datetime     null,
    age              int          null,
    gender           varchar(255) null,
    name             varchar(32)  not null
);

-- auto-generated definition
create table m_post
(
    id               bigint auto_increment
        primary key,
    create_by        varchar(255) null,
    create_date      datetime(3)  null,
    last_update_by   varchar(255) null,
    last_update_date datetime     null,
    body             varchar(255) null,
    title            varchar(255) null,
    author_id        bigint       null,
    constraint FKp1hw0ofp49x20h8yjt7fg5j0p
        foreign key (author_id) references m_author (id)
);

-- auto-generated definition
create table m_user
(
    id               bigint auto_increment
        primary key,
    create_by        varchar(255) null,
    create_date      datetime(3)  null,
    last_update_by   varchar(255) null,
    last_update_date datetime     null,
    birth_day        datetime(3)  null,
    password         varchar(255) null,
    role             int          null,
    username         varchar(255) null
);



  • data-h2.sql
INSERT INTO m_author (id, create_by, create_date, last_update_by, last_update_date, age, gender, name) VALUES (1, 'Kelvin46', '2020-03-15 04:37:11.248000000', 'Kelvin46', '2020-03-15 04:37:11', 12, 'male', 'Kelvin');

INSERT INTO m_post (id, create_by, create_date, last_update_by, last_update_date, body, title, author_id) VALUES (1, 'Kelvin46', '2020-03-15 04:37:11.310000000', 'Kelvin46', '2020-03-15 04:37:11', 'post body', 'My Home', 1);
INSERT INTO m_post (id, create_by, create_date, last_update_by, last_update_date, body, title, author_id) VALUES (2, 'Kelvin46', '2020-03-15 04:37:11.317000000', 'Kelvin46', '2020-03-15 04:37:11', 'post body2', 'My Home2', 1);
  • int 测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootTestCiApplication.class, JpaConfiguration.class},
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@ActiveProfiles("test")
public class IntTestBase {
}

  • Jpa测试基础类
@RunWith(SpringRunner.class)
@DataJpaTest
@ActiveProfiles("test")
@Import({JpaConfiguration.class, AuditAwareImpl.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class JpaTestBase {
}

因为@DataJpaTest不会加载JpaConfiguration.class, AuditAwareImpl.class,所以需要使用@Import注解导入这两个类到spring context中

  • Web mvc 测试基础类
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
public abstract class WebMvcTestBase {
}

写spring测试

有了上面的基础配置类,就可以写spring测试了。其实不一定要写一个父类的,你也可以不用写baseTest,在你的例子测试中直接添加相应的注解是一样的。
spring测试都是继承测试,我们希望继承测试跟单元测试分开不同的folder,在src下面新建int文件夹,如下:

集成测试目录
为了区分不同层的测试,int测试使用IntTest后缀,wev mvc测试使用MvcTest后缀,Jpa层测试使用JpaTest后缀。下面是几个例子

  • int测试
@AutoConfigureMockMvc
public class AuthorControllerIntTest extends IntTestBase {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void should_return_authors_reponse_dto_given_author_name() throws Exception {
        MvcResult mvcResult = mockMvc.perform(get("/author/Kelvin")).andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();
        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
        System.out.println(response.getContentAsString());
        assertThat(response.getContentAsString()).isEqualTo("{\"success\":true,\"errMsg\":null,\"data\":[{\"createBy\":\"Kelvin46\",\"createDate\":\"2020-03-14T20:37:11.248+0000\",\"lastUpdateBy\":\"Kelvin46\",\"lastUpdateDate\":\"2020-03-14T20:37:11.000+0000\",\"id\":1,\"name\":\"Kelvin\",\"age\":12,\"posts\":[{\"createBy\":\"Kelvin46\",\"createDate\":\"2020-03-14T20:37:11.310+0000\",\"lastUpdateBy\":\"Kelvin46\",\"lastUpdateDate\":\"2020-03-14T20:37:11.000+0000\",\"id\":1,\"title\":\"My Home\",\"body\":\"post body\"},{\"createBy\":\"Kelvin46\",\"createDate\":\"2020-03-14T20:37:11.317+0000\",\"lastUpdateBy\":\"Kelvin46\",\"lastUpdateDate\":\"2020-03-14T20:37:11.000+0000\",\"id\":2,\"title\":\"My Home2\",\"body\":\"post body2\"}],\"gender\":\"male\"}]}");
    }
}
  • Mvc测试
@WebMvcTest(AuthorController.class)
public class AuthorControllerMvcTest extends WebMvcTestBase {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    AuthorService authorService;

    @Test
    public void should_return_authors_reponse_dto_given_author_name() throws Exception {
        when(authorService.findAuthorByName("Kelvin")).thenReturn(getAuthors());
        MvcResult mvcResult = mockMvc.perform(get("/author/Kelvin")).andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
        assertThat(response.getContentAsString()).isEqualTo("{\"success\":true,\"errMsg\":null,\"data\":[{\"createBy\":null,\"createDate\":null,\"lastUpdateBy\":null,\"lastUpdateDate\":null,\"id\":null,\"name\":\"Kelvin\",\"age\":25,\"posts\":[{\"createBy\":null,\"createDate\":null,\"lastUpdateBy\":null,\"lastUpdateDate\":null,\"id\":null,\"title\":\"My Home\",\"body\":\"post body\"}],\"gender\":\"male\"}]}");
    }

    private List<Author> getAuthors() {
        List<Author> authors = new ArrayList<>();
        Post post = Post.builder().title("My Home").body("post body").build();
        List<Post> posts = Arrays.asList(post);
        Author author = Author.builder().name("Kelvin").age(25).gender("male").posts(posts).build();
        authors.add(author);
        return authors;
    }
}
  • Jpa测试
public class AuthorRepositoryJpaTest extends JpaTestBase {
    @Autowired AuthorRepository authorRepository;

    @Test
    public void saveAuthor() {
        List<Post> posts = new ArrayList<>();
        Post post = Post.builder().body("post body").title("My Home").build();
        Post post2 = Post.builder().body("post body2").title("My Home2").build();
        posts.add(post);
        posts.add(post2);
        Author author = Author.builder().name("Jack")
                .gender("female")
                .age(23).posts(posts).build();
        post.setAuthor(author);
        post2.setAuthor(author);
        Author result = authorRepository.save(author);
        assertThat(result.getName()).isEqualTo("Jack");
        assertThat(result.getGender()).isEqualTo("female");
    }

    @Test
    public void should_init_one_author_with_name_kelvin_when_start_embedded_database() {
        List<Author> authors = authorRepository.findAuthorsByName("Kelvin");
        assertThat(authors.size()).isEqualTo(1);
        Author author = authors.get(0);
        assertThat(author.getGender()).isEqualTo("male");
        assertThat(author.getName()).isEqualTo("Kelvin");
        assertThat(author.getAge()).isEqualTo(12);
        assertThat(author.getPosts().size()).isEqualTo(2);
    }
}
  • 单元测试
    对于在service层,描述的是复杂的业务逻辑,因此我们建议写单元测试。给AuthorServiceImpl写一个单元测试,单元测试使用UnitTest后缀(AuthorServiceImplUnitTest),这里就不贴代码了,大家根据自己的service写一个简单的测试就可以。记住,单元测试放在/src/test/java目录。

相信大家都看得懂,非常简单吧~~

分离构建集成测试和单元测试

上面我们写了几个集成测试(mvc、Jpa、int测试)以及一个单元测试,集成测试放在/src/int/java 目录,单元测试放在/src/java/test目录,为什么要分开呢?当我们项目变得越来越大,写的测试越来越多的时候,这个时候如果所有的测试都放到一个目录下,集成测试运行的时间比单元测试要长的多,当将项目集成到一些CI工具上的时候,每次提交到代码库作持续集成的时间就会很长,会影响我们的日常开发。
将集成测试跟单元测试分开,在持续集成的时候只运行单元测试,集成测试设置schdule去运行,就很方便了。而且在测试金字塔里,单元测试应该是要比集成测试多的。

那么要如何分离构建集成测试和单元测试呢,没错,使用Maven plugin + profile

  • 添加build-helper-maven-plugin
<plugin>
     <groupId>org.codehaus.mojo</groupId>
     <artifactId>build-helper-maven-plugin</artifactId>
     <version>3.0.0</version>
     <executions>
         <!-- Add a new source directory to our test -->
         <execution>
             <id>add-integration-test-sources</id>
             <phase>generate-test-sources</phase>
             <goals>
                 <goal>add-test-source</goal>
             </goals>
             <configuration>
                 <!-- Configures the source directory of our integration tests -->
                 <sources>
                     <source>src/int/java</source>
                 </sources>
             </configuration>
         </execution>
         <!-- Add a new resource directory to our test -->
         <execution>
             <id>add-integration-test-resources</id>
             <phase>generate-test-resources</phase>
             <goals>
                 <goal>add-test-resource</goal>
             </goals>
             <configuration>
                 <!-- Configures the resource directory of our integration tests -->
                 <resources>
                     <resource>
                         <directory>src/int/resources</directory>
                     </resource>
                 </resources>
             </configuration>
         </execution>
     </executions>
 </plugin>

该plugin将src/int/java, src/int/resources添加到maven测试构建目录下。

  • 配置Surefire/Failsafe plugin
<!--*** Maven surefire plugin to generate coverage file ***-->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
            <skipTests>${skipUnitTest}</skipTests>
            <includes>
                <include>**/*UnitTest.java</include>
            </includes>
            <systemPropertyVariables>
                <jacoco-agent.destfile>${sonar.jacoco.reportPath}</jacoco-agent.destfile>
            </systemPropertyVariables>
        </configuration>
    </plugin>
    
 <!--Maven failsafe for Integration Test-->
      <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-failsafe-plugin</artifactId>
           <version>2.9</version>
           <executions>
               <execution>
                   <goals>
                       <goal>integration-test</goal>
                       <goal>verify</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <!--Include only integration test here-->
               <skipITs>${skipIntTest}</skipITs>
               <includes>
                   <include>**/*IntTest.java</include>
                   <include>**/*JpaTest.java</include>
                   <include>**/*ControllerTest.java</include>
                   <include>**/*MvcTest.java</include>
               </includes>
               <systemPropertyVariables>
                   <jacoco-agent.destfile>${sonar.jacoco.itReportPath}</jacoco-agent.destfile>
               </systemPropertyVariables>
           </configuration>
       </plugin>
    

Sufire plugin主要运行单元测试,只运行UnitTest.java后缀的测试;Failsafe plugin主要运行集成测试,如上。

  • 添加Jcoco Plugin统计覆盖率
<!-- jacoco plugin -->
   <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.7.9</version>
        <executions>
            <!--Performs offline instrumentation.-->
            <execution>
                <id>default-instrument</id>
                <goals>
                    <goal>instrument</goal>
                </goals>
            </execution>

            <!--Restores original classes as they were before offline instrumentation.-->
            <execution>
                <id>default-restore-instrumented-classes</id>
                <goals>
                    <goal>restore-instrumented-classes</goal>
                </goals>
            </execution>

            <execution>
                <id>default-report</id>
                <goals>
                    <goal>report</goal>
                </goals>
                <configuration>
                    <!-- Sets the path to the file which contains the execution data. -->
                    <dataFile>${sonar.jacoco.reportPath}</dataFile>
                    <!-- Sets the output directory for the code coverage report. -->
                    <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
                </configuration>
            </execution>
            <execution>
                <id>pre-integration-test</id>
                <!--<phase>pre-integration-test</phase>-->
                <goals>
                    <goal>prepare-agent-integration</goal>
                </goals>
                <configuration>
                    <append>true</append>
                    <destFile>${sonar.jacoco.itReportPath}</destFile>
                </configuration>
            </execution>
            <execution>
                <id>default-report-integration</id>
                <goals>
                    <goal>report-integration</goal>
                </goals>
                <configuration>
                    <!-- Sets the path to the file which contains the execution data. -->
                    <dataFile>${sonar.jacoco.itReportPath}</dataFile>
                    <!-- Sets the output directory for the code coverage report. -->
                    <outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory>
                </configuration>
            </execution>
        </executions>
    </plugin>
  • 配置maven profile
 <profiles>
        <profile>
            <id>unit</id>
            <properties>
                <skipIntTest>true</skipIntTest>
                <skipUnitTest>false</skipUnitTest>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>int</id>
            <properties>
                <skipIntTest>false</skipIntTest>
                <skipUnitTest>true</skipUnitTest>
            </properties>
        </profile>
        <profile>
            <id>tests</id>
            <properties>
                <skipIntTest>false</skipIntTest>
                <skipUnitTest>false</skipUnitTest>
            </properties>
        </profile>
        <profile>
            <id>skipTests</id>
            <properties>
                <skipIntTest>true</skipIntTest>
                <skipUnitTest>true</skipUnitTest>
            </properties>
        </profile>
    </profiles>

解析:

  1. Sufire插件职能试运行单元测试,配置为<skipTests>${skipUnitTest}</skipTests>,当skipUnitTest为true的时候Maven不会运行单元测试;Failsafe职能是运行集成测试,配置为<skipITs>${skipIntTest}</skipITs>,当skipIntTest=true时不会运行集成测试。
  2. 这里配置了四个profile:unit, int, tests, skipTests. 并且不同的profile,skipUnitTest和skipIntTest的值不一样,其当执行mvn clean install -P unit的时候,构建项目时只运行单元测试;当执行 mvn clean install -P int的时候只运行集成测试,其它同理。
  3. Sufire, Failsafe结合Jcoco插件可以统计单元测试运行后代码覆盖率,并生成覆盖率统计报告供代码审查工具读取(如:sonarqube);本文的例子生成的报告文件:target/jacoco-ut.exec, target/jacoco-it.exec
// 集成测试覆盖率报告
<jacoco-agent.destfile>${sonar.jacoco.itReportPath}</jacoco-agent.destfile>
// 单元测试覆盖率报告
<jacoco-agent.destfile>${sonar.jacoco.reportPath}</jacoco-agent.destfile>
  • readMe
### 运行单元测试
- mvn clean install -P unit -s settings.xml

### 运行集成测试测试
- mvn clean install -P int -s settings.xml

### 运行所有测试
- mvn clean install -P tests -s settings.xml

### 忽略测试构建
- mvn clean install -P skipTests -s settings.xml
- mvn clean install -Dmaven.test.skip=true -s settings.xml

至此,搭建测试框架成功了;如果对你有帮助,别忘记点个赞,非常感谢~~
后续会集成gitlab ci cd以及sonarqube工具,敬请期待

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值