百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

JUnit测试框架,assert里的测试方法,程序员真的会使用它吗?

ccwgpt 2024-10-13 01:38 34 浏览 0 评论

6.4 创建单元测试方法

使用fixture是复用配置代码的好办法。但是还有很多常见的任务,很多测试都会重复执行这些任务。JUnit框架用一组assert方法封装了最常见的测试任务。这些assert方法可以极大地简化单元测试的编写。

Assert超类型

Assert类包含了一组静态的测试方法,用于验证期望值expected和实际值actual逻辑比对是否正确,即测试失败,标志为未通过测试。如果期望值和实际值比对失败,Assert类就会抛出一个AssertionFailedError异常,Junit测试框架将这种错误归入Fails并且加以记录。每一个Assert类所属的方法都会被重载(OverLoaded),如果指定了一个String类型的传参则该参数将被做为AssertionFailedError异常的标识信息,告诉测试人员该异常的具体信息。

定义assert方法的辅助类的名称:Assert类。这个类包含了很多对于编写测试很有用的具体代码。Assert只有8个公有核心公有核心方法(如下图所示)。

图3-10 Assert超类所提供的8个核心方法

Javadoc 中充满了这8个方法的便捷形式。这些便捷形式使得传递在测试中需要的任何类型都很简单。以assertEquals方法为例,它就有20种形式!大多数形式都是便捷形式,最终还是调用了核心的assertEquals(String message, Object expected, Object actual)方法。

保持测试的独立性

在 你开始编写自己的测试的时候,请记住第一条规则:每项单元测试都必须独立于其他所有单元测试而运行。单元测试必须能以任何顺序运行,一项测试不能依赖于前面的测试造成的改变(比如把某个成员变量设置成某个状态)。如果一项测试依赖于其他测试,那么你是在自找麻烦。下面是相互依赖的测试会造成的问题:

不具可移植性——默认情况下,JUnit通过反射来测试方法。反射API并不保证返回方法名的顺序。如果你的测试依赖于这个顺序,那么可能你的test suite在某个JVM上运行正常,但在另一个JVM上无法运行。

难以维护——当你修改一项测试,你会发现其他一批测试也受到了影响。若你需要修改其它测试,那么你在花时间维护测试,而这些时间本可用于开发代码。

不够清晰——为了理解相互依赖的测试是如何工作的,你必须理解每个测试是如何工作的,这样测试就变得难读也难以掌握。好的测试必须简单易懂、易于掌握。

6.5 TestSuite

您定义自己的TestCase,并使用TestRunner来运行测试,事实上TestRunner并不直接运行 TestCase上的单元方法,而是透过TestSuite,TestSuite可以将数个TestCase在一起,而让每个TestCase保持简单。

来看看一个例子:

MathToolTest.java

package onlyfun.caterpillar.test;
import onlyfun.caterpillar.MathTool;
import junit.framework.TestCase;
public class MathToolTest extends TestCase {
 public MathToolTest(String testMethod) {
 super(testMethod);
 }
 public void testGcd() {
 assertEquals(5, MathTool.gcd(10, 5));
 }
 public static void main(String[] args) {
 junit.textui.TestRunner.run(MathToolTest.class);
 }
}

在这个例子中,您并没有看到任何的TestSuite,事实上,如果您没有提供任何的TestSuite,TestRunner会自己建立一个,然后这个 TestSuite会使用反射(reflection)自动找出testXXX()方法。

如果您要自行生成TestSuite,则在继承TestCase之后,提供静态的(static)的suite()方法,例如:

public static Test suite() {

return new TestSuite(MathTool.class);

}

如果您没有提供任何的TestSuite,则TestRunner就会像上面这样自动为您建立一个,并找出testXXX()方法,您也可以如下面定义 suite()方法:

public static Test suite() {

TestSuite suite = new TestSuite(MathTool.class);

suite.addTest(new MathToolTest("testGcd"));

return suite;

}

JUnit并没有规定您一定要使用testXXX()这样的方式来命名您的测试方法,如果您要提供自己的方法(当然JUnit 鼓励您使用testXXX()这样的方法名称),则可以如上撰写,为了要能够使用建构函式提供测试方法名称,您的TestCase必须提供如下的建构函式:

public MathToolTest(String testMethod) {

super(testMethod);}

如果要加入更多的测试方法,使用addTest()就可以了,suite()方法传回一个TestSuite物件,它与 TestCase都实作了Test介面,TestRunner会调用TestSuite上的run()方法,然后TestSuite会将之委托给 TestCase上的run()方法,并执行每一个testXXX()方法。

