回系列博客主目录及代码地址 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 | 负责的职能 |
---|---|
validate | validate the project is correct and all necessary information is available |
compile | compile the source code of the project |
test | test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed |
package | take the compiled code and package it in its distributable format, such as a JAR. |
verify | run any checks on results of integration tests to ensure quality criteria are met |
install | install the package into the local repository, for use as a dependency in other projects locally |
deploy | done 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>
解析:
- Sufire插件职能试运行单元测试,配置为<skipTests>${skipUnitTest}</skipTests>,当skipUnitTest为true的时候Maven不会运行单元测试;Failsafe职能是运行集成测试,配置为<skipITs>${skipIntTest}</skipITs>,当skipIntTest=true时不会运行集成测试。
- 这里配置了四个profile:unit, int, tests, skipTests. 并且不同的profile,skipUnitTest和skipIntTest的值不一样,其当执行mvn clean install -P unit的时候,构建项目时只运行单元测试;当执行 mvn clean install -P int的时候只运行集成测试,其它同理。
- 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工具,敬请期待