【Maven教程】(九):使用 Maven 进行测试——动态指定要运行的测试用例、包含与排除测试用例、测试报告、运行TestNG测试、重用测试代码 ~_maven和testng怎么用-程序员宅基地

技术标签: jvm  学习  java  1024程序员节  maven  Maven教程  开发语言  

目录

1️⃣ account-captcha

1.1 account-captcha

1.2 account-captcha 的主代码

1.3 account-captcha的测试代码

2️⃣ maven-surefire-plugin 简介

3️⃣ 跳过测试

4️⃣ 动态指定要运行的测试用例

5️⃣ 包含与排除测试用例

6️⃣ 测试报告

6.1基本的测试报告

6.2 测试覆盖率报告

7️⃣ 运行 TestNG 测试

8️⃣ 重用测试代码

总结



随着敏捷开发模式的日益流行,软件开发人员也越来越认识到日常编程工作中单元测试的重要性。Maven的重要职责之一就是自动运行单元测试,它通过 maven-surefire-plugin与主流的单元测试框架JUnit3、JUnit4以及TestNG集成,并且能够自动生成丰富的结果报告。本文将介绍Maven关于测试的一些重要特性,但不会深入解释单元测试框架本身及相关技巧,重点是介绍如何通过Maven控制单元测试的运行。


除了测试之外,本文还会进一步丰富账户注册服务这一背景案例,引入其第3个模块:account-captcha

1️⃣ account-captcha

在讨论maven-surefire-plugin之前,本文先介绍实现账户注册服务的 account-captcha模块,该模块负责处理账户注册时验证码的key生成、图片生成以及验证等。可以回顾前面的文章的背景案例以获得更具体的需求信息。

1.1 account-captcha

该模块的POM(Project Object Model,项目对象模型)还是比较简单的,内容见代码:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/apache.org/maven-v4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.xiaoshan.mvnbook.account</groupId> 
        <artifactId>account-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>account-captcha</artifactId>
    <name>Account Captcha</name>
    <properties>
        <kaptcha.version>2.3</kaptcha.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.code.kaptcha</groupId> 
            <artifactId>kaptcha</artifactId>
            <version>${kaptcha.version}</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>org.springframework/groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-bean</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactid>
        </dependency>
    </dependencies>

    <repositories>
        <repository>    
            <id>sonatype-forge</id>
            <name>Sonatype Forge</name>
            <url>http:/repository.sonatype.org/content/groups/forge/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>                                 

首先POM中的第一部分是父模块声明,如同 account-email、account-persist 一样,这里将父模块声明为 account-parent。紧接着是该项目本身的 artifactld和名称,groupld 和version没有声明,将自动继承自父模块。再往下声明了一个Maven属性 kaptcha.version, 该属性用在依赖声明中,account-captcha的依赖除了Spring Framework和 JUnit之外,还有一个 com.google.code.kaptcha:kaptcha。Kaptcha是一个用来生成验证码(Captcha)的开源类库,
account-captcha将用它来生成注册账户时所需要的验证码图片,如果想要了解更多关于Kaptcha的信息,可以访问其项目主页:http://code.gpoogle.com/p/kaptcha/。


POM中SpringFramework和JUnit的依赖配置都继承自父模块,这里不再赘述。Kaptcha依赖声明中version使用了Maven属性,这在之前也已经见过。需要注意的是,Kaptcha依赖还有一个 classifier元素,其值为 jdk5, Kaptcha针对Java1.5和Java1.4提供了不同的分发包,因此这里使用classifier来区分两个不同的构件。


POM的最后声明了Sonatype Forge这一公共仓库,这是因为 Kaptcha并没有上传的中央仓库,我们可以从Sonatype Forge仓库获得该构件。如果有自己的私服,就不需要在POM中声明该仓库了,可以代理Sonatype Forge仓库,或者直接将Kaptcha上传到自己的仓库中。
最后,不能忘记把account-captcha加入到聚合模块(也是父模块)account-parent中,见代码:

<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
            http:/maven.apache.org/maven-v4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xiaoshan.mvnbook.account</groupId>
    <artifactId>account-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Parent</name>
    <modules>
        <module>account-email</module>
        <module>account-persist</module>
        <module>account-captcha</module>
    </modules>
</project>

1.2 account-captcha

account-caplcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后用户用肉眼读取图片的值,并将验证码的主键与这个值交给服务进行验证。这一服务对应的接口可以定义,如代码所示。

package com.xiaosahn.mvnbook.account.captcha;
import java.util.List;

public interface AccountCaptchaService{

    String generateCaptchakey() throws AccountCaptchaException;

    byte[] generatecaptchaImage(String captchaKey) throws AccountCaptchaException;	

    boolean validateCaptcha(String captchaKey, String captchaValue) throws AccountCaptchaException;

    List<String> getPreDefinedTexts();

    void setPreDefinedTexts(List<String> preDefinedTexts);
}

很显然,generateCaptchaKey() 用来生成随机的验证码主键,generateCaptchalmage() 用来生成验证码图片,而validateCaptcha()用来验证用户反馈的主键和值。
该接口定义了额外的 getPreDefinedTexts()和 setPreDefinedTexts()方法,通过这一组方法,用户可以预定义验证码图片的内容,同时也提高了可测试性。如果AccountCaptchaService永远生成随机的验证码图片,那么没有人工的参与就很难测试该功能。现在,服务允许传入一个文本列表,这样就可以基于这些文本生成验证码,那么我们也就能控制验证码图片的内容了。


为了能够生成随机的验证码主键,引入一个RandomGenerator类,见代码:

package com.xiaoshan.mvnbook.account.captcha;

import java.util.Random;

public class RandomGenerator{

    private static String range="0123456789abcdefghijklmnopqrstuvwxyz";

    public static synchronized String getRandomString()
    {
        Random random = new Random();

        StringBuffer result = new StringBuffer();

        for(int i=0;i<8;i++)
        {
            result.append(range.charAt(random.nextInt(range.Length())));
        }
        return result.toString();
    }
}

RandomGenerator类提供了一个静态且线程安全的 getRandomString()方法。该方法生成一个长度为8的字符串,每个字符都是随机地从所有数字和字母中挑选,这里主要是使用了java.util.Random类,其 nextlnt(int n)方法会返回一个大于等于0且小于n的整数。代码中的字段 range包含了所有的数字与字母,将其长度传给 nextInt()方法后就能获得一个随机的下标,再调用range.charAt()就可以随机取得一个其包含的字符了。


现在看AccountCaptchaService的实现类AccountCaptchaServicelmpl。首先需要初始化验证码图片生成器,见代码:


package com.xiaoshan.mvnbook.account.captcha;

import java.awt.image.BufferedImage;
import java.io.ByteArrayoutputStream;
import java.io.IOException;
import java.util.HashMap;

import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.imageio.ImageIo;

import org.springframework.beans.factory.InitializingBean;
import com.google.code.kaptcha.impl.DefauitKaptcha;
import com.google.code.kaptcha.uti1.config;

public class AccountCaptchaServiceImpl implements AccountCaptchaService, InitializingBean {

    private DefaultKaptcha producer;

    public void afterPropertiesSet() throws Exception {
        producer = new DefaultKaptcha();
        producer.setConfig(new Config(new Properties()));
    }
    ...
}

AccountCaptchaServiceImpl 实现了Spring Framework的 InitializingBean接口,该接口定义了一个方法 afterPropertiesSet(), 该方法会被Spring Framework初始化对象的时候调用。该代码清单中使用该方法初始化验证码生成器 producer,并且为 producer提供了默认的配置。接着AccountCaptehaServicelmpl需要实现 generateCaptchaKey()方法,见代码:

private Map<String, String> captchaMap = new HashMap<String, String>();

private List<String> preDefinedTexts;

private int textCount=0;

public String generateCaptchaKey()
{
        String key=RandomGenerator.getRandomstring();
        String value=getCaptchaText();
        captchaNap.put(key,value);
        return key;
}

public List<String>	getPreDefinedTexts()
{
        return preDefinedTexts;
}

public void setPreDefinedTexts(List<String> preDefinedTexts)
{
        this.preDefinedTexts=preDefinedTexts;
}

private String getCaptchaText()
{
        if(preDefinedTexts!=null&&!preDefinedTexts.isEmpty())
        {
            String text=preDefinedTexts.get(textCount);
            textCount=(textCount+1)%preDefinedTexts.size();
            return text;
        }
        else
        {
            return producer.createText();
        }
}

上述代码清单中的 generateCaptchaKey()首先生成一个随机的验证码主键,每个主键将和一个验证码字符串相关联,然后这组关联会被存储到 captchaMap中以备将来验证。主键的目的仅仅是标识验证码图片,其本身没有实际的意义。代码清单中的 getCaptchaText()用来生成验证码字符串,当 preDefinedTexts不存在或者为空的时候,就是用验证码图片生成器 pruducer创建一个随机的字符串,当 preDefinedTexts不为空的时候,就顺序地循环该字符串列表读取值。preDefinedTexts有其对应的一组get和set方法,这样就能让用户预定义验证码字符串的值。


有了验证码图片的主键,AccountCaptchaServicelmpl就需要实现 generateCaptchalmage()方法来生成验证码图片,见代码:

public byte[] generateCaptchaImage(String captchakey) throws AccountCaptchaException
{
    String text = captchaMap.get(captchakey);
    if(text == null)
    {
        throw new AccountCaptchaException("Captch key'"+ captchakey +"'not found!");
    }

    BufferedImage image = producer.createImage(text);
    
    ByteArrayOutputStream out = new ByteArrayOutputStream();

    try
    {
        ImageIO.write(image,"jpg",out);
    }
    catch(IOException e)
    {
        throw new AccountCaptchaException("Failed to write captcha stream!",e);
    }
    return out.toByteArray();
}

为了生成验证码图片,就必须先得到验证码字符串的值,代码清单中通过使用主键来查询captchaMap获得该值,如果值不存在,就抛出异常。有了验证码字符串的值之后,generateCaptchalmage()方法就能通过 producer来生成一个 BufferedImage, 随后的代码将这个图片对象转换成 jpg格式的字节数组并返回。有了该字节数组,用户就能随意地将其保存成文件,或者在网页上显示。

最后是简单的验证过程,见代码:

public boolean validateCaptcha(String captchaKey, String captchaValue) throws                AccountCaptchaException {
    String text = captchaMap.get(captchakey);

    if(text == null)
    {
        throw new AccountCaptchaException("Captch key'"+ captchaKey +"not
found!");
    }
    if(text.equals(captchaValue))
    {
        captchaMap.remove(captchaKey);
        return true;
    }
    else
    {
        return false;
    }
}

用户得到了验证码图片以及主键后,就会识别图片中所包含的字符串信息,然后将此验证码的值与主键一起反馈给 validateCaptcha()方法以进行验证。validateCaptcha()通过主键找到正确的验证码值,然后与用户提供的值进行比对,如果成功,则返回true。
当然,还需要一个Spring Framework的配置文件,它在资源目录 src/main/resources/下,名为account-captcha.xml,见代码:

<?xml version="1.0"encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="accountCaptchaService"
        class="com.xiaoshan.mvmbook.account.captcha.AccountCaptchaServiceImpl">
    </bean>
</beans>

这是一个最简单的Spring Framework配置,它定义了一个id为accountCaptchaService的 bean,其实现为刚才讨论的AccountCaptchaServicelmpl。


1.3 account-captcha的测试代码

测试代码位于src/test/java日录,其包名也与主代码一致,为com.xiaoshan.mvnbook.accountcaptcha。首先看一下简单的RandomeGeneratorTest,见代码:

package com.xiaoshan.mvnbook.account.captcha;

import static org.junit.Assert.assertFalse;
import java.util.Hashset;
import java.util.Set;
import org.junit.Test;

public class RandomGeneratorTest
{

    @Test 
    public void testGetRandomString() throws Exception
    {
        Set<String> randoms = new HashSet<String>(100);

        for(int i=0; i<100; i++)
        {
            String random = RandomGenerator.getRandomString();

            aasertFalse(randoms.cantains(random));
            randoms.add(random);
        }
    }
}

该测试用例创建一个初始容量为100的集合randoms,然后循环100次用RandomGenerator生成随机字符串并放入randoms中,同时每次循环都检查新生成的随机值是否已经包含在集合中。这样一个简单的检查能基本确定RandomGenerator生成值是否为随机的。
当然这个模块中最重要的测试应该在AccounCaptchaService上,见代码:

package com.xiaoshan.mvnbook.account.captcha;
import static org.junit.Assert.*;
import java.io.File;
import java.io.FileoutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.apringframework.context.support.ClassPathxmlapplicationcontext;

public class AccountCaptchaServiceTest
{

    private AcccuntCaptchaService service;

    @Before
    public void prepare() throws Exception
    {
        ApplicatlonContext ctx = new ClassPathXmlApplicationContext("accounc-captcha.xml");
        service = (AccountCaptchaservice) ctx.getBean("accountCaptchaservice");
    }

    @Test
    public void testGenerateCaptcha() throws Exception
    {
        String captchaKey = service.generaceCaptchaKey();
        assertNotNull(captchakey);

        byte[] captchaImage = service.generateCaptchaImage(captchakey);
        assertTrue(captchaImage.Length >0);

        File image = new File("target/"+ captchakey + ".jpg");
        OutputStream output = null;
        try
        {
            output = new FileOutputStream(image);
            output.write(captchaImage);
        }
        finally
        {
            if(output != null)
            {
                output.close();
            }
        }
        assertTrue(image.exists() && image.length() > 0);
    }

    @Test
    public void testValidatecaptchacorrect() throws Exception
    {
        List<String> preDefinedTexts = new ArrayList<String>();
        preDefinedTexts.add("12345");
        preDefinedTexts.add("abcde"); 
        service.setPreDefinedTexts(preDefinedTexts);

        String captchaKey = service.generatecaptchakey();
        service.generateCaptchaImage(captchaKey);
        assertTrue(service.validateCaptcha(captchaKey,"12345"));

        captchaKey = service.generateCaptchaKey();
        service.generateCaptchaImage(captchaKey):
        assertTrue(service.validateCaptcha(captchaKey,"abcde"));
    }

    @Test
    public void testValidateCaptchaIncorrect() throws Exception
    {
        List<String> preDefinedTexts = new ArrayList<String>();
        preDefinedTexts.add("12345");
        service.setPreDefinedTexts(preDefinedTexts);

        String captchaKey = service.generateCaptchakey();
        service.generateCaptchaImage(captchaKey);
        assertFalse(service.validatecaptcha(captchaKey,"67890"));
    }
}

该测试类的prepare()方法使用@Before标注,在运行每个测试方法之前初始化AccountCaptchaService这个bean。
testGenerateCaptcha()用来测试验证码图片的生成。首先它获取一个验证码主键并检查其非空,然后使用该主键获得验证码图片,实际上是一个字节数组,并检查该字节数组的内容非空。紧接着该测试方法在项目的target目录下创建一个名为验证码主键的jpg格式文件,并将AccountCaptchaService返回的验证码图片字节数组内容写入到该jpg文件中,然后再检查文件存在且包含实际内容。运行该测试之后,就能在项目的target目录下找到一个名如dhb022fc.jpg的文件,打开是一个验证码图片。

testValidateCaptchaCorrect()用来测试一个正确的Captcha验证流程。它首先预定义了两个Captcha的值放到服务中,然后依次生成验证码主键、验证码图片,并且使用主键和已知的值进行验证,确保服务正常工作。
最后的testValidateCaptchalncorrect()方法测试当用户反馈的Captcha值错误时发生的情景,它先预定义Captcha的值为“12345”,但最后验证是传入了“67890”,并检查validateCaptcha()方法返回的值为false。


现在运行测试,在项目目录下运行mvn test,就会得到如下输出:

[INFO] Scanning for projects...
[INFO]
[INFO]
[INFO] Building Account Captcha 1.0.0-SNAPSHOT
[INFO]
[INFO].
[INFO]
[INFO]---maven-surefire-plugin:2.4.3:test(defauit-test)@account-captcha---
[INFO] surefire report directory:D:\code\ch-10\account-aggregator account-captcha\target\surefire-reports
TESTS
Running                                  com.xiaoshan.mvnbook.account.captcha.RandomGeneratorTest
Tests run:1, Failures:0, Errors:0, Skipped:0, Time elapsed:0.037 sec
Running                                       com.xiaoshan.mvnbook.account.captcha.AccountCaptchaServiceTest
Tests run:3, Failures:0, Errors:0, Skipped:0, Time elapsed:1.016 sec
Results;
Tests run:4, Failures:0, Errors:0, Skipped:0
[INFO]-----
[INFO] BUILD SUCCESS
[INFO]

这个简单的报告告诉我们,Maven运行了两个测试类,其中第一个测试类 RandomGeneratorTest包含1个测试,第二个测试类 AccountCaptchaServiceTest包含3个测试,所有4个测试运行完毕后,没有任何失败和错误,也没有跳过任何测试。


报告中的Failures、Errors、Skipped信息来源于JUnit测试框架。Failures(失败) 表示要测试的结果与预期值不一致,例如测试代码期望返回值为true, 但实际为false; Errors(错误)表示测试代码或产品代码发生了未预期的错误,例如产品代码抛出了一个空指针错误,该错误又没有被测试代码捕捉到;Skipped表示那些被标记为忽略的测试方法,在JUnit中用户可以使用@Ignore注解标记忽略测试方法。

2️⃣ maven-surefire-plugin

Maven本身并不是一个单元测试框架,Java世界中主流的单元测试框架为 JUnit(http://www.junit.org/)和 TestNG(http://testng.org/)。Maven所做的只是在构建执行到特定生命周期阶段的时候,通过插件来执行JUnit或者TestNG的测试用例。这一插件就是maven-surefire-plugin,可以称之为测试运行器(TestRuner), 它能很好地兼容JUnit3、JUnit4以及TestNG。

可以回顾一下前面介绍过的default生命周期,其中的 test阶段被定义为“使用单元测试框架运行测试”。我们知道,生命周期阶段需要绑定到某个插件的目标才能完成真正的工作,test阶段正是与maven-surefire-plugin的test目标相绑定了,这是一个内置的绑定,具体可参考前面的文章。

在默认情况下,maven-surefire-plugin的test目标会自动执行测试源码路径(默认为src/test/java/)下所有符合一组命名模式的测试类。这组模式为:

  • **/Test *.java:任何子目录下所有命名以Test开头的Java类。
  • **/* Test.java:任何子目录下所有命名以Test结尾的Java类。
  • **/* TestCase.java:任何子目录下所有命名以TestCase结尾的Java类。

只要将测试类按上述模式命名,Maven就能自动运行它们,用户也就不再需要定义测试集合(TestSuite)来聚合测试用例(TestCase)。关于模式需要注意的是,以Tests结尾的测试类是不会得以自动执行的。

当然,如果有需要,可以自己定义要运行测试类的模式,这一点将在下文中详细描述。此外,maven-surefire-plugin还支持更高级的TestNG测试集合xml文件,这一点也将在下文中详述。

当然,为了能够运行测试,Maven需要在项目中引入测试框架的依赖,我们已经多次涉及了如何添加JUnit测试范围依赖,这里不再赘述,而关于如何引入TestNG依赖,可参看下文第7节。

3️⃣ 跳过测试

日常工作中,软件开发人员总有很多理由来跳过单元测试,“我敢保证这次改动不会导致任何测试失败", "测试运行太耗时了,暂时跳过一下", “有持续集成服务跑所有测试呢,我本地就不执行啦”。在大部分情况下,这些想法都是不对的,任何改动都要交给测试去验证,测试运行耗时过长应该考虑优化测试,更不要完全依赖持续集成服务来报告错误,测试错误应该尽早在尽小范围内发现,并及时修复。
不管怎样,我们总会要求Maven跳过测试,这很简单,在命令行加入参数skipTests就可以了。例如:

$mvn package -D skipTests


Maven输出会告诉你它跳过了测试:

[INFO]---maven-compller-plugin:2.0.2:testCompile(default-testCompile)@ac-
count-captcha---
[INFO]Compiling 2 source files to D:\\code\ch-10\account-aggregator\account-
captcha\target\test-classes
[INFO]
[INFO]---maven-surefire-plugin:2.4.3:test(default-test)@account-captcha---
[INPO]Tests are skipped.

当然,也可以在POM中配置 maven-surefire-plugin插件来提供该属性,如代码所示。但这是不推荐的做法,如果配置POM让项目长时间地跳过测试,则还要测试代码做什么呢?

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <skipTests>true</skipTests>
    </configuration>
</plugin>

有时候用户不仅仅想跳过测试运行,还想临时性地跳过测试代码的编译,Maven也允许你这么做,但记住这是不推荐的:
$mvn package -D maven.test.skip=true


这时Maven的输出如下:

[INFO]---maven-compiler-plugin:2.0.2:testCompile(default-testcompile)@account-captcha---
[INFO]Not compiling test sources
[INFO]
[INFO]---maven-surefire-plugin:2.4.3:test(default-test)@account-captcha---
[INFO]Tests are skipped.

参数maven.test.skip同时控制了maven-compiler-plugin和 maven-surefire-plugin两个插件的行为,测试代码编译跳过了,测试运行也跳过了。


对应于命令行参数maven.test.skip的POM配置如代码所示,但这种方法也是不推荐使用的。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.1</version>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>

实际上 maven-compiler-plugin的 testCompile目标和 maven-surefire-plugin的 test目标都提供了一个参数skip用来跳过测试编译和测试运行,而这个参数对应的命令行表达式为maven.test.skip。
 

4️⃣ 动态指定要运行的测试用例

反复运行单个测试用例是日常开发中很常见的行为。例如,项目代码中有一个失败的测试用例,开发人员就会想要再次运行这个测试以获得详细的错误报告,在修复该测试的过程中,开发人员也会反复运行它,以确认修复代码是正确的。如果仅仅为了一个失败的测试用例而反复运行所有测试,未免太浪费时间了,当项目中测试的数目比较大的时候,这种浪费尤为明显。


maven-surefire-plugin提供了一个test参数让Maven用户能够在命令行指定要运行的测试用例。例如,如果只想运行account-captcha的RandomGeneratorTest,就可以使用如下命令:
$mvn test-Dtest=RandomGeneratorTest


这里test参数的值是测试用例的类名,这行命令的效果就是只有RandomGeneratorTest这一个测试类得到运行。
maven-surefire-plugin的test参数还支持高级一些的赋值方式,能让用户更灵活地指定需要运行的测试用例。例如:
$mvn test -Dtest=Random*Test


星号可以匹配零个或多个字符,上述命令会运行项目中所有类名以Random开头、Test 结尾的测试类。
除了星号匹配,还可以使用逗号指定多个测试用例:
$mvn test -Dtest=RandomGeneratorTest,AccountCaptchaServiceTest


该命令的test参数值是两个测试类名,它们之间用逗号隔开,其效果就是告诉Maven只运行这两个测试类。
当然,也可以结合使用星号和逗号。例如:
$mvn test -Dtest=Random*Test,AccountCaptchaServiceTest

需要注意的是,上述几种从命令行动态指定测试类的方法都应该只是临时使用,如果长时间只运行项目的某几个测试,那么测试就会慢慢失去其本来的意义。
test参数的值必须匹配一个或者多个测试类,如果maven-surefire-plugin找不到任何匹配的测试类,就会报错并导致构建失败。例如下面的命令没有匹配任何测试类:
$mvn test -Dtest
这样的命令会导致构建失败,输出如下:

[INFO]---maven-surefire-plugin:2.4.3:test(default-test)@account-captcha---
[INPO]Surefire report directory:D:\code\ch-10\account-aggregator\account-captcha\target\surefire-reports
------------------------------------------------
TESTS
------------------------------------------------
There are no tests to run.
Results:
Tests run:0,Failures:0,Errors:0,Skipped:0

[INFO]------------------------------------------------
[INFO]BUILD FAILURE
[INPO]------------------------------------------------
[INFO]Total time:1.747s
[INFO]Finished at: Sun Mar 28 17:00:27 CST 2023
[INFO]FinalMemory:2M/5M
[INFO]------------------------------------------------
[ERROR]Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:
2.4.3:test(default-test)on project account-captcha:No tests were executed!(Set-DfailIfNoTests=false to ignore this error.)->[Help1]
[ERROR]
[ERROR]To see the full stack trace of the errors,re-run Maven with the -e switch.

根据错误提示可以加上-DfaiIlfNoTests=false,告诉maven-surefire-plugin即使没有任何测试也不要报错:
$mvn test -Dtest-DfaillfNoTests=false

这样构建就能顺利执行完毕了。可以发现,实际上使用命令行参数-Dtest-DfaillfNoTests=false是另外一种跳过测试的方法。
我们看到,使用test参数用户可以从命令行灵活地指定要运行的测试类。可惜的是,maven-surefire-plugin并没有提供任何参数支持用户从命令行跳过指定的测试类,好在用户可以通过在POM中配置maven-surefire-plugin排除特定的测试类。

5️⃣ 包含与排除测试用例

上文第2节介绍了一组命名模式,符合这一组模式的测试类将会自动执行。Maven提倡约定优于配置原则,因此用户应该尽量遵守这一组模式来为测试类命名。即便如此,maven-surefire-plugin还是允许用户通过额外的配置来自定义包含一些其他测试类,或者排除一些符合默认命名模式的测试类。

例如,由于历史原因,有些项目所有测试类名称都以Tests结尾,这样的名字不符合默认的3种模式,因此不会被自动运行,用户可以通过代码所示的配置让Maven自动运行这些测试。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <includes>
            <include>**/*Tests.java</include>
        </includes>
    </configuration>
</plugin>

上述代码清单中使用了 **/*Tests.java来匹配所有以Tests结尾的Java类,两个星号**用来匹配任意路径,一个星号*匹配除路径风格符外的0个或者多个字符。
类似地,也可以使用excludes元素排除一些符合默认命名模式的测试类,如下代码所示。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <excludes>
            <exclude>**/*ServiceTest.java</exclude>
            <exclude>**/TempDaoTest.java</exclude>
        </excludes>
    </configuration>
</plugin>

上述代码清单排除了所有以ServiceTest结尾的测试类,以及一个名为TempDaoTest的测试类。它们都符合默认的命名模式**/*Test.java,不过,有了excludes配置后,maven-surefire-plugin将不再自动运行它们。


6️⃣ 测试报告

除了命令行输出,Maven用户可以使用maven-surefire-plugin等插件以文件的形式生成更丰富的测试报告。

6.1基本的测试报告

默认情况下,maven-surefire-plugin会在项目的 target/surefire-reports目录下生成两种格式的错误报告:

  • 简单文本格式
  • 与JUnit兼容的XML格式

例如,运行 1.3节代码中的RandomGeneratofTest后会得到一个名为 com.xiaoshan.mvnbook.account.captcha.RandomGeneratorfTest.txt的简单文本测试报告和一个名为TEST-com.xiaoshan.mvnbook.account.captcha.RandomGeneratorTest.xml的 XML测试报告。前者的内容十分简单:

----------------------------------------------------------------
Test set:com.xiaoshan.mvnbook.account.captcha.RandomGeneratorTest
----------------------------------------------------------------
Tests run:1,Failures:0,Errors:0,Skipped:0,Time elapsed:0.029sec


这样的报告对于获得信息足够了,XML格式的测试报告主要是为了支持工具的解析,如Eclipse的JUnit插件可以直接打开这样的报告。

由于这种XML格式已经成为了Java单元测试报告的事实标准,一些其他工具也能使用它们。例如,持续集成服务器Hudson就能使用这样的文件提供持续集成的测试报告。
以上展示了一些运行正确的测试报告,实际上,错误的报告更具价值。我们可以修改 1.3节代码中的AccountCaptchaServiceTest让一个测试失败,这时得到的简单文本报告会是这样:

-----------------------------------------------------------------------
Test set:com.xiaoshan.mvnbook.account.captcha.AccountCaptchaServiceTest
-----------------------------------------------------------------------
Tests run:3,Failures:1,Errors:0,Skipped:0,Time elapsed:0.932 sec
FAILURE!
testValidateCaptchaCorrect(com.xiaoshan.mvnbook.account.captcha.AccountCaptchaserviceTest)Time elapsed:0.047 sec <<< FAILURE!
Java,lang.AssertionError:
    at org.junit.Assert.fail(Assert.java:91)
    at org.jundt.Assert.assertTrue(Assert.java:43)
    at org.junlt.Assert.assertTrue(Assert.java:54)
    at com.xiaoshan.mvnbook.account.captcha.AccountCaptchaServiceTest.testvalidateCaptchaCorrect(AccountCaptchaServiceTest.java:66)


报告说明了哪个测试方法失败、哪个断言失败以及具体的堆栈信息,用户可以据此快速地寻找失败原因。该测试的XML格式报告用EelipseJUnit插件打开,从所示的堆栈信息中可以看到,该测试是由maven-surefire-plugin发起的。

6.2 测试覆盖率报告

测试覆盖率是衡量项目代码质量的一个重要的参考指标。Cobertura是一个优秀的开源测试覆盖率统计工具(详见http://cobertura.soureeforge.net/), Maven通过cobertura-maven-plugin与之集成,用户可以使用简单的命令为Maven项目生成测试覆盖率报告。例如,可以在account-captcha目录下运行如下命令生成报告:
$mvn cobertura:cobertura
接着打开项目目录 target/site/cobertura/下的index.html文件,就能看到测试覆盖率报告。单击具体的类,还能看到精确到行的覆盖率报告。

7️⃣ 运行 TestNG 测试

TestNG是Java社区中除JUnit之外另一个流行的单元测试框架。NG是NextGeneration的缩写,译为“下一代”。TestNG在JUnit的基础上增加了很多特性,读者可以访问其站点 http://testng.org/ 获取更多信息。值得一提的是,《Next Generation Java Testing》(Java测试新技术,中文版已由机械工业出版社引进出版,书号为978-7-111-24550-6) 一书专门介绍TestNG和相关测试技巧。
使用Maven运行TestNG十分方便。以1.3节中的account-captcha测试代码为例,首先需要删除POM中的JUnit依赖,加入TestNG依赖,见代码:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>5.9</version>
    <scope>test</scope>
    <classifier>jdk15</classifier>
</dependency>              

与JUnit类似,TestNG的依赖范围应为test。此外,TestNG使用 classifier jdk15和jdk14为不同的Java平台提供支持。
下一步需要将对JUnit的类库引用更改成对TestNG的类库引用。下表给出了常用类库的对应关系。

JUnit类

TestNG类

org. junit. Test

org. testng. annotations. Test

标注方法为测试方法

org. junit. Assert

org. testng. Asser

检查测试结果

org. junit. Before

org. testng. annotations. BeforeMethod

标注方法在每个测试方法之前运行

org. junit. After

org. testng. annotations. AfterMethod

标注方法在每个测试方法之后运行

org. junit. BeforeClass

org. testng. annotations. BeforeClass

标注方法在所有测试方法之前运行

org. junit. AfterClass

org. testng. annotations. AfterClass

标注方法在所有测试方法之后运行

将JUnit的类库引用改成TestNG之后,在命令行输入mvn test, Maven就会自动运行那些符合命名模式的测试类。这一点与运行JUnit测试没有区别。
TestNG允许用户使用一个名为testng.xml的文件来配置想要运行的测试集合。例如,可以在account-captcha的项目根目录下创建一个testng.xml文件,配置只运行RandomGeneratorTest,如代码所示:

<?xml version="1.0" encoding="UTF-8"?>

<suite name="Suitel" verbose="1">
    <test name="Regression1">
        <classes>
            <class name="com.xiaoshan.mvnbook.account.captcha.RandomGeneratorTest"/>
        </classes>	
    </test>
</suite>

同时再配置maven-surefire-plugin使用该testng.xml,如代码所示:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <suiteXmlFiles>
            <suiteXmlFile>testng.xml</suiteXmlFile>
        </suiteXmlFiles>
    </configuration>
</plugin>

TestNG较JUnit的一大优势在于它支持测试组的概念,如下的注解会将测试方法加入到两个测试组util和medium中:
@Test(groups={"util","medium"})

由于用户可以自由地标注方法所属的测试组,因此这种机制能让用户在方法级别对测试进行归类。这一点JUnit无法做到,它只能实现类级别的测试归类。Maven用户可以使用代码所示的配置运行一个或者多个TestNG测试组。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-piugin</artifactId>
    <version>2.5</version>
    <configuration>
        <groups>util,medium</groups>
    </configuration>
</plugin>

由于篇幅所限,这里不再介绍更多TestNG的测试技术,感兴趣的读者请访问TestNG 站点。

8️⃣ 重用测试代码

优秀的程序员会像对待产品代码一样细心维护测试代码,尤其是那些供具体测试类继承的抽象类,它们能够简化测试代码的编写。还有一些根据具体项目环境对测试框架的扩展,也会被大范围地重用。


在命令行运行mvn package的时候,Maven会将项目的主代码及资源文件打包,将其安装或部署到仓库之后,这些代码就能为他人使用,从而实现Maven项目级别的重用。默认的打包行为是不会包含测试代码的,因此在使用外部依赖的时候,其构件一般都不会包含测试代码。
然后,在项目内部重用某个模块的测试代码是很常见的需求,可能某个底层模块的测试代码中包含了一些常用的测试工具类,或者一些高质量的测试基类供继承。这个时候Maven用户就需要通过配置maven-jar-plugin将测试类打包,如代码所示:

<plugin>
    <groupId>org.apache,maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.2</version>
    <executions>
        <execution>
            <goals>
                <goal>test-jar</goal>
            </goals>
        </execution>
    </executions>
<plugin>        

maven-jar-plugin有两个目标,分别是jar和testjar,前者通过Maven的内置绑定在default生命周期的package阶段运行,其行为就是对项目主代码进行打包,而后者并没有内置绑定,因此上述的插件配置显式声明该目标来打包测试代码。通过查询该插件的具体信息可以了解到,test-jar的默认绑定生命周期阶段为 package,因此当运行mvn clean package后就会看到如下输出:

[INFO]---maven-jar-plugin:2.2:jar (default-jar)@account-captcha ---
[INFO] Building jar:D:\code\ch-10\account-aggregator\account-captcha\target\account-captcha-1.0.0-SNAPSHOT.jar
[INFO]
[INFO]---maven-jar-plugin:2.2:test-jar (default)@account-captcha ---
[INFO] Building jar:D:\code\ch-10\account-aggregator\account-captcha\target\account-captcha-1.0.0-SNAPSHOT-tests.jar

maven-jar-plugin的两个目标都得以执行,分别打包了项目主代码和测试代码。现在,就可以通过依赖声明使用这样的测试包构件了,如代码所示:

<dependency>
    <groupId>com.xiaoshan.mvnbook.account</groupId>
    <artifactId>account-captcha</artifactId>
    <version>1.0.0-SNAPSHOT<version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>

上述依赖声明中有一个特殊的元素 type, 所有测试包构件都使用特殊的 test-jar 型。需要注意的是,这类型的依赖同样都使用test依赖范围。

总结

本文的主题是Maven与测试的集成,不过在讲述具体的测试技巧之前先实现了背景案例的account-captcha模块,这一模块的测试代码也成了本章其他内容良好的素材。maven-surefire-plugin是Maven背后真正执行测试的插件,它有一组默认的文件名模式来匹配并自动运行测试类。用户还可以使用该插件来跳过测试、动态执行测试类、包含或排除测试等。maven-surefire-plugin能生成基本的测试报告,除此之外还能使用cobertura-maven-plugin生成测试覆盖率报告。
除了主流的JUnit之外,本章还讲述了如何与TestNG集成,最后介绍了如何重用测试代码。

温习回顾上一篇(点击跳转):【Maven教程】(八):使用 Nexus 创建私服 ~icon-default.png?t=N7T8https://blog.csdn.net/LVSONGTAO1225/article/details/132475920

继续阅读下一篇(点击跳转):【Maven教程】(十):使用 Hudson 进行持续集成—— 从Hudson的安装到任务创建 ~icon-default.png?t=N7T8https://blog.csdn.net/LVSONGTAO1225/article/details/134035587

欢迎三连 相互支持

   

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/LVSONGTAO1225/article/details/133948946

智能推荐

解决win10/win8/8.1 64位操作系统MT65xx preloader线刷驱动无法安装_mt65驱动-程序员宅基地

文章浏览阅读1.3w次。转载自 http://www.miui.com/thread-2003672-1-1.html 当手机在刷错包或者误修改删除系统文件后会出现无法开机或者是移动定制(联通合约机)版想刷标准版,这时就会用到线刷,首先就是安装线刷驱动。 在XP和win7上线刷是比较方便的,用那个驱动自动安装版,直接就可以安装好,完成线刷。不过现在也有好多机友换成了win8/8.1系统,再使用这个_mt65驱动

SonarQube简介及客户端集成_sonar的客户端区别-程序员宅基地

文章浏览阅读1k次。SonarQube是一个代码质量管理平台,可以扫描监测代码并给出质量评价及修改建议,通过插件机制支持25+中开发语言,可以很容易与gradle\maven\jenkins等工具进行集成,是非常流行的代码质量管控平台。通CheckStyle、findbugs等工具定位不同,SonarQube定位于平台,有完善的管理机制及强大的管理页面,并通过插件支持checkstyle及findbugs等既有的流..._sonar的客户端区别

元学习系列(六):神经图灵机详细分析_神经图灵机方法改进-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏27次。神经图灵机是LSTM、GRU的改进版本,本质上依然包含一个外部记忆结构、可对记忆进行读写操作,主要针对读写操作进行了改进,或者说提出了一种新的读写操作思路。神经图灵机之所以叫这个名字是因为它通过深度学习模型模拟了图灵机,但是我觉得如果先去介绍图灵机的概念,就会搞得很混乱,所以这里主要从神经图灵机改进了LSTM的哪些方面入手进行讲解,同时,由于模型的结构比较复杂,为了让思路更清晰,这次也会分开几..._神经图灵机方法改进

【机器学习】机器学习模型迭代方法(Python)-程序员宅基地

文章浏览阅读2.8k次。一、模型迭代方法机器学习模型在实际应用的场景,通常要根据新增的数据下进行模型的迭代,常见的模型迭代方法有以下几种:1、全量数据重新训练一个模型,直接合并历史训练数据与新增的数据,模型直接离线学习全量数据,学习得到一个全新的模型。优缺点:这也是实际最为常见的模型迭代方式,通常模型效果也是最好的,但这样模型迭代比较耗时,资源耗费比较多,实时性较差,特别是在大数据场景更为困难;2、模型融合的方法,将旧模..._模型迭代

base64图片打成Zip包上传,以及服务端解压的简单实现_base64可以装换zip吗-程序员宅基地

文章浏览阅读2.3k次。1、前言上传图片一般采用异步上传的方式,但是异步上传带来不好的地方,就如果图片有改变或者删除,图片服务器端就会造成浪费。所以有时候就会和参数同步提交。笔者喜欢base64图片一起上传,但是图片过多时就会出现数据丢失等异常。因为tomcat的post请求默认是2M的长度限制。2、解决办法有两种:① 修改tomcat的servel.xml的配置文件,设置 maxPostSize=..._base64可以装换zip吗

Opencv自然场景文本识别系统(源码&教程)_opencv自然场景实时识别文字-程序员宅基地

文章浏览阅读1k次,点赞17次,收藏22次。Opencv自然场景文本识别系统(源码&教程)_opencv自然场景实时识别文字

随便推点

ESXi 快速复制虚拟机脚本_exsi6.7快速克隆centos-程序员宅基地

文章浏览阅读1.3k次。拷贝虚拟机文件时间比较长,因为虚拟机 flat 文件很大,所以要等。脚本完成后,以复制虚拟机文件夹。将以下脚本内容写入文件。_exsi6.7快速克隆centos

好友推荐—基于关系的java和spark代码实现_本关任务:使用 spark core 知识完成 " 好友推荐 " 的程序。-程序员宅基地

文章浏览阅读2k次。本文主要实现基于二度好友的推荐。数学公式参考于:http://blog.csdn.net/qq_14950717/article/details/52197565测试数据为自己随手画的关系图把图片整理成文本信息如下:a b c d e f yb c a f gc a b dd c a e h q re f h d af e a b gg h f bh e g i di j m n ..._本关任务:使用 spark core 知识完成 " 好友推荐 " 的程序。

南京大学-高级程序设计复习总结_南京大学高级程序设计-程序员宅基地

文章浏览阅读367次。南京大学高级程序设计期末复习总结,c++面向对象编程_南京大学高级程序设计

4.朴素贝叶斯分类器实现-matlab_朴素贝叶斯 matlab训练和测试输出-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏12次。实现朴素贝叶斯分类器,并且根据李航《统计机器学习》第四章提供的数据训练与测试,结果与书中一致分别实现了朴素贝叶斯以及带有laplace平滑的朴素贝叶斯%书中例题实现朴素贝叶斯%特征1的取值集合A1=[1;2;3];%特征2的取值集合A2=[4;5;6];%S M LAValues={A1;A2};%Y的取值集合YValue=[-1;1];%数据集和T=[ 1,4,-1;..._朴素贝叶斯 matlab训练和测试输出

Markdown 文本换行_markdowntext 换行-程序员宅基地

文章浏览阅读1.6k次。Markdown 文本换行_markdowntext 换行

错误:0xC0000022 在运行 Microsoft Windows 非核心版本的计算机上,运行”slui.exe 0x2a 0xC0000022″以显示错误文本_错误: 0xc0000022 在运行 microsoft windows 非核心版本的计算机上,运行-程序员宅基地

文章浏览阅读6.7w次,点赞2次,收藏37次。win10 2016长期服务版激活错误解决方法:打开“注册表编辑器”;(Windows + R然后输入Regedit)修改SkipRearm的值为1:(在HKEY_LOCAL_MACHINE–》SOFTWARE–》Microsoft–》Windows NT–》CurrentVersion–》SoftwareProtectionPlatform里面,将SkipRearm的值修改为1)重..._错误: 0xc0000022 在运行 microsoft windows 非核心版本的计算机上,运行“slui.ex