【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.mzph.cn/news/121599.shtml

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

相关文章

通过jsonobject.tostring 传字符串为有空格问题

目录 通过jsonobject.tostring 传字符串为有空格问题 1.问题原因解决思路解决方案总结参考 文章所属专区 项目问题解决 1.问题原因 通过JSONObject.toString()方法将字符串转换为JSON格式时&#xff0c;可能会出现空格的情况。这是因为JSONObject.toString()方法在生成JSON字…

Games104现代游戏引擎笔记 网络游戏进阶架构

Character Movement Replication 角色位移同步 玩家2的视角看玩家1的移动是起伏一截一截&#xff0c;并且滞后的 interpolation&#xff1a;内插值&#xff0c;在两个旧的但已知的状态计算 extrapolation&#xff1a;外插值&#xff0c;本质是预测 内插值&#xff1a;但网络随着…

ICLR 2023丨3DSQA:3D 场景中的情景问答

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/pdf/2210.07474.pdf 主页链接&#xff1a;http://sqa3d.github.io 图 1&#xff1a;3D 场景中情景问答 (SQA3D) 的任务图示。给定场景上下文 S&#xff08;例如&#…

Angular-03:组件模板

各种学习后的知识点整理归纳&#xff0c;非原创&#xff01; 组件模板 ① 数据绑定② 属性绑定③ 类名绑定④ 样式绑定⑤ 事件绑定⑥ 获取原生DOM对象6.1 在组件模板中获取6.2 在组件类中获取 ⑦ 双向数据绑定⑧ 内容投影8.1 select选择器8.2 单槽投影8.3 多槽投影 ⑨ 安全操作…

基于 Qt控制开发板 LED和C语言控制LED渐变亮度效果

## 资源简介 在STM32开发板,板载资源上有两个可自由控制的 LED。如下图原理 图其中我们以操作 LED1 为示例,LED1 为出厂系统的心跳指示灯。 ## 应用实例 想要控制这个 LED,首先出厂内核已经默认将这个 LED 注册成了 gpio-leds类型设备。所以我们可以直接在应用层接口直接…

基础课12——数据采集

数据采集是指从传感器和其它待测设备等模拟和数字被测单元中自动采集非电量或者电量信号,送到上位机中进行分析处理。数据采集系统是结合基于计算机或者其他专用测试平台的测量软硬件产品来实现灵活的、用户自定义的测量系统。采集一般是采样方式,即隔一定时间(称采样周期)对同…

【易售小程序项目】后端部署、Uniapp项目Web部署

文章目录 Uniapp项目Web打包部署为什么不部署小程序Web打包前对项目进行调整网站、小程序切换增加constant.js来控制常量将js绑定到main.js的全局变量中 WebSocket差异监听键盘呼出导航条打包部署 后端项目打包部署打包前准备打包部署 Uniapp项目Web打包部署 为什么不部署小程…

Python中json的用法

python 中 json的用法 一、JSON 的介绍二、json和python的转换1&#xff09; python 的字典或列表转换为json2) json转换为python的字典或列表 一、JSON 的介绍 Json本质上一个带有特定格式的字符串&#xff0c;json是一种在各个编程语言中流通的数据格式&#xff0c;负责不同…

rust学习

rust学习 String类型clone和copy结构体的内存分布for循环&#xff08;<font color red>important&#xff01;&#xff09;堆和栈数据结构vector panic失败就 panic: unwrap 和 expect传播错误 模式匹配忽略模式的值绑定 泛型特征Trait定义特征为类型实现特征孤儿规则使…

Centos安装RabbitMQ,JavaSpring发送RabbitMQ延迟延时消息,JavaSpring消费RabbitMQ消息

1&#xff0c;版本说明 erlang 和 rabbitmq 版本说明 https://www.rabbitmq.com/which-erlang.html 确认需要安装的mq版本以及对应的erlang版本。 2&#xff0c;下载安装文件 RabbitMQ下载地址&#xff1a; https://packagecloud.io/rabbitmq/rabbitmq-server Erlang下载地…

【C++项目】高并发内存池第七讲性能分析

目录 1.测试代码2.代码介绍3.运行结结果 1.测试代码 #include"ConcurrentAlloc.h" #include"ObjectPool.h" #include"Common.h" void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds) {std::vector<std::thread> vthread(…

接口自动化测试要做什么?一文3个步骤带你成功学会!

先了解下接口测试流程&#xff1a; 1、需求分析 2、Api文档分析与评审 3、测试计划编写 4、用例设计与评审 5、环境搭建&#xff08;工具&#xff09; 6、执行用例 7、缺陷管理 8、测试报告 了解了接口测试的工作流程&#xff0c;那"接口自动化测试"怎么弄&#xff1…

可视化 | (三)Edward Tufted基本设计准则

文章目录 &#x1f4da;Edward Tufted基本设计准则&#x1f407;Graphical Integrity&#x1f407;Lie Factor&#x1f407;Data-Ink&#x1f407;Chart Junks &#x1f4da;其他注意事项&#x1f407;Pie Charts&#x1f407;Rainbow Colormap&#x1f407;3D charts&#x1f…

基于华为云 IoT 物联网平台实现家居环境实时监控

01 智能家居环境监测 智能家居环境监测采用 Ruff 开发板作为主控&#xff0c;串口线连接温湿度传感器 DHT11 和空气质量传感器 SDS011&#xff0c;每5分钟采集一次数据&#xff0c;通过 MQTT 协议发送到华为云 IoT 物联网平台&#xff0c;并基于数据分析服务实时计算出整个家庭…

【java学习—十】异常(1)

文章目录 1. 概念1.1. 前言1.2. java中的异常 2. java运行时异常举例3. 总结 1. 概念 1.1. 前言 任何一种程序设计语言设计的程序在运行时都有可能出现错误&#xff0c;例如除数为 0 &#xff0c;数组下标越界&#xff0c;要读写的文件不存在等等。     捕获错误最理想的是…

【网络】详解http协议

目录 一、认识URLurlencode和urldecode 二、HTTP协议HTTP协议格式HTTP的方法HTTP的状态码HTTP常见Header 一、认识URL URL叫做统一资源定位符&#xff0c;也就是我们平时俗称的网址&#xff0c;是因特网的万维网服务程序上用于指定信息位置的表示方法。 urlencode和urldecode …

asp.net学生考试报名管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net学生考试报名管理系统是一套完善的web设计管理系统系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使 用c#语言开发 应用技术&#xff1a;asp…

MySQL数据库#6

Python操作mysql 在使用Python连接mysql之前我们需要先下载一个第三方的模块 pymysql的模块&#xff0c;导入后再进行操作。 操作步骤&#xff1a;1. 先连接mysql host&#xff0c;port&#xff0c;charset&#xff0c;username password 库&#xff0c;等等。 import pymysql…

面试题之JavaScript经典for循环(var let)

如果你也在面试找工作&#xff0c;那么也一定遇到过这道for循环打印结果的题&#xff0c;下面我们来探讨下 var循环 for(var i 0; i < 10; i) {setTimeout(function(){console.log(i)}); } 先把答案写出来 下面来讲一下原因&#xff1a; 划重点 ① var ②setTimeout() …

10款轻量型的嵌入式GUI库分享

LVGL LittlevGL是一个免费的开源图形库&#xff0c;提供了创建嵌入式GUI所需的一切&#xff0c;具有易于使用的图形元素、漂亮的视觉效果和低内存占用。 特点&#xff1a; 强大的构建模组 按钮、图表、列表、滑块、图像等 ​先进的图形 动画、反锯齿、半透明、平滑滚动 多样…