除了组合TestCase之外,您还可以将数个TestSuite组合在一起,例如:

public static Test suite() {

TestSuite suite= new TestSuite();

suite.addTestSuite(TestCase1.class);

suite.addTestSuite(TestCase2.class);

return suite;

}

如此之来,您可以一次运行所有的测试,而不必个别的运行每一个测试案例,您可以写一个运行全部测试的主测试,而在使用TestRunner时呼叫 suite()方法,例如:

junit.textui.TestRunner.run(TestAll.suite());

TestCase与TestSuite都实现了Test介面,其运行方式为 Command 模式 的一个实例,而TestSuite可以组合数个TestSuite或TestCase,这是 Composite 模式 的一个实例。

6.6 Fail Error

在运行TestRunner执行您的测试时,您会发现到有Failure与Error两种测试尚未通过的讯息。

Failure指的是预期的结果与实际运行单元的结果不同所导致,例如当您使用assertEquals()或其它assertXXX()方法断言失败时,就会回报Failure,这时候您要检查您的单元方法中的逻辑设计是否有误。

Error指的是您程式没有考虑到的情况,在断言之前程式就因为某种错误引发例外而终止,例如在单元中存取某个阵列,因为存取超出索引而引发 ArrayIndexOutOfBoundsException,这会使得单元方法无法正确完成,在测试运行到asertXXXX()前就提前结束,这时候您要检查您的单元方法中是否有未考虑到的情况而引发流程突然中断。

来看个实际的例子,如果您设计了下面的测试案例:

ObjectArrayTest.java

package onlyfun.caterpillar.test;
import onlyfun.caterpillar.ObjectArray;
import junit.framework.TestCase;
public class ObjectArrayTest extends TestCase {
 public void testAdd() {
 ObjectArray objArr = new ObjectArray();
 Object testObj = new Object();
 Object obj = objArr.setObject(0, testObj);
 assertEquals(testObj, obj);
 }
 public static void main(String[] args) { junit.textui.TestRunner.run(ObjectArrayTest.class);
 }
}

然后根据这个测试案例,您撰写了ObjectArray类别:

ObjectArray.java

package onlyfun.caterpillar;
public class ObjectArray {
 private Object[] objs;
 public Object setObject(int i, Object o) {
 return objs[i];
 }
}

接下来运行TestRunner,您发现程式回报了Error:

仔细看一下您所设计的ObjectArray类别,显然程式撰写的太勿忙,忘了初始化Object阵列了,因而引发了 NullPointerException,这是程式设计上的错误,使得尚未进行断言之前就引发例外中断,所以修正一下ObjectArray,提供一个建构函式,预设产生长度为10的阵列:

ObjectArray.java

package onlyfun.caterpillar;
public class ObjectArray {
 private Object[] objs; 
 public ObjectArray() {
 objs = new Object[10];
 } 
 public Object setObject(int i, Object o) {
 return objs[i];
 }
}

再运行一次TestRunner,这次Error没了,但是有Failure:

这表示可以执行到assertEquals()完毕,但是断言失败,所以再设计程式以消除Failure:

ObjectArray.java

package onlyfun.caterpillar;
public class ObjectArray {
 private Object[] objs; 
 public ObjectArray() {
 objs = new Object[10];
 } 
 public Object setObject(int i, Object o) {
 objs[i] = o;
 return objs[i];
 }
}

再次运行TestRunner,这次应该可以通过测试了。

如果您的单元在执行时,使用throws声明会丢出某些例外,例如IOException,您希望可以在testXXXX()中测试例外是否真的被丢出,您可以使用try.....catch来处理,下面这个程式是个实例,假设java.io.FileReader是您所设计的类别,您提供一个测试档案 test.txt:

FileReaderTest.java

package onlyfun.caterpillar.test; 
import java.io.*; 
import junit.framework.TestCase; 
public class FileReaderTest extends TestCase { 
 public void testClose() throws IOException {
 FileReader reader = new FileReader("test.txt");
 reader.close(); 
 try { 
 reader.read(); 
 fail("reading file after closed" + 
 " and didn't throw IOException"); 
 } 
 catch(IOException e) {
 assertTrue(true);
 } 
 } 
 public static void main(String[] args) { 
 junit.textui.TestRunner.run(FileReaderTest.class); 
 } 
}

