关于此常见问题列表
概述
入门
编写测试
组织测试
运行测试
最佳实践
杂项
此常见问题解答的当前版本由 Mike Clark 维护。
此常见问题解答中包含的大部分智慧都来自参与 JUnit 邮件列表和 JUnit 社区的许多优秀人士的集体见解和来之不易的经验。
如果您在此常见问题解答中看到您的才华被体现出来,但没有给予您应有的赞誉,请给我发送电子邮件,我会改正错误。
非常感谢您对此常见问题解答的贡献!JUnit 社区提前感谢您。
要为此常见问题解答做出贡献,只需编写一个与 JUnit 相关的问题和答案,然后将未格式化的文本发送给 Mike Clark。始终欢迎对本常见问题解答进行更正。
任何合理的贡献都不会被拒绝。您的姓名将始终与您所做的任何贡献一起显示。
本常见问题解答的最新副本可在 https://junit.cn/junit4/faq.html 找到。
JUnit 发行版还在doc目录中包含此常见问题解答。
JUnit 是一个简单、开源的框架,用于编写和运行可重复的测试。它是用于单元测试框架的 xUnit 架构的一个实例。JUnit 的功能包括
JUnit 最初由 Erich Gamma 和 Kent Beck 编写。
JUnit 的官方主页是 https://junit.cn。
有 3 个邮件列表专门讨论 JUnit 的所有内容
以下文档包含在 JUnit 发行版的doc目录中
JUnit 是 开源软件,根据 Eclipse Public License Version 1.0 发布,并托管在 SourceForge 上。
最佳 Java 性能监控/测试工具
最佳 Java 性能监控/测试工具
最新版本的 JUnit 可在 SourceForge 上找到。
首先,下载 最新版本的 JUnit,以下简称junit.zip.
然后在您选择的平台上安装 JUnit
Windows
要在 Windows 上安装 JUnit,请按照以下步骤操作
解压缩junit.zip发行文件到一个名为%JUNIT_HOME%.
将 JUnit 添加到类路径
set CLASSPATH=%CLASSPATH%;%JUNIT_HOME%\junit.jar
Unix (bash)
解压缩junit.zip发行文件到一个名为要在 Unix 上安装 JUnit,请按照以下步骤操作.
的目录
$JUNIT_HOME
export CLASSPATH=$CLASSPATH:$JUNIT_HOME/junit.jar(可选)解压缩$JUNIT_HOME/src.jar
文件。通过运行 JUnit 随附的示例测试来测试安装。请注意,示例测试直接位于安装目录中,而不是junit.jar
java org.junit.runner.JUnitCore org.junit.tests.AllTests
文件。因此,请确保 JUnit 安装目录在您的 CLASSPATH 中。然后只需键入
所有测试都应通过并显示“OK”消息。通过运行 JUnit 随附的示例测试来测试安装。请注意,示例测试直接位于安装目录中,而不是如果测试未通过,请验证
是否在 CLASSPATH 中。
最后,阅读 文档。
删除解压缩 JUnit 发行版的目录结构。通过运行 JUnit 随附的示例测试来测试安装。请注意,示例测试直接位于安装目录中,而不是从 CLASSPATH 中删除
JUnit 不会修改注册表,因此只需删除所有文件即可完全卸载它。
在 FAQ 或 文档 中未解答的问题应发布到 jGuru 讨论论坛 或 JUnit 用户邮件列表。
请在讨论论坛和邮件列表中坚持讨论技术问题。请记住,这些是公开的,因此不要在您的问题中包含任何机密信息!
在参与讨论论坛和邮件列表之前,您还应该阅读 Eric Raymond 的 “提问的智慧”。
注意
请不要向讨论论坛或邮件列表提交错误、补丁或功能请求。
请参阅 “我如何提交错误、补丁或功能请求?”。
JUnit 赞扬程序员测试自己的软件。本着这种精神,包含 JUnit 测试的错误、补丁和功能请求比没有 JUnit 测试的错误、补丁和功能请求更有可能得到解决。
JUnit 托管在 GitHub 上。请使用 GitHub 提供的工具进行提交。
创建一个类
package junitfaq; import org.junit.*; import static org.junit.Assert.*; import java.util.*; public class SimpleTest {
编写一个测试方法(使用@Test注释),该方法断言被测对象上的预期结果
@Test public void testEmptyCollection() { Collection collection = new ArrayList(); assertTrue(collection.isEmpty()); }
如果您正在使用 JUnit 3.x 运行器运行 JUnit 4 测试,请编写一个suite()方法,该方法使用JUnit4TestAdapter类来创建一个包含所有测试方法的套件
public static junit.framework.Test suite() { return new junit.framework.JUnit4TestAdapter(SimpleTest.class); }
虽然编写一个main()方法来运行测试在使用 IDE 运行器后变得不那么重要,但这仍然是可能的
public static void main(String args[]) { org.junit.runner.JUnitCore.main("junitfaq.SimpleTest"); } }
运行测试
要从控制台运行测试,请键入
java org.junit.runner.JUnitCore junitfaq.SimpleTest
要使用main()中使用的测试运行器运行测试,请键入
java junitfaq.SimpleTest
通过的测试会产生以下文本输出
. Time: 0 OK (1 tests)
(由:Jeff Nielsen 提交)
如果您有两个或多个测试用于一组通用对象,则测试夹具非常有用。使用测试夹具可以避免重复初始化(和清理)通用对象所需的代码。
测试可以使用测试夹具中的对象(变量),每个测试都在夹具中的对象上调用不同的方法并断言不同的预期结果。每个测试都在其自己的测试夹具中运行,以将测试与其他测试所做的更改隔离开来。也就是说,测试不共享测试夹具中对象的状态。由于测试是隔离的,因此它们可以按任何顺序运行。
要创建测试夹具,请为通用对象声明实例变量。在带有public void方法中初始化这些对象,并使用@Before进行注释。JUnit 框架在每次测试运行之前自动调用任何@Before方法。
以下示例显示了一个带有通用Collection对象的测试夹具。
package junitfaq; import org.junit.*; import static org.junit.Assert.*; import java.util.*; public class SimpleTest { private Collection<Object> collection; @Before public void setUp() { collection = new ArrayList<Object>(); } @Test public void testEmptyCollection() { assertTrue(collection.isEmpty()); } @Test public void testOneItemCollection() { collection.add("itemA"); assertEquals(1, collection.size()); } }
给定此测试,方法可能会按以下顺序执行
setUp() testEmptyCollection() setUp() testOneItemCollection()
测试方法调用的顺序不能保证,因此testOneItemCollection()可能在testEmptyCollection()之前执行。但这没关系,因为每个方法都会获得collection.
的自己的实例@Before虽然 JUnit 为每个测试方法提供夹具对象的新实例,但如果您在方法中分配任何外部资源,则应在测试运行后通过使用进行注释。JUnit 框架在每次测试运行之前自动调用任何方法中分配任何外部资源,则应在测试运行后通过使用@After
package junitfaq; import org.junit.*; import static org.junit.Assert.*; import java.io.*; public class OutputTest { private File output; @Before public void createOutputFile() { output = new File(...); } @After public void deleteOutputFile() { output.delete(); } @Test public void testSomethingWithFile() { ... } }
注释方法在每次测试运行后释放它们。例如
createOutputFile() testSomethingWithFile() deleteOutputFile()
使用此测试,方法将按以下顺序执行
(由:Dave Astels 提交)
通常,如果一个方法不返回值,它将具有一些副作用。实际上,如果它不返回值并且没有副作用,那么它什么都不做。可能有一种方法可以验证副作用是否按预期发生。例如,考虑 Collection 类中的add()
@Test public void testCollectionAdd() { Collection collection = new ArrayList(); assertEquals(0, collection.size()); collection.add("itemA"); assertEquals(1, collection.size()); collection.add("itemB"); assertEquals(2, collection.size()); }
方法。有多种方法可以验证副作用是否发生(即,对象是否已添加)。您可以检查大小并断言它是否是预期的
另一种方法是使用 MockObjects。一个相关的问题是为测试而设计。例如,如果您有一个旨在输出到文件的方法,请不要传入文件名,甚至不要传入FileWriter。而是传入一个Writer。这样,您可以传入一个StringWriter来捕获输出以进行测试。然后您可以添加一个方法(例如writeToFileNamed(String filename)一个相关的问题是为测试而设计。例如,如果您有一个旨在输出到文件的方法,请不要传入文件名,甚至不要传入)来封装
创建。单元测试旨在减轻对某些东西可能损坏的恐惧。如果您认为get()或set()
方法可能会合理地损坏,或者实际上导致了缺陷,那么请务必编写测试。
简而言之,测试直到您有信心为止。您选择测试什么是主观的,基于您的经验和信心水平。记住要务实并最大化您的测试投资。
另请参阅 “‘简单到不会出错’有多简单?”。
(由:J. B. Rainsberger 提交)
大多数情况下,get/set 方法只是不会损坏,如果它们不会损坏,那么为什么要测试它们呢?虽然通常最好测试更多,但在测试工作量与“代码覆盖率”之间存在明显的收益递减曲线。记住格言:“测试直到恐惧变成无聊。”假设getX()方法仅执行“return x;”,并且setX()
@Test public void testGetSetX() { setX(23); assertEquals(23, getX()); }
方法仅执行“this.x = x;”。如果您编写此测试
@Test public void testGetSetX() { x = 23; assertEquals(23, x); }
那么您正在测试以下等效内容
@Test public void testGetSetX() { assertEquals(23, 23); }
或者,如果您愿意,
此时,您正在测试 Java 编译器,或者可能是解释器,而不是您的组件或应用程序。通常不需要您为他们进行 Java 的测试。假设如果您担心在您希望调用假设时属性是否已设置,那么您要测试的是构造函数,而不是
@Test public void testCreate() { assertEquals(23, new MyClass(23).getX()); }
方法。如果您有多个构造函数,则此类测试特别有用将可选的expected@Test属性添加到注释。以下是一个示例测试,当预期的IndexOutOfBoundsException
@Test(expected=IndexOutOfBoundsException.class) public void testIndexOutOfBoundsException() { ArrayList emptyList = new ArrayList(); Object o = emptyList.get(0); }
被引发时通过在测试方法的throws
子句中声明异常,并且不要在测试方法中捕获异常。未捕获的异常将导致测试失败并出现错误。注释。以下是一个示例测试,当预期的IndexOutOfBoundsException
@Test public void testIndexOutOfBoundsExceptionNotRaised() throws IndexOutOfBoundsException { ArrayList emptyList = new ArrayList(); Object o = emptyList.get(0); }
以下是一个示例测试,当
将您的测试放在与被测类相同的包中。
有关如何组织受保护方法访问的测试示例,请参阅 “我应该把我的测试文件放在哪里?”。
测试私有方法可能表明这些方法应该移动到另一个类中以提高可重用性。
但如果您必须...
如果您使用的是 JDK 1.3 或更高版本,则可以使用反射借助 PrivilegedAccessor 来颠覆访问控制机制。有关如何使用它的详细信息,请阅读 本文。
另请参阅 “‘简单到不会出错’有多简单?”。
如果您使用的是 JDK 1.6 或更高版本,并且您使用 @Test 注释您的测试,则可以使用 Dp4j 在您的测试方法中注入反射。有关如何使用它的详细信息,请参阅 此测试脚本。
在单个测试中报告多个失败通常表明该测试做的太多了,与单元测试应该做的相比。通常这意味着该测试实际上是一个功能/验收/客户测试,或者,如果它是一个单元测试,那么它是一个太大的单元测试。
JUnit 被设计为最适合大量小型测试。它在测试类的单独实例中执行每个测试。它报告每个测试的失败。当在测试之间共享时,共享设置代码是最自然的。这是一个渗透 JUnit 的设计决策,当您决定报告每个测试的多个失败时,您就开始与 JUnit 作战。不建议这样做。
长时间的测试是一种设计异味,表明可能存在设计问题。Kent Beck 喜欢在这种情况下说“有机会了解您的设计”。我们希望看到围绕这些问题的模式语言发展,但尚未写下来。
最后,请注意,具有多个断言的单个测试与具有多个测试的测试用例是同构的
public class MyTestCase { @Test public void testSomething() { // Set up for the test, manipulating local variables assertTrue(condition1); assertTrue(condition2); assertTrue(condition3); } }
一个测试方法,三个断言
public class MyTestCase { // Local variables become instance variables @Before public void setUp() { // Set up for the test, manipulating instance variables } @Test public void testCondition1() { assertTrue(condition1); } @Test public void testCondition2() { assertTrue(condition2); } @Test public void testCondition3() { assertTrue(condition3); } }
三个测试方法,每个方法一个断言
生成的测试使用 JUnit 的自然执行和报告机制,并且,一个测试中的失败不会影响其他测试的执行。通常,如果可以管理,您希望每个给定的错误恰好失败一个测试。JUnit 3.7 已弃用assert()并将其替换为assertTrue()
,它的工作方式完全相同。JUnit 4 与assert关键字兼容。如果您使用-ea
JVM 开关运行,则 JUnit 将报告失败的断言。
重构 J2EE 组件以将功能委托给不必在 J2EE 容器中运行的其他对象将提高软件的设计和可测试性。
另请参阅 “‘简单到不会出错’有多简单?”。
Cactus 是一个开源 JUnit 扩展,可用于在其自然环境中测试 J2EE 组件。
否。每个被测类从一个测试类开始是一种约定,但这不是必需的。
测试类仅提供一种组织测试的方式,仅此而已。通常,您将从每个被测类一个测试类开始,但随后您可能会发现一小组测试与它们自己的通用测试夹具[1] 属于一起。在这种情况下,您可以将这些测试移动到一个新的测试类。这是一个简单的面向对象重构:分离做得太多的对象的职责。要考虑的另一点是TestSuite
是 JUnit 中最小的执行单元:如果不更改源代码,则一次不能执行小于 TestSuite 的任何内容。在这种情况下,除非测试以某种方式“属于一起”,否则您可能不想将测试放在同一个测试类中。如果您有两组您认为希望彼此分开执行的测试,那么将它们放在单独的测试类中是明智的。
[1] 测试夹具是许多测试共享的一组通用测试数据和协作对象。通常,它们在测试类中作为实例变量实现。
(由:Eric Armstrong 提交)
以下模板是一个很好的起点。复制/粘贴并编辑这些模板以适合您的编码风格。
import org.junit.*; import static org.junit.Assert.*; public class SampleTest { private java.util.List emptyList; /** * Sets up the test fixture. * (Called before every test case method.) */ @Before public void setUp() { emptyList = new java.util.ArrayList(); } /** * Tears down the test fixture. * (Called after every test case method.) */ @After public void tearDown() { emptyList = null; } @Test public void testSomeBehavior() { assertEquals("Empty list should have 0 elements", 0, emptyList.size()); } @Test(expected=IndexOutOfBoundsException.class) public void testForException() { Object o = emptyList.get(0); } }
SampleTest 是一个基本测试模板
请参阅 http://c2.com/cgi/wiki?AbstractTestCases。
(由:Timothy Wall 和 Kent Beck 提交)
按照设计,Test 实例树在一个过程中构建,然后在第二个过程中执行测试。测试运行器在测试执行期间保持对所有 Test 实例的强引用。这意味着对于具有许多 Test 实例的非常长的测试运行,在整个测试运行结束之前,可能不会对任何测试进行垃圾回收。因此,如果您在测试中分配外部或有限的资源,您有责任释放这些资源。例如,在中显式地将对象设置为null方法,允许在整个测试运行结束之前对其进行垃圾回收。
您可以将测试放在与被测类相同的包和目录中。
例如
src com xyz SomeClass.java SomeClassTest.java
虽然对于小型项目来说足够了,但许多开发人员认为这种方法会使源目录变得混乱,并且使得在不包含不需要的测试代码或编写不必要的复杂打包任务的情况下打包客户端交付变得困难。
一种可以说更好的方法是将测试放在一个单独的并行目录结构中,并进行包对齐。
例如
src com xyz SomeClass.java test com xyz SomeClassTest.java
这些方法允许测试访问被测类的所有公共和包可见方法。
一些开发人员主张将测试放在被测类的子包中(例如 com.xyz.test)。本常见问题解答的作者认为采用这种方法没有明显的优势,并且认为上述开发人员也将他们的大括号放在错误的行上。:-)
这样做的愿望通常是您的设计中过度耦合的症状。如果两个或多个测试必须共享相同的测试夹具状态,那么测试可能试图告诉您被测类有一些不良的依赖关系。
重构设计以进一步解耦被测类并消除代码重复通常比设置共享测试夹具更好。
测试私有方法可能表明这些方法应该移动到另一个类中以提高可重用性。
您可以将@BeforeClass注释添加到要在类中所有测试之前运行的方法,并将@AfterClass注释添加到要在类中所有测试之后运行的方法。这是一个例子
package junitfaq; import org.junit.*; import static org.junit.Assert.*; import java.util.*; public class SimpleTest { private Collection collection; @BeforeClass public static void oneTimeSetUp() { // one-time initialization code } @AfterClass public static void oneTimeTearDown() { // one-time cleanup code } @Before public void setUp() { collection = new ArrayList(); } @After public void tearDown() { collection.clear(); } @Test public void testEmptyCollection() { assertTrue(collection.isEmpty()); } @Test public void testOneItemCollection() { collection.add("itemA"); assertEquals(1, collection.size()); } }
给定此测试,方法将按以下顺序执行
oneTimeSetUp() setUp() testEmptyCollection() tearDown() setUp() testOneItemCollection() tearDown() oneTimeTearDown()
[1] 测试夹具是许多测试共享的一组通用测试数据和协作对象。通常,它们在测试类中作为实例变量实现。
要运行您的 JUnit 测试,您需要在您的 CLASSPATH 中包含以下元素
如果尝试运行您的测试导致NoClassDefFoundError,则您的 CLASSPATH 中缺少某些内容。
Windows 示例
set CLASSPATH=%JUNIT_HOME%\junit.jar;c:\myproject\classes;c:\myproject\lib\something.jar
Unix (bash) 示例
export CLASSPATH=$JUNIT_HOME/junit.jar:/myproject/classes:/myproject/lib/something.jar
(由:J.B. Rainsberger 和 Jason Rogers 提交)
最有可能的是您的 CLASSPATH 不包含 JUnit 安装目录。
有关更多指导,请参阅 “运行 JUnit 需要哪些 CLASSPATH 设置?”。
另请考虑运行 WhichJunit 以打印运行和测试 JUnit 及其示例所需的 JUnit 类文件的绝对位置。
如果 CLASSPATH 看起来很神秘,请阅读 此文档!
[1] 测试夹具是许多测试共享的一组通用测试数据和协作对象。通常,它们在测试类中作为实例变量实现。
调用运行器
java org.junit.runner.JUnitCore <测试类名>
[1] 测试夹具是许多测试共享的一组通用测试数据和协作对象。通常,它们在测试类中作为实例变量实现。
定义任何必要的 Ant 属性
<property name="src" value="./src" /> <property name="lib" value="./lib" /> <property name="classes" value="./classes" /> <property name="test.class.name" value="com.xyz.MyTestSuite" />
设置 JUnit 要使用的 CLASSPATH
<path id="test.classpath"> <pathelement location="${classes}" /> <pathelement location="/path/to/junit.jar" /> <fileset dir="${lib}"> <include name="**/*.jar"/> </fileset> </path>
定义用于运行 JUnit 的 Ant 任务
<target name="test"> <junit fork="yes" haltonfailure="yes"> <test name="${test.class.name}" /> <formatter type="plain" usefile="false" /> <classpath refid="test.classpath" /> </junit> </target>
运行测试
ant test
有关更多信息,请参阅 JUnit Ant 任务。
(由:Eric Armstrong 和 Steffen Gemkow 提交)
确保 Ant 的optional.jar文件在您的 CLASSPATH 中或存在于您的$ANT_HOME/lib目录中包含此常见问题解答。
中
<property name="test.reports" value="./reports" />
为包含 HTML 报告的目录添加 ANT 属性
<target name="test-html"> <junit fork="yes" printsummary="no" haltonfailure="no"> <batchtest fork="yes" todir="${test.reports}" > <fileset dir="${classes}"> <include name="**/*Test.class" /> </fileset> </batchtest> <formatter type="xml" /> <classpath refid="test.classpath" /> </junit> <junitreport todir="${test.reports}"> <fileset dir="${test.reports}"> <include name="TEST-*.xml" /> </fileset> <report todir="${test.reports}" /> </junitreport> </target>
运行测试
有关更多信息,请参阅 JUnit Ant 任务。
ant test-html使用-D
-DparameterName=parameterValue
JVM 命令行选项,如如果命令行上的参数数量变得笨拙,请传入定义一组参数的属性文件的位置。或者,JUnit-addons 包 包含XMLPropertyManager和PropertyManager
类,这些类允许您定义包含测试参数的属性文件(或 XML 文件)。
(由:Scott Stirling 提交)截至 JUnit 3.7 的解决方法是添加XMLPropertyManagerorg.w3c.dom.*org.xml.sax.*到您的.
excluded.properties到您的中只是时间问题,此修复程序将合并到 JUnit 的发布版本中,因为 JAXP 是 JDK 1.4 的标准部分。它将像排除
# # The list of excluded package paths for the TestCaseClassLoader # excluded.0=sun.* excluded.1=com.sun.* excluded.2=org.omg.* excluded.3=javax.* excluded.4=sunw.* excluded.5=java.* excluded.6=org.w3c.dom.* excluded.7=org.xml.sax.* excluded.8=net.jini.*
org.omg.*到您的一样。顺便说一句,如果您从 Sourceforge CVS 下载 JUnit 源代码,您会发现这些模式已经添加到默认的 excluded.properties 中,并且 JINI 的模式也已添加。实际上,这是 CVS 中的当前版本,它演示了如何将排除项添加到列表中LinkageError这是默认列表需要修改的最常见情况。的的原因与在您的测试用例中使用 JAXP 有关。我所说的 JAXP 是指整套截至 JUnit 3.7 的解决方法是添加XMLPropertyManagerorg.w3c.dom.*javax.xml.*
类和支持到您的类。截至 JUnit 3.7 的解决方法是添加XMLPropertyManagerorg.w3c.dom.*如上所述,JUnit GUI TestRunners 的类加载器依赖于对于它应该委托给系统类加载器的类。JAXP 是一种不寻常的情况,因为它是标准的 Java 扩展库,它依赖于包名称为 () 的类,这些类不以标准的 Java 或 Sun 前缀开头。这类似于只是时间问题,此修复程序将合并到 JUnit 的发布版本中javax.rmi.*到您的和
类之间的关系,这些类已默认在 JUnit 的列表需要修改的最常见情况。的中排除了一段时间。当将 JUnit Swing 或 AWT UI 与引用、使用或依赖 JAXP 类(例如 Log4J、Apache SOAP、Axis、Cocoon 等)的测试用例一起使用时,可能发生并且经常发生的是,JUnit 类加载器(正确地)委托/它“看到”的类给系统加载器。但是,系统加载器在初始化和加载该 JAXP 类的过程中,链接并加载了一堆org.w3c.dom当将 JUnit Swing 或 AWT UI 与引用、使用或依赖 JAXP 类(例如 Log4J、Apache SOAP、Axis、Cocoon 等)的测试用例一起使用时,可能发生并且经常发生的是,JUnit 类加载器(正确地)委托/它“看到”的类给系统加载器。但是,系统加载器在初始化和加载该 JAXP 类的过程中,链接并加载了一堆org.xml.saxLinkageError类。当它这样做时,JUnit 自定义类加载器根本没有参与,因为系统类加载器从不“向下”委托或与自定义类加载器检查以查看类是否已加载。在此之后的任何时候,如果要求 JUnit 加载一个
它以前从未见过的类,它将尝试加载它,因为该类的名称与默认排除列表中的任何模式都不匹配。那时会发生
。这实际上是 JUnit 类加载器设计中的缺陷,但上面给出了解决方法。LinkageErrorJava 2 JVM 将类(记住,类和对象虽然相关,但对于 JVM 来说是不同的实体 - 我在这里谈论的是类,而不是对象实例)保存在命名空间中,通过它们的完全限定类名加上它们的定义(而不是启动)加载器的实例来标识它们。JVM 将尝试将已定义和加载的类引用的所有未加载的类分配给该类的定义加载器。JVM 的类解析器例程(在 JVM 源代码中实现为 C 函数)跟踪所有这些类加载事件,并“查看”另一个类加载器(例如 JUnit 自定义加载器)是否尝试定义系统加载器已定义的类。根据 Java 2 加载器约束的规则,如果系统加载器已经定义了一个类,则任何加载类的尝试都应首先委托给系统加载器。JUnit 处理此功能的“正确”方法是从系统类加载器一无所知的存储库加载类。然后,JUnit 自定义类加载器可以遵循标准的 Java 2 委托模型,该模型始终将类加载委托给系统加载器,并且仅在失败时才尝试加载。由于它们都在当前模型中从 CLASSPATH 加载,因此如果 JUnit 加载器像它应该的那样委托,它将永远无法加载任何类,因为系统加载器总是会找到它们。您可以尝试在 JUnit 源代码中通过捕获在 TestCaseClassLoader 的loadClass()方法中解决此问题,然后对当将 JUnit Swing 或 AWT UI 与引用、使用或依赖 JAXP 类(例如 Log4J、Apache SOAP、Axis、Cocoon 等)的测试用例一起使用时,可能发生并且经常发生的是,JUnit 类加载器(正确地)委托/它“看到”的类给系统加载器。但是,系统加载器在初始化和加载该 JAXP 类的过程中,链接并加载了一堆进行恢复调用
findSystemClass()@Test.
例如
@Test public void testSomething() { }
-- 从而在捕获违规后委托给系统加载器。但是,此 hack 仅在某些时候有效,因为现在您可能会遇到相反的问题,即 JUnit 加载器将加载大量
类,然后系统加载器在尝试执行我上面描述的与 JAXP 完全相同的事情时违反加载器约束,因为它从不委托给其逻辑子级(JUnit 加载器)。不可避免地,如果您的测试用例使用许多 JAXP 和相关的 XML 类,则无论您做什么,一个或另一个类加载器最终都会违反约束。确保您有更多或更多方法使用@Test
进行注释必须启用 Java 编译器的调试选项,才能在堆栈跟踪中看到源文件和行号信息。从命令行调用 Java 编译器时,请使用
<javac srcdir="${src}" destdir="${build}" debug="on" />
-g选项生成所有调试信息。从 Ant 任务调用 Java 编译器时,请使用
debug="on"
属性。例如
当使用较旧的 JVM pre-Hotspot(JDK 1.1 和大多数/所有 1.2)时,请使用
-DJAVA_COMPILER=noneJMV 命令行参数运行 JUnit,以防止运行时 JIT 编译模糊行号信息。使用启用调试的测试源编译将显示断言失败的行。使用启用调试的非测试源编译将显示在被测类中引发异常的行。(由:Bill de hora 提交)有很多方法可以做到这一点
<junit printsummary="yes" haltonfailure="yes"> ... <batchtest fork="yes"> <fileset dir="${src.dir}"> <include name="**/*Test.java" /> <include name="**/Test*.java" /> </fileset> </batchtest> </junit>
在 Ant 中,使用junitXMLPropertyManager任务和batchtest
ant test-html元素XMLPropertyManager单元测试的惯用命名模式是Test*.java
DirectorySuiteBuilder builder = new DirectorySuiteBuilder(); builder.setSuffix("Test"); Test suite = builder.suite("/home/project/myproject/tests");
*Test.java
。文档和示例位于 https://ant.apache.ac.cn/manual/Tasks/junit.html。
DirectorySuiteBuilder
ArchiveSuiteBuilder
(对于 jar/zip 文件)由 JUnit-addons 项目提供的类
编写您自己的自定义套件构建器。
让您的测试类实现一个接口,并编写一个 treewalker 来加载目录中的每个类,检查该类,并将实现该接口的任何类添加到 TestSuite。
如果您非常不习惯使用测试类的命名约定,您可能只想这样做。除了对于较大的套件来说速度较慢之外,最终是否更费力地遵循命名约定让测试类实现一个接口是有争议的!
此方法的一个示例位于 http://www.javaworld.com/javaworld/jw-12-2000/jw-1221-junit_p.html。
测试应在代码之前编写。测试优先编程的实践是在自动化测试失败时才编写新代码。
好的测试会告诉您如何最好地为预期用途设计系统。它们有效地以可执行格式传达如何使用软件。它们还可以防止基于推测过度构建系统的趋势。当所有测试都通过时,您就知道您完成了!
另请参阅 “‘简单到不会出错’有多简单?”。
每当客户测试失败或报告错误时,首先编写必要的单元测试来暴露错误,然后修复它们。这几乎可以使该特定错误以后不会再次出现。
测试驱动开发比在代码似乎可以工作后编写测试更有趣。试一试!假设不,只需测试所有可能合理损坏的东西。假设方法只返回实例变量的值。 在这种情况下,假设除非编译器或解释器也损坏,否则不会出错。 因此,不要测试假设;这样做没有任何好处。 这对于方法仅执行“return x;”,并且方法也是如此,尽管如果你的方法仅执行“return x;”,并且方法进行任何参数验证或有任何副作用,你可能需要对其进行测试。
下一个例子:假设你编写了一个方法,该方法除了将参数转发到在另一个对象上调用的方法之外,什么也不做。 该方法太简单,不会出错。
public void myMethod(final int a, final String b) { myCollaborator.anotherMethod(a, b); }
myMethod不可能出错,因为它什么也不做:它将其输入转发到另一个对象,仅此而已。
此方法的唯一前提条件是“myCollaborator != null”,但这通常是构造函数的责任,而不是 myMethod 的责任。 如果你担心,请添加一个测试来验证每个构造函数是否始终将 myCollaborator 设置为非空值。
myMethod 出错的唯一可能是myCollaborator.anotherMethod()出错。 在这种情况下,测试myCollaborator,而不是当前类。
确实,即使为这些简单方法添加测试,也可以防止有人重构并使这些方法变得“不再那么简单”的可能性。 但是,在这种情况下,重构者需要意识到该方法现在足够复杂,可能会出错,并且应该为其编写测试——最好在重构之前。
另一个例子:假设你有一个 JSP,并且像一个优秀的程序员一样,你已经从中删除了所有业务逻辑。 它所做的只是为许多 JavaBeans 提供布局,并且从不做任何可能更改任何对象的值的事情。 该 JSP 太简单,不会出错,并且由于 JSP 众所周知地难以测试,因此你应该努力使所有 JSP 都太简单而不会出错。
以下是测试的进行方式
becomeTimidAndTestEverything while writingTheSameThingOverAndOverAgain becomeMoreAggressive writeFewerTests writeTestsForMoreInterestingCases if getBurnedByStupidDefect feelStupid becomeTimidAndTestEverything end end
正如你所见,循环永远不会终止。
尽可能频繁地运行所有单元测试,理想情况下,每次代码更改时都运行。 确保所有单元测试始终以 100% 的通过率运行。 频繁的测试让你确信你的更改没有破坏任何东西,并且通常会降低在黑暗中编程的压力。
对于较大的系统,你可能只需运行与你正在处理的代码相关的特定测试套件。
每天(或每晚)至少运行一次所有验收测试、集成测试、压力测试和单元测试。
如果你正在使用 Eclipse,请务必查看 David Saff 的 持续测试插件。
测试驱动开发通常会降低软件的缺陷密度。 但是我们都会犯错,所以有时缺陷会溜过去。 当这种情况发生时,编写一个失败的测试来暴露缺陷。 当测试通过时,你就知道缺陷已修复!
不要忘记将此用作学习机会。 也许可以通过更积极地测试所有可能合理出错的东西来防止缺陷。
在代码中插入调试语句是一种低技术含量的调试方法。 它通常要求每次程序运行时手动扫描输出,以确保代码正在执行预期的操作。
从长远来看,以自动化 JUnit 测试的形式编纂期望通常花费的时间更少,并且随着时间的推移保持其价值。 如果难以编写测试来断言期望,则测试可能是在告诉你,更短、更具凝聚力的方法会改善你的设计。
调试器通常用于单步执行代码并检查沿途的变量是否包含预期值。 但是,在调试器中单步执行程序是一个手动过程,需要繁琐的视觉检查。 本质上,调试会话只不过是对预期结果与实际结果的手动检查。 此外,每次程序更改时,我们都必须在调试器中手动回溯程序,以确保没有破坏任何东西。
以自动化 JUnit 测试的形式编纂期望通常花费的时间更少,并且随着时间的推移保持其价值。 如果难以编写测试来断言预期值,则测试可能是在告诉你,更短、更具凝聚力的方法会改善你的设计。
启动TestRunner在调试器下,并配置调试器使其捕获junit.framework.AssertionFailedError.
如何配置取决于你喜欢使用的调试器。 大多数 Java 调试器都提供在引发特定异常时停止程序的支持。
请注意,这仅在发生预期故障时启动调试器。
维基百科维护着一份可用的 xUnit 测试框架 列表。