JUnit讲课文档.doc

本文介绍了JUnit4在Java单元测试中的应用,详细讲解了JUnit4的新特性,包括使用注释来标识测试方法、断言方法的增强、异常测试的改进等。并通过具体示例展示了如何在Eclipse中进行JUnit4测试。

JUnit4测试驱动开发

1预备知识

1.1可变长参数

1.2Assert

1.3Annotation

21.简介

JUnit是Java语言事实上的标准单元测试库。JUnit的易用性无疑是它受欢迎的主要原因。也许你会这样想:它做的事情不多,仅仅是做一些测试然后报告结果,JUnit应该是很简单的。

XP--敏捷编程--领袖

JUnit4.x是最初由ErichGammaKentBeck编写的,能够自动化测试Java代码的框架,JUnit的一大主要特点是,它在执行的时候,各个方法之间是相互独立的,一个方法的失败不会导致别的方法失败,方法之间也不存在相互依赖的关系,彼此是独立的。JUnit4是该库以来最具里程碑意义的一次发布。它的新特性主要是通过采用Java5中的标记(Annotation)而不是利用子类、反射或命名机制来识别测试,从而简化测试。

然而,JUnit仅仅是一个工具而已。真正的优势来自于JUnit所采用的思想和技术,而不是框架本身。单元测试、测试先行的编程和测试驱动的开发并非都要在JUnit中实现。JUnit本身的最后一次更新差不多是三年以前了。尽管它被证明比大多数框架更健壮、更持久,但是也发现了bug;而更重要的是,Java不断在发展。Java语言现在支持泛型(RawType)、枚举(Enum)、可变长度参数列表和注释,这些特性为可重用的框架设计带来了新的可能。

新版本的JUnit,它利用Java5的新特性(尤其是注释)的优势,使得单元测试比起用最初的JUnit来说更加简单。用Beck的话来说,“JUnit4的主题是通过进一步简化JUnit,鼓励更多的开发人员编写更多的测试。”JUnit4尽管保持了与现有JUnit3.8测试套件的向后兼容,但是它仍然承诺是自JUnit1.0以来Java单元测试方面最重大的改进。

2.1一个最简单的测试

2.1.13.8系列

以前所有版本的JUnit都使用命名约定和反射来定位测试。例如,下面的代码测试1+1等于2

importjunit.framework.TestCase;

publicclassAdditionTestextendsTestCase{

privateintx=1;

privateinty=1;

publicvoidtestAddition(){

intz=x+y;

assertEquals(2,z);

}

}

2.1.24.X系列

JUnit4中,测试是由@Test注释来识别的,如下所示:

importorg.junit.Test;

importjunit.framework.TestCase;

publicclassAdditionTestextendsTestCase{

privateintx=1;

privateinty=1;

@TestpublicvoidtestAddition(){

intz=x+y;

assertEquals(2,z);

}

}

使用注释的优点是不再需要将所有的方法命名为testFoo()testBar(),等等。而在3.8中如果你的方法不是以test开头的,将不被解释为测试方法。

这允许您遵循最适合您的应用程序的命名约定。

通过继承TestCase类的方式,仍然可以工作,但是您不再需要扩展它了。只要您用@Test来注释测试方法,就可以将测试方法放到任何类中。但是您需要导入junit.Assert类以访问各种assert方法,如下所示:

importorg.junit.Assert;

publicclassAdditionTest{

privateintx=1;

privateinty=1;

@Testpublicvoidaddition(){

intz=x+y;

Assert.assertEquals(2,z);

}

}

还可以使用JDK5中新特性(staticimport),使得与以前版本一样简单:

importstaticorg.junit.Assert.assertEquals;

publicclassAdditionTest{

privateintx=1;

privateinty=1;

@Testpublicvoidaddition(){

intz=x+y;

assertEquals(2,z);

}

}

这种方法使得测试受保护的方法非常容易,因为测试案例类现在可以扩展包含受保护方法的类了。

2.2什么是单元测试

单元测试是由程序员编写的,测试被测试代码的某一个很小的、特定的功能区域的代码。它可以用来确保在代码或者程序运行的环境中发生变化后,已经存在的功能还是能够执行的。

2.3在Eclipse中使用JUnit

准备

创建一个新的工程,并增加JUnit到库到项目的BuildPath

创建一个普通类

创建名字为"MyClass"Java文件

牋牋牋牋牋牋

publicclassMyClass{

牋爌ublicintmultiply(intx,inty){

牋牋牋returnx/y;

牋爙

}

创建测试类

创建测试套件

如果你有很多个测试,想要合并到一起来执行,你可以创建一个TestSuite.在一个TestSuite中的所有的测试将会被执行。

1SetUp和TearDown及断言方法

1.13.8系列

JUnit3测试运行程序(testrunner)会在运行每个测试之前自动调用setUp()方法。该方法一般会初始化字段,打开日志记录,重置环境变量,等等。例如,下面是摘自XOMXSLTransformTest中的setUp()方法:

protectedvoidsetUp(){

System.setErr(newPrintStream(newByteArrayOutputStream()));

inputDir=newFile("data");

inputDir=newFile(inputDir,"xslt");

inputDir=newFile(inputDir,"input");

}

JUnit3中,您使用tearDown()方法,该方法类似于我在XOM中为消耗大量内存的测试所使用的方法:

protectedvoidtearDown(){

doc=null;

System.gc();

}

1.24.X系列

JUnit4中,您仍然可以在每个测试方法运行之前初始化字段和配置环境。然而,完成这些操作的方法不再需要叫做setUp(),只要用@Before注释来指示即可,如下所示:

@Beforeprotectedvoidinitialize(){

System.setErr(newPrintStream(newByteArrayOutputStream()));

inputDir=newFile("data");

inputDir=newFile(inputDir,"xslt");

inputDir=newFile(inputDir,"input");

}

甚至可以用@Before来注释多个方法,这些方法都在每个测试之前运行:

@BeforeprotectedvoidfindTestDataDirectory(){

inputDir=newFile("data");

inputDir=newFile(inputDir,"xslt");

inputDir=newFile(inputDir,"input");

}

@BeforeprotectedvoidredirectStderr(){

System.setErr(newPrintStream(newByteArrayOutputStream()));

}

对于JUnit4,我可以给它取一个更自然的名称,并用@After注释它:

@AfterprotectedvoiddisposeDocument(){

doc=null;

System.gc();

}

@Before一样,也可以用@After来注释多个清除方法,这些方法都在每个测试之后运行。

最后,您不再需要在超类中显式调用初始化和清除方法,只要它们不被覆盖即可,测试运行程序将根据需要自动为您调用这些方法。超类中的@Before方法在子类中的@Before方法之前被调用(这反映了构造函数调用的顺序)。@After方法以反方向运行:子类中的方法在超类中的方法之前被调用。否则,多个@Before@After方法的相对顺序就得不到保证。

1.3断言方法

3.8系列,每一个类都继承了一个叫做TestCase的类,而TestCase的父类是Assert

4.x系列,每一个可以不继承任何的类

,但是仍然可以直接使用

importstaticorg.junit.Assert.*;

3.8当中,所有的测试类必须都是TestCase的子类

4.X中,测试类可以是一个普通类,也可以去继承一个类或者实现一个接口

要实现测试,只需要在要测试的方法之前加@Test注释

same---相同

equal---相等

1注释(Annotations)

Annotation

含义

@Testpublicvoidmethod()

定义一个要测试的方法

@Beforepublicvoidmethod()

在每一个测试之前都会被执行的方法,这个方法常常用来进行一

些测试环境的准备,比喻说读入输入数据,初始化类

@Afterpublicvoidmethod()

@Before进行对应,做一个清理工作

@BeforeClasspublicvoidmethod()

在所有的测试开始之前执行,这个方法在类运行的时候运行,

而且只会运行一次,所以常常用来做一些所有的方法都要依赖

到工作,比喻说,数据库的链接。

@AfterClasspublicvoidmethod()

@BeforeClass进行对应,做一些类级别的清理工作

@Ignore

表明方法是被忽略的,这个方法非常实用,比喻你的方法已经

修改,但是对应的测试方法还没有得到一致的修改的时候,可以

忽略掉这个测试方法先。

@Test(expected=IllegalArgumentException.class)

检查测试方法是不是抛出了对应的异常

@Test(timeout=100)

如果方法的执行操作所耗费的毫秒数>100MS,那么方法失败。

表格1Annotations

2新的断言

JUnit4为比较数组添加了两个assert()方法:

publicstaticvoidassertEquals(Object[]expected,Object[]actual)

publicstaticvoidassertEquals(Stringmessage,Object[]expected,

Object[]actual)

这两个方法以最直接的方式比较数组:如果数组长度相同,且每个对应的元素相同,则两个数组相等,否则不相等。数组为空的情况也作了考虑。

1套件范围的初始化

JUnit4也引入了一个JUnit3中没有的新特性:类范围的setUp()tearDown()方法。任何用@BeforeClass注释的方法都将在该类中的测试方法运行之前刚好运行一次,而任何用@AfterClass注释的方法都将在该类中的所有测试都运行之后刚好运行一次。

例如,假设类中的每个测试都使用一个数据库连接、一个网络连接、一个非常大的数据结构,或者还有一些对于初始化和事情安排来说比较昂贵的其他资源。不要在每个测试之前都重新创建它,您可以创建它一次,并还原它一次。该方法将使得有些测试案例运行起来快得多。例如,当我测试调用第三方库的代码中的错误处理时,我通常喜欢在测试开始之前重定向System.err,以便输出不被预期的错误消息打乱。然后我在测试结束后还原它,如下所示:

//Thisclasstestsalotoferrorconditions,which

//XalanannoyinglylogstoSystem.err.ThishidesSystem.err

//beforeeachtestandrestoresitaftereachtest.

privatePrintStreamsystemErr;

@BeforeClassprotectedvoidredirectStderr(){

systemErr=System.err;//Holdontotheoriginalvalue

System.setErr(newPrintStream(newByteArrayOutputStream()));

}

@AfterClassprotectedvoidtearDown(){

//restoretheoriginalvalue

System.setErr(systemErr);

}

没有必要在每个测试之前和之后都这样做。但是一定要小心对待这个特性。它有可能会违反测试的独立性,并引入非预期的混乱。如果一个测试在某种程度上改变了@BeforeClass所初始化的一个对象,那么它有可能会影响其他测试的结果。它有可能在测试套件中引入顺序依赖,并隐藏bug。与任何优化一样,只在剖析和基准测试证明您具有实际的问题之后才实现这一点。这就是说,我看到了不止一个测试套件运行时间如此之长,以至不能像它所需要的那样经常运行,尤其是那些需要建立很多网络和数据库连接的测试。(例如,LimeWire测试套件运行时间超过两小时。)要加快这些测试套件,以便程序员可以更加经常地运行它们,您可以做的就是减少bug

1测试异常

异常测试是JUnit4中的最大改进。旧式的异常测试是在抛出异常的代码中放入try块,然后在try块的末尾加入一个fail()语句。例如,该方法测试被零除抛出一个ArithmeticException

publicvoidtestDivisionByZero(){

try{

intn=2/0;

fail("Dividedbyzero!");

}

catch(ArithmeticExceptionsuccess){

assertNotNull(success.getMessage());

}

}

该方法不仅难看,而且试图挑战代码覆盖工具,因为不管测试是通过还是失败,总有一些代码不被执行。在JUnit4中,您现在可以编写抛出异常的代码,并使用注释来声明该异常是预期的:

@Test(expected=ArithmeticException.class)

publicvoiddivideByZero(){

intn=2/0;

}

如果该异常没有抛出(或者抛出了一个不同的异常),那么测试就将失败。但是如果您想要测试异常的详细消息或其他属性,则仍然需要使用旧式的try-catch样式。

1可以忽略到测试

也许您有一个测试运行的时间非常地长。不是说这个测试应该运行得更快,而是说它所做的工作从根本上比较复杂或缓慢。需要访问远程网络服务器的测试通常都属于这一类。如果您不在做可能会中断该类测试的事情,那么您可能想要跳过运行时间长的测试方法,以缩短编译-测试-调试周期。或者也许是一个因为超出您的控制范围的原因而失败的测试。例如,W3CXInclude测试套件测试Java还不支持的一些Unicode编码的自动识别。不必老是被迫盯住那些红色波浪线,这类测试可以被注释为@Ignore,如下所示:

//Javadoesn'tyetsupport

//theUTF-32BEandUTF32LEencodings

@IgnorepublicvoidtestUTF32BE()

throwsParsingException,IOException,XIncludeException{

Fileinput=newFile("data/xinclude/input/UTF32BE.xml");

Documentdoc=builder.build(input);

Documentresult=XIncluder.resolve(doc);

DocumentexpectedResult=builder.build(newFile(outputDir,"UTF32BE.xml"));

assertEquals(expectedResult,result);

}

测试运行程序将不运行这些测试,但是它会指出这些测试被跳过了。

但是一定要小心。最初编写这些测试可能有一定的原因。如果永远忽略这些测试,那么它们期望测试的代码可能会中断,并且这样的中断可能不能被检测到。忽略测试只是一个权宜之计,不是任何问题的真正解决方案。

2时间测试

3Failure和Error

Failure指的是由于预期的结果与实际运行的测试的结果不同而导致的實際運行單元的結果不同所導致,例如当使用assertEquals()或其它assertXXX()方法断言失败时,就会报出Failure,如果发现Faulure,你就要去检查你的测试方法或者是被测试方法中编写的逻辑是否有误。

Error指的是编写程序时没有考虑到的问题。在执行测试的断言之前,程序就因为某种类型的意外而停止,比喻说我们在操作数组的时候,因为存取超出索引会引发ArrayIndexOutOfBoundsException,这个时候程序就会报出Error,程序将无法运行下去,提前结束,这个时候你要检查被测试方法中是不是有欠缺考虑到地方。

看一个具体的例子:

如果执行这个测试的话,Eclipse会提示你创建一个名字为ObjectArray的类,在其中要求你创建一个名字为setObject的方法。

假设你创建的类的样子如下:

publicclassObjectArray{

牋publicObjectsetObject(inti,ObjecttestObj){

牋爎eturnnull;

牋}

}

如果现在你来运行测试的话,会出现以下类似的界面:

大家注意看,里面报出了Failures:1

为什么呢?

Objectobj=objArr.setObject(0,testObj);

牋assertEquals(testObj,obj);

看这里的断言,Objectobj这一行很明显是可以运行的,只是返回的

是一个null值,接下来,assertEquals(...)也是可以运行的,只不过

期望值为testObjc,而实际值为一个null,所以会有一个failure产生。

这是很正常的一种现象。

如果我们把被测试类改变一下:

publicclassObjectArray{

牋Objectobjs[];

牋publicObjectsetObject(inti,ObjecttestObj){

牋爎eturnobjs[i];

牋}

}

将会产生如下的结果:

那么为什么会产生这样的结果呢?

因为我们对源代码做了修改以后,在执行到

Objectobj=objArr.setObject(0,testObj);

牋assertEquals(testObj,obj);

时候,首先执行Objectobj....这句,在这一句的时候,被测试代码

中出现了java.lang.NullPointerException异常,这个可以通过

1EasyMock

EasyMock总览

接下来,让我告诉大家如何使用JUnit和EasyMock框架来进行单元测试。

我们的第一个例子非常简单,但是在现实情况下,你通常是在一些类里使用另外的一些类。在进行真正的测试之前,你可能需要做很多的工作,比喻说安置大量的环境代码,启动一种大型的、复杂的系统,可能是数据库、功过刘或者是某一种类型的IDE环境,你的预设环境代码需要是系统进入某种特定的状态,以便按照测试所需要的方法进行响应。但是这种工作不大可能很快就能完成。

为了对一部分类进行单元测试,你需要建立和控制另外一些类。最好的办法就是为需要测试的类创建一个模拟对象。你可以自己手工的编写类,也可以使用EasyMock来产生这些对象。

模拟对象提供了一种经过证明是成功的解决方案。当我们很难或不可能为某种难以处理的资源创建需要的状态或者存取某种资源受限时,你就可以使用模拟对象。

模拟对象取代真实对象的位置,用于测试一些与真实对象进行交互或依赖于真实对象的功能。模拟对象背后的基本思想就是创建轻量级的、可控制的对象来代替为了编写测试为需要使用的对象。模拟对象还能够让你指定和测试你的代码与模拟对象本身之间的交互。

说的再直白一点,一个模拟对象就是一个简单的接口或者是类,在里面你可以定义一个特定的方法调用之后的简单的输出。

使用Junit和EasyMock

从EasyMock到主页上下载EasyMock,把下载得到的easymock.jar增加到你的classpath中。

创建一个Java工程JavaMockTest,创建下面的这些类。

IncomeCalculator是要被测试的类,这个类用来根据一个人的职位来计算一个人的工资的方法。

packageincome;

publicenumPosition{BOSS,PROGRAMMER,SURFER}

packageincome.exceptions;

publicclassPositionExceptionextendsRuntimeException{

privatestaticfinallongserialVersionUID=1L;

publicPositionException(Stringmessage){

super(message);

}

packageincome.exceptions;

publicclassCalcMethodExceptionextendsRuntimeException{

privatestaticfinallongserialVersionUID=1L;

publicCalcMethodException(Stringmessage){

super(message);

}

packageincome.method;

importincome.Position;

publicinterfaceICalcMethod{

publicabstractdoublecalc(Positionposition);

}

packageincome;

importincome.exceptions.CalcMethodException;

importincome.exceptions.PositionException;

importincome.method.ICalcMethod;

publicclassIncomeCalculator{

privateICalcMethodcalcMethod;

privatePositionposition;

publicvoidsetCalcMethod(ICalcMethodcalcMethod){

this.calcMethod=calcMethod;

}

publicvoidsetPosition(Positionposition){

this.position=position;

publicdoublecalc(){

if(calcMethod==null){

thrownewCalcMethodException("CalcMethodnotyetmaintained");

}

if(position==null){

thrownewPositionException("Positionnotyetmaintained");

}

returncalcMethod.calc(position);

}

使用Eclipse的JUnit功能为IncomeCalulator创建一个测试。在我的例子中,我同时创建一个新的源文件夹,名字为"test",我把我创建的测试类全部放到里面。

下面就是一个使用EasyMock创建的测试。

packageincome;

importstaticorg.junit.Assert.assertEquals;

importstaticorg.junit.Assert.fail;

importincome.exceptions.CalcMethodException;

importincome.exceptions.PositionException;

importincome.method.ICalcMethod;

importorg.easymock.EasyMock;

importorg.junit.Before;

importorg.junit.Test;

publicclassIncomeCalculatorTest{

privateICalcMethodcalcMethod;

privateIncomeCalculatorcalc;

@Before

publicvoidsetUp()throwsException{

calcMethod=EasyMock.createMock(ICalcMethod.class);

calc=newIncomeCalculator();

}

@Test

publicvoidtestCalc1(){

//Settinguptheexpectedvalueofthemethodcallcalc

EasyMock.expect(calcMethod.calc(Position.BOSS)).andReturn(70000.0).times(2);

EasyMock.expect(calcMethod.calc(Position.PROGRAMMER)).andReturn(50000.0);

//Setupisfinishedneedtoactivatethemock

EasyMock.replay(calcMethod);

calc.setCalcMethod(calcMethod);

try{

calc.calc();

fail("Exceptiondidnotoccur");

}catch(PositionExceptione){

}

calc.setPosition(Position.BOSS);

assertEquals(70000.0,calc.calc());

assertEquals(70000.0,calc.calc());

calc.setPosition(Position.PROGRAMMER);

assertEquals(50000.0,calc.calc());

calc.setPosition(Position.SURFER);

EasyMock.verify(calcMethod);

}

@Test(expected=CalcMethodException.class)

publicvoidtestNoCalc(){

calc.setPosition(Position.SURFER);

calc.calc();

}

@Test(expected=PositionException.class)

publicvoidtestNoPosition(){

EasyMock.expect(calcMethod.calc(Position.BOSS)).andReturn(70000.0);

EasyMock.replay(calcMethod);

calc.setCalcMethod(calcMethod);

calc.calc();

}

@Test(expected=PositionException.class)

publicvoidtestCalc2(){

//Settinguptheexpectedvalueofthemethodcallcalc

EasyMock.expect(calcMethod.calc(Position.SURFER)).andThrow(newPositionException("Don'tknowthisguy")).times(1);

//Setupisfinishedneedtoactivatethemock

EasyMock.replay(calcMethod);

calc.setPosition(Position.SURFER);

calc.setCalcMethod(calcMethod);

calc.calc();

}

expect方法告知EasyMock希望得到一个特定的方法,使用一些特定的参数,addReturn为某一个方法定义返回值。times方法定义了模拟对象将被调用几次。reply方法需要在让模拟对象可用之前被调用。执行完测试之后,你可以调用verify方法来检查模拟对象是不是跟预期的一样被调用了。

1使用HttpUnit测试Html

测试1:测试网页是否存在

要测试一個网页是否存在,只要简单的通过WebConversation的getResponse()方法即可,例如:

WebConversationwebConversation=newWebConversation();

webConversation.getResponse(

牋牋牋牋牋牋"http://localhost:8080/httpUnit/");

如果找不到网页,則會引发HttpNotFoundException,由于不是断言错误,所以这会在JUnit中产生一個Error。

测试2:Get、Post

您可以分別使用GetMethodWebRequest或PostMethodWebRequest來发出Get或Post请求,例如:

WebConversationwebConversation=newWebConversation();

WebRequestrequest=newGetMethodWebRequest("http://localhost:8080/httpUnit/");

WebResponseresponse=webConversation.getResponse(request);

要在请求中加上參數,可以使用setParamter()方法,例如:

request.setParameter("username","taobaoge");

测试3:取得表格信息

您可以从WebResponse中取得相关的HTML信息,假设网页中有如下这样的一个表格:

书籍名称

设计模式(DesignPattern)

软件版本

书籍版次

第二版

修改时间

2004/12/26

下面的程序演示如何取得表格相关信息进行测试:

WebConversationwebConversation=newWebConversation();

WebResponseresponse=webConversation.getResponse("http://localhost:8080/httpUnit/tableTest.jsp");

WebTablewebTable=response.getTables()[0];

assertEquals(2,webTable.getColumnCount());

TableCellcell=webTable.getTableCell(2,0);

assertEquals("书籍版次",cell.getText());

测试4:跟随超链接

网页中有很多的超链接,我们可以使用HttpUnit来模拟超链接的点击,例如网页中如果有个超链接如下:

<ahref="httpUnitABC.jsp">HttpUnitABC</a>

则可以使用下面的程序来吵到链接,然后模拟一个click动作来跟随这个超链接:

WebConversationwebConversation=newWebConversation();

WebResponseresponse=webConversation.getResponse("http://localhost:8080/httpUnit/");

WebLinklink=response.getLinkWith("HttpUnitABC");

WebRequestclickRequest=link.getRequest();

WebResponselinkPage=webConversation.getResponse(clickRequest);

测试5:测试Cookie

如果被测试的网页需要Cookie信息,您可以使用WebConversation的addCookie()方法发送Cookie信息

给网页,例如:

WebConversationwebConversation=newWebConversation();

webConversation.addCookie("user","taobaoge");

WebResponseresponse=webConversation.getResponse("http://localhost:8080/httpUnit/");

如果网页中包含了Cookie,您可以使用getCookieValue()方法取得网页中包含的Cookie信息,若网页包括下面的Scriptlet:

<%

牋Cookiecookie=newCookie("customerId","12345");

牋response.addCookie(cookie);

%>

可使用下面的方式來测试传回的Cookie信息:

assertEquals("taobaoge",webConversation.getCookieValue("user"));

测试6:Authorization

如果您的网页中有预设的HTTP基本验证,则可以使用WebConversation的setAuthorization()方法來设定验证信息,例如:

webConversation.setAuthorization("justin","123456");

测试7:设定代理

有的時候,您测试的目的网页可能必须通过代理服务器才能连上,你可以通过设定系统属性来设定HttpUnit使用代理,例如:

Exception in thread "main" java.lang.NoSuchMethodError: 'java.util.Set org.junit.platform.engine.TestDescriptor.getAncestors()' at org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener.getTestClassNames(StackTracePruningEngineExecutionListener.java:50) at org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener.executionFinished(StackTracePruningEngineExecutionListener.java:39) at org.junit.platform.launcher.core.DelegatingEngineExecutionListener.executionFinished(DelegatingEngineExecutionListener.java:46) at org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener.reportEngineFailure(OutcomeDelayingEngineExecutionListener.java:83) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:203) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
11-07
<think>我们正在处理一个JUnit版本冲突导致的NoSuchMethodError异常。具体错误信息为: java.lang.NoSuchMethodError: 'java.util.Set org.junit.platform.engine.TestDescriptor.getAncestors()' 这个错误通常是由于JUnit Platform(junit-platform-engine)的版本不兼容引起的。在JUnit 5.7.0版本中,TestDescriptor接口添加了`getAncestors()`方法。因此,如果测试运行时加载了一个旧版本的junit-platform-engine(低于5.7.0),而测试代码或某个依赖项期望使用新版本的方法,就会抛出此异常。 根据用户之前的问题,我们已经尝试添加了junit-platform-launcher依赖,但问题可能出现在多个JUnit依赖版本不一致上。 ### 解决步骤: 1. **确认依赖树中的JUnit版本** 使用Maven命令检查依赖树,特别是所有JUnit相关的依赖(如junit-jupiter, junit-platform-engine, junit-platform-launcher等): ```bash mvn dependency:tree -Dincludes=org.junit.platform,org.junit.jupiter ``` 2. **统一JUnit版本** 推荐在`<dependencyManagement>`中统一管理JUnit版本,或者显式指定所有JUnit依赖的版本。例如: ```xml <properties> <junit-jupiter.version>5.10.2</junit-jupiter.version> <junit-platform.version>1.10.2</junit-platform.version> </properties> <dependencies> <!-- 其他依赖 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>${junit-platform.version}</version> <scope>test</scope> </dependency> </dependencies> ``` 注意:junit-jupiter和junit-platform-launcher的版本需要兼容。通常,它们的主版本号应该一致(例如都是5.10.x和1.10.x)。 3. **排除旧版本** 如果依赖树中有其他依赖引入了旧版本的JUnit依赖,需要排除。例如: ```xml <dependency> <groupId>某个依赖</groupId> <artifactId>某个artifact</artifactId> <version>X.Y.Z</version> <exclusions> <exclusion> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-engine</artifactId> </exclusion> <exclusion> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> </exclusion> </exclusions> </dependency> ``` 4. **清理并重新构建** 在修改依赖后,执行Maven清理和重新构建: ```bash mvn clean install ``` ### 验证: 运行测试`UserMapperTest.testList`,确认问题是否解决。 ### 常见原因分析: - 项目中可能存在多个版本的JUnit Platform依赖,尤其是当项目引入了多个测试框架(如JUnit4和JUnit5混合)时。 - Spring Boot项目可能通过`spring-boot-starter-test`引入了JUnit5,但其默认版本可能较低。可以通过覆盖父POM中的版本属性来升级: ```xml <properties> <junit-jupiter.version>5.10.2</junit-jupiter.version> </properties> ``` ### 相关问题: 1. 如何解决JUnit5与其他测试框架(如JUnit4)的兼容性问题? 2. 在Spring Boot项目中如何升级JUnit版本? 3. 如何排查Maven依赖冲突导致的NoSuchMethodError异常? 注意:以上步骤基于Maven项目,如果使用Gradle,则需要调整依赖配置方式。 </think>### 🔍 问题分析 异常 `java.lang.NoSuchMethodError: java.util.Set org.junit.platform.engine.TestDescriptor.getAncestors()` 表明存在 **JUnit 版本冲突**。具体原因是: - JUnit 5.7.0+ 在 `TestDescriptor` 接口中新增了 `getAncestors()` 方法[^1] - 您的测试运行时加载了**旧版本 JUnit 依赖**(<5.7.0),无法找到该方法 --- ### 🛠️ 解决方案 #### 1. **统一 JUnit 版本**(关键步骤) 在 `pom.xml` 中显式指定所有 JUnit 依赖的版本: ```xml <properties> <!-- 使用最新稳定版或与项目兼容的版本 --> <junit-jupiter.version>5.10.2</junit-jupiter.version> <junit-platform.version>1.10.2</junit-platform.version> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>${junit-platform.version}</version> <scope>test</scope> </dependency> </dependencies> ``` #### 2. **排除冲突的旧版本依赖** 检查依赖树中是否存在旧版本 JUnit: ```bash mvn dependency:tree -Dincludes=org.junit* ``` 若发现旧版本(如 JUnit 4.x),在依赖声明中排除: ```xml <dependency> <groupId>com.example</groupId> <artifactId>problematic-lib</artifactId> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> <!-- JUnit 4 --> </exclusion> <exclusion> <groupId>org.junit.jupiter</groupId> <!-- 旧版本 Jupiter --> <artifactId>junit-jupiter-api</artifactId> </exclusion> </exclusions> </dependency> ``` #### 3. **清理并重建项目** ```bash mvn clean install -U # -U 强制更新快照依赖 ``` #### 4. **验证配置** 确保测试配置正确(如使用 JUnit Jupiter): ```java import org.junit.jupiter.api.Test; // 正确导入 class UserMapperTest { @Test void testList() { ... } // JUnit 5 无需 public } ``` --- ### 🌟 技术原理 - **JUnit 5 模块化**:由 `jupiter`(API+引擎)、`platform`(启动)、`vintage`(兼容JUnit4)组成 - **版本兼容规则**: - `junit-platform-launcher` 和 `junit-jupiter` 主版本需一致 - 新方法引入遵循语义化版本(如 `getAncestors()` 在 5.7.0+ 存在) - **依赖优先级**:Maven 采用**最短路径优先**原则,需显式控制版本[^1] --- ### 📚 相关问题 1. **如何排查 Maven 中的依赖冲突?** > 使用 `mvn dependency:tree -Dverbose` 分析冲突路径,配合 `mvn help:effective-pom` 验证最终依赖版本[^1] 2. **Spring Boot 项目中如何管理 JUnit 版本?** > 覆盖父 POM 中的版本属性: > ```xml > <properties> > <junit-jupiter.version>5.10.2</junit-jupiter.version> > </properties> > ``` 3. **JUnit 5 和 JUnit 4 共存时如何配置?** > 需同时添加: > ```xml > <dependency> > <groupId>org.junit.vintage</groupId> > <artifactId>junit-vintage-engine</artifactId> > <scope>test</scope> > </dependency> > ``` > 引用文献: > [^1] JUnit 5.7 Release Notes - [New Features](https://junit.org/junit5/docs/5.7.0/release-notes/index.html#new-features)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值