您所测试的可能是一个预期会丢出的例外,您想要看看当错误的情况成立时,是不是真会丢出例外,例如 testClose() 测试FileReader 在close()之后如果再read(),是不是会如期丢出IOException,您先行在方法中用try....catch捕捉了这个例外,如果没有如期丢出例外,则不会被catch捕捉,而程式流程继续往下,执行到 fail() 陈述,这表示例外处理没有发生,此时主动丢出一个Failure,表示程式的执行并不如您所预期的。

6.7 创建TestCalculator全过程

比方说,你刚写完代码代码1.1所示的Calculator类。

public class Calculator {

public double add(double number1, double number2)

{

return number1 + number2;

}

}

让我们来展示一下所有这些JUnit核心类是如何协同工作的,就以代码代码2.1所示的TestCalculator类为例。这是个非常简单的测试类,只有一个测试方法:

import junit.framework.TestCase;

public class TestCalculator extends TestCase

{

public void testAdd()

{

Calculator calculator = new Calculator();

double result = calculator.add(10, 50);

assertEquals(60, result, 0);

}

}

当你通过键入java junit.swingui.TestRunner TestCalculator来启动JUnit test runner的时候,JUnit框架执行了如下动作:

创建一个TestSuite。

创建一个TestResult。

执行测试方法(在这个例子中是testAdd)。

我们将用标准的UML序列图来表示这些步骤。

6.7.1 创建TestSuite

在后面的UML图中,我们会提到TestRunner类。虽然没有TestRunner接口,但是所有的JUnit test runner都继承自BaseTestRunner类,都叫做TestRunner:比如Swing test runner叫做junit.swingui.TestRunner;文本test runner叫做junit.textui.TestRunner。通过外推,我们把所有继承自BaseTestRunner的类都叫做 TestRunner。这意味着当我们提到TestRunner的时候,你可以在脑海中用任何JUnit test runner来替换它。图3-11 JUnit 创建了一个显式的test suite(若test case中定义了suite方法的话)

TestRunner一开始先寻找TestCalculator类中的suite方法。若找到了,那么TestRunner就会调用它(就如同图3-11所示的),这个suite方法会创建不同的TestCase类,并把它们加入test suite。

因 为TestCalculator类中没有suite方法,所以TestRunner创建了一个默认的TestSuite对象,如图3-12所示。图3-11和 图3-12的主要区别在于,在图3-12中,TestCalculator中的测试方法是通过Java的反射机制找出来的,而在图3-11中,suite方法显 式地定义了TestCalculator的测试方法。例如:

public static Test suite()

{

TestSuite suite = new TestSuite();

suite.addTest(new TestCalculator("testAdd"));

return suite;

}

图3-12 JUnit自动创建了test suite (若test case没有定义suite方法)

6.7.2 创建TestResult

图3-13展示了JUnit创建包含测试结果(成功、失败或者出错)的TestResult对象

的步骤。

图3-13 JUnit创建了一个TestResult对象来收集测试结果(不管结果是好是坏)

步骤如下:

1.在图中1处,TestRunner实例化了一个TestResult对象,在测试顺序执行的时候,这个对象将用于存放测试结果。

2.TestRunner向TestResult注册,这样在执行测试的过程中TestRunner就可以收到各种事件(2)。这是Observer模式的典型例子。TestResult会广播如下方法:

测试开始(startTest;参见4)。.

测试失败(addFailure;参见图3-14)。

测试抛出了未被预期的异常(addError;参见图3-14)。

测试结束(endTest;参见图3-14)。

3. 直到知道了这些事件,TestRunner就可以随着测试的进行而显示进度条,并且在出现了失败和错误的时候显示出来(而不必等到所有的测试都完成之后)。

4. TestRunner通过调用TestSuite的run(TestResult)方法来开始测试(3)。

5. TestSuite为它拥有的每个TestCase实例调用run(TestResult)方法。

6. TestCase使用传递给它的TestResult实例来调用其run(Test)方法,并把自身作为参数传递给run方法,这样TestResult 稍后就可以以用runBare(5)来回调它。JUnit需要把控制权交给TestResult实例的理由是,这样TestResult就可以通知所有的 listener,测试已经开始了(4)。

6.7.4 执行测试方法

在前一小节中,你已经看到,对于TestSuite中的每个TestCase,runBare方法都会被调用一次。在这个test case中,因为TestCalculator只有一个testAdd测试,所以runBare只会被调用一次。

图3-14描绘了执行单个测试方法的步骤。步骤如下:

在图中1处,runBare方法顺序执行setUp、testAdd和tearDown方法。

如果在执行这3个方法的过程中发生了任何失败或者错误,那么TestResult就会分别调用addFailure(3)和addError(4)来通知它的所有listener。

如果发生了任何错误,那么TestRunner会列出这些错误,否则进度条就是绿色的,从而让你得知代码没有问题。图3-14 JUnit执行一个测试方法并在测试结束后把失败和错误通知到所有test listener

当tearDown方法执行完毕后,测试就完成了。TestResult会通过调用endTest(e)把这个结果通告给所有的listener。

相关推荐

RACI矩阵:项目管理中的角色与责任分配利器

作者:赵小燕RACI矩阵RACI矩阵是项目管理中的一种重要工具,旨在明确团队在各个任务中的角色和职责。通过将每个角色划分为负责人、最终责任人、咨询人和知情人四种类型,RACI矩阵确保每个人都清楚自己...

在弱矩阵组织中,如何做好项目管理工作?「慕哲制图」

慕哲出品必属精品系列在弱矩阵组织中,如何做好项目管理工作?【慕哲制图】-------------------------------慕哲制图系列0:一图掌握项目、项目集、项目组合、P2、商业分析和NP...

Scrum模式:每日站会(Daily Scrum)

定义每日站会(DailyScrum)是一个Scrum团队在进行Sprint期间的日常会议。这个会议的主要目的是为了应对Sprint计划中的不断变化,确保团队能够有效应对挑战并达成Sprint目标。为...

大家都在谈论的敏捷开发&Scrum,到底是什么?

敏捷开发作为一种开发模式,近年来深受研发团队欢迎,与瀑布式开发相比,敏捷开发更轻量,灵活性更高,在当下多变环境下,越来越多团队选择敏捷开发。什么是敏捷?敏捷是一种在不确定和变化的环境中,通过创造和响应...

敏捷与Scrum是什么?(scrum敏捷开发是什么)

敏捷是一种思维模式和哲学,它描述了敏捷宣言中的一系列原则。另一方面,Scrum是一个框架,规定了实现这种思维方式的角色,事件,工件和规则/指南。换句话说,敏捷是思维方式,Scrum是规定实施敏捷哲学的...

敏捷项目管理与敏捷:Scrum流程图一览

敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示,以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例:这个流程图涵盖了Scrum框架的主要阶段和活动,其中包...

一张图掌握项目生命周期模型及Scrum框架

Mockito 的最佳实践(mock方法)

记得以前面试的时候,面试官问我,平常开发过程中自己会不会测试?我回答当然会呀,自己写的代码怎么不测呢。现在想想我好像误会他的意思了,他应该是想问我关于单元测试,集成测试以及背后相关的知识,然而当时说到...

EffectiveJava-5-枚举和注解(java枚举的作用与好处)

用enum代替int常量1.int枚举:引入枚举前,一般是声明一组具名的int常量,每个常量代表一个类型成员,这种方法叫做int枚举模式。int枚举模式是类型不安全的,例如下面两组常量:性别和动物种...

Maven 干货 全篇共:28232 字。预计阅读时间:110 分钟。建议收藏!

Maven简介Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。仔...

Java单元测试框架PowerMock学习(java单元测试是什么意思)

前言高德的技术大佬在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现...

Spring框架基础知识-第六节内容(Spring高级话题)

Spring高级话题SpringAware基本概念Spring的依赖注入的最大亮点是你所有的Bean对Spring容器的存在是没有意识的。但是在实际的项目中,你的Bean必须要意识到Spring容器...

Java单元测试浅析(JUnit+Mockito)

作者:京东物流秦彪1.什么是单元测试(1)单元测试环节:测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:1)单元测试:针对计算机程序模块进行输出正确性检验工作...

揭秘Java代码背后的质检双侠:JUnit与Mockito!

你有没有发现,现在我们用的手机App、逛的网站,甚至各种智能设备,功能越来越复杂,但用起来却越来越顺畅,很少遇到那种崩溃、卡顿的闹心事儿?这背后可不是程序员一拍脑袋写完代码就完事儿了!他们需要一套严谨...

单元测试框架哪家强?Junit来帮忙!

大家好,在前面的文章中,给大家介绍了以注解和XML的方式分别实现IOC和依赖注入。并且我们定义了一个测试类,通过测试类来获取到了容器中的Bean,具体的测试类定义如下:@Testpublicvoid...

取消回复欢迎 发表评论: