【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.0http:/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/beanshttp://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;@Beforepublic void prepare() throws Exception{ApplicatlonContext ctx = new ClassPathXmlApplicationContext("accounc-captcha.xml");service = (AccountCaptchaservice) ctx.getBean("accountCaptchaservice");}@Testpublic 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);}@Testpublic 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"));}@Testpublic 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 创建私服 ~》

⏩ 继续阅读下一篇(点击跳转)

欢迎三连 相互支持

   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/170167.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试知识储备--打包工具篇(webpack和vite)

1.vite常用配置 常用配置 1.preprocessorOptions 传递给 CSS 预处理器的配置选项 2.PostCSS 也是用来处理 CSS 的&#xff0c;只不过它更像是一个工具箱&#xff0c;可以添加各种插件来处理 CSS 3.resolve.extensions 导入时想要省略的扩展名列表。默认值为 [‘.mjs’, ‘.js’…

武汉洪山区申请ITSS认证和能力评估CS认证的好处

武汉洪山区企业申请ITSS认证和信息系统建设和服务能力评估CS认证的好处 ITSS认证和信息系统建设和服务能力评估CS认证是一种评估和认证企业信息系统建设及服务能力的标准。这个认证体系基于一套全面的标准&#xff0c;通过评估企业的技术能力、管理能力和服务水平&#xff0c;…

Kafka3.x安装以及使用

一、Kafka下载 下载地址&#xff1a;https://kafka.apache.org/downloads 二、Kafka安装 因为选择下载的是 .zip 文件&#xff0c;直接跳过安装&#xff0c;一步到位。 选择在任一磁盘创建空文件夹&#xff08;不要使用中文路径&#xff09;&#xff0c;解压之后把文件夹内容剪…

自媒体创业秘籍:视频号视频下载助你打造热门账号

​自媒体创业者们都知道&#xff0c;视频号已经成为拓展影响力和吸引更多用户的热门平台之一。然而&#xff0c;要想在这个竞争激烈的市场中脱颖而出&#xff0c;并打造一个热门账号&#xff0c;你需要掌握一些技巧和秘籍。在本文中&#xff0c;我将分享关于视频号视频下载的方…

基于卷积神经网络的乳腺癌分类 深度学习 医学图像 计算机竞赛

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

esp32-S3 + visual studio code 开发环境搭建

一、首先在下面链接网页中下载esp-idf v5.1.1离线安装包 &#xff0c;并安装到指定位置。dl.espressif.cn/dl/esp-idf/https://dl.espressif.cn/dl/esp-idf/ 安装过程中会提示需要长路径支持&#xff0c;所以windows系统需要开启长路径使能 Step 1&#xff1a; 打开运行&…

Day9力扣打卡

打卡记录 掷骰子等于目标和的方法数&#xff08;动态规划&#xff09; 链接 用 f[i][j] 表示投了 i 次投骰子得到点数总和&#xff0c;从而得到状态转移方程 f[i][j]f[i−1][j]f[i−1][j−1]⋯f[i−1][j−min(k−1,j)] 。 class Solution { public:int numRollsToTarget(int…

【分布式】: 幂等性和实现方式

【分布式】: 幂等性和实现方式 幂等&#xff08;idempotent、idempotence&#xff09;是一个数学与计算机学概念&#xff0c; 常见于抽象代数中。在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数&#xff0c;或幂等方法&#xff0c;是…

生成式 AI 如何释放开发者的生产力?

生成式 AI 可以将程序员的开发速率提高两倍。技术管理者有望通过 AIGC 应用&#xff0c;大幅缩短四类关键开发任务的完成时间&#xff0c;进而提升组织生产力。 ——麦肯锡《通过生成式 AI 释放开发者生产力》 01 生成式 AI 将如何影响研发效能&#xff1f; 麦肯锡最近的一项实…

Python 算法高级篇:分治算法的原理与应用

Python 算法高级篇&#xff1a;分治算法的原理与应用 1. 什么是分治算法&#xff1f;2. 分治算法的应用2.1 归并排序2.2 快速排序2.3 最大子数组问题2.4 汉诺塔问题 3. 代码示例3.1 分治算法求幂 4. 总结 分治算法是一种重要的算法设计技巧&#xff0c;它将一个大问题分解为多个…

SpringBoot自动配置原理解析 | 京东物流技术团队

1: 什么是SpringBoot自动配置 首先介绍一下什么是SpringBoot&#xff0c;SpringBoost是基于Spring框架开发出来的功能更强大的Java程序开发框架&#xff0c;其最主要的特点是&#xff1a;能使程序开发者快速搭建一套开发环境。SpringBoot能将主流的开发框架&#xff08;例如Sp…

Mybatis-Plus CRUD

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Mybatis-Plus CRUD 通用 Service CRUD 封装 IService 接口&#xff0c;进一步封装 CRUD 采用 get 查询、remove 删除 、list 查询集合、page 分页的前缀命名方式区分 …

用爬虫代码爬取高音质音频示例

目录 一、准备工作 1、安装Python和相关库 2、确定目标网站和数据结构 二、编写爬虫代码 1、导入库 2、设置代理IP 3、发送HTTP请求并解析HTML页面 4、查找音频文件链接 5、提取音频文件名和下载链接 6、下载音频文件 三、完整代码示例 四、注意事项 1、遵守法律法…

运维 | 使用 Docker 安装 Jenkins | Jenkins

运维 | 使用 Docker 安装 Jenkins | Jenkins 前言 本期内容主要是为了学习如何通过 Docker 安装Jenkins&#xff0c;仅作为记录与参考&#xff0c;希望对大家有所帮助。 准备工作 系统&#xff1a;CentOS 7.9配置&#xff1a;4c8g 快速安装 下面以 Docker 方式安装 Jenkin…

【java学习—八】单例设计模式(5)

文章目录 1. 相关概念2. 单例设计模式-饿汉式3. 单例设计模式-懒汉式4. 总结 1. 相关概念 单例&#xff1a;只有一个实例&#xff08;实例化对象&#xff09; 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的…

Android笔记

目录 触摸事件java弱引用WorkerThread注解NonNull注解 触摸事件 java弱引用 创建对象的弱引用&#xff0c;在没有强引用指向改对象的情况下&#xff0c;垃圾回收器可以将其回收 WorkerThread注解 NonNull注解 用在方法的参数前&#xff0c;表示该参数不能为空。

25.2 MySQL 运算符

1. 伪表 在MySQL中, DUAL是一个特殊的单行, 单列的虚拟表, 主要用于在SELECT语句中计算表达式或执行函数, 而不需要从实际的数据表中检索数据. 使用DUAL的原因主要有以下几点:* 1. 简化计算: 通过在SELECT语句中使用DUAL, 可以方便地计算表达式或执行函数, 而无需创建临时表或…

SpringMVC系列-5 消息转换器

背景 SpringMVC系列的第五篇介绍消息转换器&#xff0c;本文讨论的消息转换指代调用Controller接口后&#xff0c;对结果进行转换处理的过程。 内容包括介绍自定义消息转换器、SpringMVC常见的消息转换器、Spring消息转换器工作原理等三部分。 本文以 SpringMVC系列-2 HTTP请求…

Selenium获取百度百科旅游景点的InfoBox消息盒

前面我讲述过如何通过BeautifulSoup获取维基百科的消息盒&#xff0c;同样可以通过Spider获取网站内容&#xff0c;最近学习了SeleniumPhantomjs后&#xff0c;准备利用它们获取百度百科的旅游景点消息盒&#xff08;InfoBox&#xff09;&#xff0c;这也是毕业设计实体对齐和属…

酷开科技 | 酷开系统沉浸式大屏游戏更解压!

随着家庭娱乐需求日益旺盛&#xff0c;越来越多的家庭消费者和游戏玩家开始追求大屏游戏带来的沉浸感。玩家在玩游戏的时候用大屏能获得更广阔的视野和更出色的视觉包围感&#xff0c;因此用大屏玩游戏已经成为了一种潮流。用酷开系统玩大屏游戏&#xff0c;过瘾又刺激&#xf…