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

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

ccwgpt 2024-10-13 01:38 29 浏览 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。

相关推荐

5 分钟搭建 Node.js 微服务原型(node 微服务架构)

微服务已成为在Node.js中构建可扩展且强大的云应用的主流方法。同时也存在一些门槛,其中一些难点需要你在以下方面做出决策:组织项目结构。将自定义服务连接到第三方服务(数据库,消息代理等)处理微服...

当前的前端,真的不配叫程序员吗?

今天看到一个比较令人震惊的帖子,说前端不配叫程序员,令我很吃鲸,是谁我就不说了,帖子出处是一个大龄程序员组里面的,想想也不觉得奇怪了,毕竟对于年龄比较大的程序员来说,前端起步比较晚,最开始就是一个切图...

聊聊asp.net中Web Api的使用(asp.net core web api教程)

扯淡随着app应用的崛起,后端服务开发的也越来越多,除了很多优秀的nodejs框架之外,微软当然也会在这个方面提供更便捷的开发方式。这是微软一贯的作风,如果从开发的便捷性来说的话微软是当之无愧的老大哥...

NodeJS中,listen Access:permission denied解决办法

错误描述:Win10系统,NodeJS程序。使用express框架开发的http服务器,启动时出现错误提示“listenAccess:permissiondenied"。错误原因:这是由于...

Hono — 下一代高性能web框架(天融信下一代vnp)

最近公司可能要有变革,要统计我们的技能。真的是很无语,但是有没有办法。哎,问豆包吧提起Hono大家可能很陌生,这是什么?但是我提到Expressjs、nodejs想必前端小伙伴很熟悉啊。那么Hon...

生活例子说明线程,简单明了(列举一个日常生活中的例子以程序的形式表示)

1.程序设计的目标在我看来单从程序的角度来看,一个好的程序的目标应该是性能与用户体验的平衡。当然一个程序是否能够满足用户的需求暂且不谈,这是业务层面的问题,我们仅仅讨论程序本身。围绕两点来展开,性能...

Node实战006:自定义模块的创建和使用详解

Node的应用是由模块组成的,每个文件的定义都是一个模块(module变量代表当前模块)并有自己的作用域。Node遵循commonjs的模块规范,用来隔离每个模块的作用域,使每一个模块在自身的命名空间...

Node.js基本内容和知识点(node.js的概念)

简单的说Node.js就是运行在服务端的JavaScript,起初段定位是后端开发语言,由于技术的不够成熟,一般小型项目会完全使用node.js作为后台支撑,大项目中,运行不够稳定,不会轻易使用...

干货 | 如何利用Node.js 构建分布式集群

引言在软件定义的世界里,企业通过Web应用和移动应用程序来提供大部分的服务,Node.js迅速成为时下最为流行的一个平台之一,就和它可以搭建响应速度快、易于扩展的web应用和移动应用有很大关系,并凭...

nodejs mongodb 实现简易留言板(node.js留言板)

一个朋友问了一下mongodb的一些操作问题我就做了下面这个简单的留言板给他做一个实例希望能帮助到他express的框架就不说了express的问题请移步nodejs之expressht...

nodejs mqtt 智能售货机系统物联网控制系统源码分享

智能售货机系统(Moleintelligentvendingmachinesystem)是一套物联网控制系统性的解决方案。主要涉及到的语言和库有c,c++,js,nodejs,vue.js,...

为什么 Node.js 这么火,而同样异步模式 Python 框架 Twisted 却十几年一直不温不火?

说nodejs只是靠营销的是否太天真了些?当初nodejs出来的时候各种BUG,我简单的测试其大文件传输都会出现各种问题。而同時期的其他阵营早就甩其几条街了。但是为什么却能一直不断发展壮大?...

2020年14个最有用的NodeJS库(node用什么数据库)

Express快速,简单,极简的节点Web框架对…有好处·易于处理多种类型的请求,例如GET,PUT,POST和DELETE请求·快速构建单页,多页和混合Web应用程序每周下载1100万Lice...

连载:2016年最好的JS框架和库(下)

继续上一期的介绍:Agility.jsAgility.js是专为JS服务的MVC库,你可以免费编写可再用和可维护的浏览器代码,Agility支持Js,样式(CSS)、内容(HTML)和行为(JS)。C...

awesome-nodejs 终极资源库:60K+星标的开发者宝藏

Node.js终极资源库:60K+星标的开发者宝藏引言在GitHub上,有一个备受瞩目的Node.js资源仓库,以其惊人的60.6k星标量和6kfork量,成为了Node.js开发者的必备参考。这个...

取消回复欢迎 发表评论: