美文网首页互联网科技
Java应该如何优雅地实现单元测试与集成测试

Java应该如何优雅地实现单元测试与集成测试

作者: 风平浪静如码 | 来源:发表于2020-07-13 13:46 被阅读0次

在日常的开发过程中,为了保证代码质量,有追求的程序员一般都会对自己编写的代码进行充分的测试,这种测试不仅仅是体现在对正常功能的简单接口调用,而是要根据代码中的各种逻辑分支,进行尽可能多的覆盖性单元测试以及主要逻辑的集成测试。

上面说到的测试对于程序员来说,绝不仅仅只是依赖于Postman之类的网络工具,而要以编写独立的单元/集成测试代码的方式来实现,具体来说在Java中就是要基于JUnit、Mocktio之类的测试框架编写相应的UT及IT代码,并在这个过程中提前发现软件Bug、重新审视所写代码并进行优化。

实话说编写测试代码对提高软件质量,及自身编程水平来说都是一种非常有用的手段。但在工作中,并不是所有人都能正确地掌握单元测试和集成测试代码的写法和组织形式。以Maven工程代码为例,很多人会把单元测试和集成测试代码弄混,这样导致的后果就是大部分Maven工程代码:"mvn test"几乎很难跑通。

而本文想要表达的内容就是如何在Maven工程中有效的区分和组织单元测试、集成测试代码使得它们互不干扰,并具体演示它们的写法。

Maven测试代码结构的组织

我们知道在Maven工程结构中“src/test”目录是专门用于存放测试代码的,但令人痛苦的是Maven的标准目录结构只定义了这样一个测试目录,也就是说它本身是无法单独区分单元测试代码和集成测试代码的,这也是为什么很多人会把UT和IT代码同时写到"src/test"目录而导致“mvn test”难以跑过的原因。

那么有什么办法可以友好地解决这个问题呢?在接下来的内容中我们以Maven构建Spring Boot项目为例来具体演示下在Maven中如何友好地分离UT及IT,具体步骤如下:

1)、首先我们创建一个基于Maven构建的Spring Boot项目,代码结构如下图所示:

如上图所示,在规划的目录结构中我们将IT的代码目录及资源文件目录单独分离在“src/integration-test”目录下,默认的“src/test”目录还是作为存放UT代码的目录,而Maven在构建的过程中默认只运行UT代码。这样即便IT代码由于网络、环境等原因无法正常执行,但也不至于影响到UT代码的运行。

2)、创建区分UT、IT代码的Maven Profiles文件

默认情况下Maven是无法主动识别“src/test”目录之外的测试代码的,所以当我们将IT代码抽象到"src/integration-test"目录之后,需要通过编写Maven Profiles文件来进行区分,具体示意图如下:

如上图所示,我们可以在与“src”目录平行创建一个“profiles”的目录,其中分别用“dev”“integration-test”目录中的config.properties文件来进行区分,其中dev目录下的config.properties文件的内容为:

profile=dev

而integration-test目录中的config.properties文件则为:

profile=integration-test

3)、通过pom.xml文件配置上述profiles文件生效规则

为了使得这些profiles文件生效,我们还需要在pom.xml文件中进行相应的配置。具体如下:

<!--定义关于区分集成测试及单元测试代码的profiles-->
<profiles>
    <!-- The Configuration of the development profile -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <build.profile.id>dev</build.profile.id>
            <!--Only unit tests are run when the development profile is active-->
            <skip.integration.tests>true</skip.integration.tests>
            <skip.unit.tests>false</skip.unit.tests>
        </properties>
    </profile>
    <!-- The Configuration of the integration-test profile -->
    <profile>
        <id>integration-test</id>
        <properties>
            <build.profile.id>integration-test</build.profile.id>
            <!--Only integration tests are run when the integration-test profile is active-->
            <skip.integration.tests>false</skip.integration.tests>
            <skip.unit.tests>true</skip.unit.tests>
        </properties>
    </profile>
</profiles>

上述内容先定义了区分dev及integration-test环境的的profile信息,接下来在build标签中定义资源信息及相关plugin,具体如下:

<build>
    <finalName>${project.artifactId}</finalName>
    <!--步骤1:单元测试代码、集成测试代码分离-->
    <filters>
        <filter>profiles/${build.profile.id}/config.properties</filter>
    </filters>
    <resources>
        <resource>
            <filtering>false</filtering>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
                <include>**/*.tld</include>
                <include>**/*.yml</include>
            </includes>
        </resource>
        <!--步骤2:通过Profile区分Maven集成测试代码、单元测试代码目录-->
        <resource>
            <filtering>true</filtering>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
                <include>**/*.tld</include>
                <include>**/*.yml</include>
                <include>**/*.sh</include>
            </includes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <!-- 步骤三:将源目录和资源目录添加到构建中 -->
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.1.0</version>
            <executions>
                <!-- Add a new source directory to our build -->
                <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/integration-test/java</source>
                        </sources>
                    </configuration>
                </execution>
                <!-- Add a new resource directory to our build -->
                <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>
                                <filtering>true</filtering>
                                <directory>src/integration-test/resources</directory>
                                <includes>
                                    <include>**/*.properties</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <!--步骤四:Runs unit tests -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.18</version>
            <configuration>
                <!-- Skips unit tests if the value of skip.unit.tests property is true -->
                <skipTests>${skip.unit.tests}</skipTests>
                <!-- Excludes integration tests when unit tests are run -->
                <excludes>
                    <exclude>**/IT*.java</exclude>
                </excludes>
            </configuration>
        </plugin>
        <!--步骤五:Runs integration tests -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.18</version>
            <executions>
                <execution>
                    <id>integration-tests</id>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                    <configuration>
                        <skipTests>${skip.integration.tests}</skipTests>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

到这里我们就完成了基于Maven构建的Spring Boot项目的UT及IT代码目录的分离配置,此时对UT代码的执行还是通过默认“mvn test”命令,而集成测试代码的运行则可以通过如下命令:

mvn clean verify -P integration-test

单元测试代码示例

通过前面的配置操作就完成了单元测试、集成测试代码目录的分离设置。在后续的开发过程中只需要将相应的测试代码写在对应的测试目录即可。接下来我们模拟一段业务逻辑并演示如何编写其对应的UT代码。具体如下:

如上图所示,参考MVC三层规范,我们编写了一个接口逻辑,该接口Controller层接收Http请求后调用Service层进行处理,而Service层处理逻辑时会调用Dao层操作数据库,并将具体信息插入数据库。

那么我们编写单元测试(UT)代码时,针对的是单独的某个逻辑单元的测试,而不是从头到位的整个逻辑,它的运行不应该依赖于任何网络环境或其他组件,所有依赖的组件或网络都应该先进行Mock。以单元测试TestServceImpl中的“saveTest”方法为例,其UT代码编写如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestServiceImpl.class)
@ActiveProfiles("test")
public class TestServiceImplTest {

    @Autowired
    TestServiceImpl testServiceImpl;

    @MockBean
    TestDao testDao;

    @Test
    public void saveTest() {
        //调用测试方法
        testServiceImpl.saveTest("风平浪静如码微信公众号");
        //验证执行测试的逻辑中是否调用过addUser方法
        verify(testDao).addUser(any());
    }
}

如上所示UT代码,我们UT测试的主要对象为TestServiceImpl类,所以可以在@SpringBootTest注解中进行范围指定。而@ActiveProfiles("test")则表示代码中所依赖的系统参数,可以从测试资源目录resouces/application-test.yml文件中获得。

单元测试的主要目的是验证单元代码内的逻辑,对于所依赖的数据库Dao组件并不是测试的范围,但是没有该Dao组件对象,UT代码在执行的过程中也会报错,所以一般会通过@MockBean注解进行组件Mock,以此解决UT测试过程中的代码依赖问题。此时运行“mvn test”命令:

单元测试代码得以正常执行!

集成测试代码示例

在Spring Boot中UT代码的编写方式与IT代码类似,但是其执行范围是包括了整个上下文环境。我们以模拟从Controller层发起Http接口请求为例,来完整的测试整个接口的逻辑,并最终将数据存入数据库。具体测试代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class ITTestControllerTest {

    @Autowired
    TestController testController;

    @Test
    public void saveTest() {
        testController.saveTest("风平浪静如码微信公众号");
    }
}

可以看到对于集成测试代码在@SpringBootTest中并没有指定具体的类,它的默认执行范围为整个应用的上下文环境。而代码中的依赖组件由于整个应用上下文都会被启动,所以依赖上并不会报错,可以理解为是一个正常启动的Spring Boot应用。

需要注意的是由于IT代码的目录有独立的资源配置,所以相关的依赖配置,如数据库等需要在“src/integration-test/resouces/application-test.yml”文件中单独配置,例如:

spring:
  application:
    name: springboot-test-demo
  #数据库逻辑
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    separator: //

server:
  port: 8080

此时运行集成测试命令“mvn clean verify -P integration-test”:

可以看到执行IT测试代码得以正常执行!

后记

本文着重介绍了在Java项目中如何编写单元测试(UT)和集成测试(IT)代码的工程实践。在日常编写代码的过程中,良好的测试代码编写是一种非常好的习惯,一般来说对于UT或IT代码执行错误的工程,要求严格的团队会让其构建的过程中无法通过,以此来严格要求团队成员。

希望本文的内容能对你的编码有所启发,如果觉得还不错,可以转发给更多的朋友!

写在最后

欢迎大家关注我的公众号【风平浪静如码】,海量Java相关文章,学习资料都会在里面更新,整理的资料也会放在里面。

觉得写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!

相关文章

  • Java应该如何优雅地实现单元测试与集成测试

    在日常的开发过程中,为了保证代码质量,有追求的程序员一般都会对自己编写的代码进行充分的测试,这种测试不仅仅是体现在...

  • 基于spring-boot的应用程序的单元+集成测试方案

    概述 本文主要介绍单元测试、集成测试相关的概念、技术实现以及最佳实践。 本文的demo是基于Java语言,Spri...

  • 《徐昊-TDD项目实战70讲》学习笔记 -- Day 7

    07|TDD中的测试(3):集成测试还是单元测试? 集成测试还是单元测试? TDD 中的单元测试 在 TDD 的语...

  • 测试过程

    一、测试过程简介 单元测试 集成测试 系统测试 二、测试过程单元集成系统及比较 1.单元测试--函数 单元测试时针...

  • 契约测试

    业界认为应该让契约测试来替代集成测试。认为你写的2-5%的集成测试和单元测试有重复,或者和其它地方的集成测试存在重...

  • Android测试

    单元测试: 本地单元测试 仪器单元测试集成测试: 测试不易交互的部件: service和contentProvid...

  • 前端面试日更解答 2020-03-10

    今天的知识点 (2020.03.10) 14.单元测试、集成测试、系统测试区别? 13.什么是单元测试和集成测试?...

  • 中级测试工程师面试题

    【进阶题】 1.什么是单元测试、功能测试、集成测试? 加分项:单元测试、功能测试、集成测试分别在web端、接口端、...

  • nodejs mock数据 单元测试

    nodejs 单元测试 nodejs koa 框架实现Java 中mock+junit类似的单元测试。 需要用到的...

  • 软件测试基础-软件测试阶段

    软件测试阶段 单元测试集成测试系统测试验收测试 单元测试 什么是单元测试呢? 我理解的单元测试是最小力度的测试,可...

网友评论

    本文标题:Java应该如何优雅地实现单元测试与集成测试

    本文链接:https://www.haomeiwen.com/subject/khyacktx.html