JUnit 南京大学软件学院
课程内容 单元测试简介 JUnit 简介 JUnit 核心类 / 接口 JUnit 单元测试的步骤 JUnit 实例 JUnit 在 Eclipse 下的使用 JUnit 最佳实践
什么是软件测试 为了发现错误而执行程序的过程 IEEE 提出的软件工程标准术语中, 软件测试被定义为: “ 使用人工和自 动手段来运行或测试某个系统的过 程,其目的在于检验它是否满足规 定的需求或弄清楚预期结果与实际 结果之间的差别。 ”
测试类型 单元测试 集成测试 功能测试 压力 / 负荷测试 验收测试
单元测试 单元测试是开发者编写的一小段代 码,用于检验被测代码的一个很小 的、很明确的功能是否正确 通常而言,一个单元测试是用于判 断某个特定条件(或者场景)下某 个特定函数的行为
单元测试的方法 人工静态分析:通过人工阅读代码的方 式来查找代码中存在的错误 自动静态分析:使用代码复查工具,主 要用来发现语法特征错误 自动动态测试:用工具自动生成测试用 例并执行被测程序,主要用来发现行为 特征错误 人工动态测试:人工设定程序的输入和 预期输出,执行程序,判断实际输出是 否符合预期,若不符则自动报告错误。 利用 JUnit 完成的便是人工动态测试
单元测试的必要性 带来比功能测试更广范围的测试覆 盖 让团队协作成为可能 能够防止衰退,降低对调试的需要 能为我们带来重构的勇气 能改进实现设计 当作开发者文档使用
JUnit 简介 JUnit 是 Java 社区中知名度最高的单 元测试工具。由 Erich Gamma 和 Kent Beck 共同开发完成 开源软件 支持语言 – Smalltalk, Java, C++, Perl 等等 支持的 IDE – JBuilder, VisualAge,Eclipse 等
JUnit 功能 可供选择的其他前端或者 test-runner ,用 来显示你的测试结果 用单独的 classloader 来运行每个单元测试, 以避免副作用 标准的资源初始化和回收方式( setUp 和 tearDown ) 各种不同的 assert 方法,让你检查测试结 果的操作变得更容易 同流行的工具,比如 Ant ,以及流行 IDE 比 如 Eclipse , JBuilder 整合
JUnit 的好处 开源工具,可以免费使用,可以找到很 多实际项目中的应用示例。由于源码开 放,开发者还可以根据需要扩展 JUnit 功 能 可以将测试代码和产品代码分开 测试代码编写容易,功能强大 自动检验结果并且提供立即的反馈 易于集成到开发的构建过程中,在软件 的构建过程中完成对程序的单元测试 测试包结构便于组织和集成运行,支持 图形交互模式和文本交互模式
JUnit 安装 Java 的 JUnit 可从网上免费下载 将下载的 junit.zip 解压到你指定的目录 设置环境变量 – Variable:CLASSPATH – Variable Value:.;Install Path/junit.jar 测试运行(进入命令提示符安装目录下) – java junit.swingui(textui,awtui).TestRunner junit.samples.AllTests
JUnit 框架
JUnit 核心类及接口 (1)
JUnit 核心类及接口 (2) TestRunner (测试运行器) – 没有 TestRunner 接口,只有一个所有 TestRunner 都继承的 BaseTestRunner – 执行测试并提供相关的结果的统计信 息 包含三个 TestRunner 类 – 一个用于文本控制台 – 一个用于 Swing – 还有一个 AWT (遗产代码,很少有人用)
JUnit 核心类及接口 (3) TestRunner (测试运行器) – 实际运用中的 Swing test runner – Green bar 通过测试 – Red bar 测试失败
JUnit 核心类及接口 (4) TestCase (测试用例) – 把具有公共行为的测试归入一组 – 扩展了 JUnit 的 TestCase 类的类。它以 testXXX 方法的形式包含一个或多个测 试 – 典型的 TestCase 包含两个主要部件 fixture 单元测试
JUnit 核心类及接口 (5) TestCase (测试用例) – Fixture 管理资源,复用配置代码 运行一个或多个测试所需的公用资源或者 数据集合 TestCase 通过 setUp 和 tearDown 方法来创建 和销毁 fixture 典型应用数据库连接,生成输入文件
JUnit 核心类及接口 (6) TestCase (测试用例) – 创建单元测试方法 继承自 TestCase 的一组 assert 方法封装了最 常见的测试任务,这些 assert 方法可以极 大地简化单元测试的编写
Assert 超类所提供的 8 个核心方 法
JUnit 核心类及接口 (7) TestCase (测试用例) – 创建单元测试方法 除了 Assert 提供的方法之外, TestCase 还实 现了 10 个它自己的方法 coutTestCases , CreateResult , getName , run , runBare , setName , setup , teardown , toString
TestCase 的 10 个自己的方法
JUnit 核心类及接口 (8) TestCase (测试用例) – 这 18 个方法共同为你提供了使用 JUnit 编写测试的全部功能
JUnit 核心类及接口 (9) TestSuite (测试集合) – test suite 是把多个相关测试归入一组便 捷方式 – 若你没有提供自己的 TestSuite , test runner 会自动创建一个 – 缺省的 TestSuite 不能满足时,可能会 想组合多个 suite ,把它们作为主 suite 的一部分,这些 suite 来自几个不同的 package
JUnit 核心类及接口 (10) TestSuite (测试集合) – 通常情况下 TestAll 类仅仅包括一个静态 的 suite 方法,这个方法会注册应用程 序需要定期执行的所有 Test 对象(包括 TestCase 对象和 TestSuite 对象),下面 是一个典型的 TestAll 类
TestAll 类 import junit.framework.Test; import junit.framework.TestSuite; import junitbook.sampling.TestDefaultController; public class TestAll extend TestCase { public static Test suite() { TestSuite suite = new TestSuite("All tests from part 1"); suite.addTestSuite(TestCalculator3.class); suite.addTestSuite(TestDefaultController.class); return suite; }
JUnit 核心类及接口 (11) TestResult – 所有的 TestSuite 都有一个对应的 TestResult – 负责收集 TestCase 的执行结果。储存了 所有测试的详细情况,是通过还是失 败。失败则会创建一个 TestFailure 对象 – TestRunner 使用 TestResult 来报告测试 结果 。没有 TestFailure 对象进度条就用 绿色,否则进度条用红色并输出失败 测试的数目
JUnit 核心类及接口 (12) TestResult – JUnit 区分失败和错误 失败:是可以预期的,代码的改变不时会 造成断言失败,你只要修正代码,断言就 可以再次通过 错误:比如常规程序抛出的异常,则是测 试时不可预料的
JUnit 核心类及接口 (13) TestListener – 帮助对象访问 TestResult 并创建有用的 报告。 – 虽然 Testlistener 接口是 JUnit 框架的重 要部分,但是你编写自己的测试时不 必实现这个接口。只有需要扩展 JUnit 框架时才会需要实现这个接口
JUnit 单元测试的步骤 (1) JUnit 成员三重唱,共同产生测试结果 – 当你需要编写更多的 TestCase 的时候,你可 以创建更多的 TestCase 对象。当你需要一次 执行多个 TestCase 对象的时候,您可以创建 一个 TestSuite 对象或使用缺省的 TestSuite 对象 进行封装。为了执行 TestSuite ,需要使用 TestRunner 。通过 TestRunner 的执行生成 TestResult 对象
JUnit 单元测试的步骤 (2) 1. 重载 setUp() ,封装测试环境初始化及测 试数据准备 2. 设计测试方法,以 testXXX 命名 3. 在测试方法中使用断言方法如 assertEquals() , assertTrue() 等 4. 设计测试套件,或使用缺省的测试套件, 调用 TestRunner 执行测试脚本,生成测 试结果 5. 重载 tearDown() 析构测试环境,执行收 尾动作
待测类 public class Calculator { public int add(int a, int b){ return a + b; } public int minus(int a, int b){ return a - b; } public int multiply(int a, int b){ return a * b; } public int divide(int a, int b) throws Exception{ if(0 == b){ throw new Exception(" 除数不能为零! "); } return a / b; }
该类的测试类 [1/4] public class CalculatorTest extends TestCase { private Calculator cal; public void setUp() { cal = new Calculator(); } public void tearDown() { } 测试类必须以 TestCase 为父类 每个测试方法 执行前都会调 用该方法 析构测试环境,执行收尾动作 生成对象
该类的测试类 [2/4] public void testAdd() { int result = cal.add(1,2); Assert.assertEquals (3,result); } public void testMinus() { int result = cal.minus(1,2); Assert.assertEquals(-1,result); } public void testMultiply() { int result = cal.multiply(2,3); Assert.assertEquals(6,result); } Junit3.8 测试方法需满足: 1)Public 的 2)Void 的 3) 无方法参数 4) 方法名称必须以 test 开头 断言 调用该方法
该类的测试类 [3/4] public void testDivide() { int result = 0; try{ result = cal.divide(6,4); } catch (Exception e){ e.printStackTrace(); Assert.fail(); } Assert.assertEquals(1,result); } 期望该行代码永远 不会被执行,断言 失败,停止执行立 即失败
该类的测试类 [4/4] public void testDivide2() { Throwable tx = null; try{ cal.divide(4,0); Assert.fail(); } catch(Exception ex){ tx = ex; } Assert.assertNotNull(tx); Assert.assertEquals(Exception.class,tx.getClass()); Assert.assertEquals(" 除数不能为零! ",tx.getMessage()); } 一个方法可以有 多个测试方法, 输入的不同情况 会有不同的 testcase 出现 tx 是 Exception 类型的 一旦发生异常,则 tx 一定不为空 期望该行代码永远 不会被执行,断言 失败,停止执行立 即失败
同时测试多个类 (1) 现有两个测试类 – CalculatorTest.java – LargestTest.java 如何一下全部运行?如何自动化测 试?
同时测试多个类 (2) public class TestAll extends TestCase{ public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(CalculatorTest.class); suite.addTestSuite(LargestTest.class); return suite; } 必须是 static 返回一个 Test 类型 方法名必须 是 suite 生成一个 TestSuite 测试类对应的 class 对象,还可 增加 TestSuite 的 对象 返回 suite , TestSuite 实现了 Test 接口
在 Eclipse 下使用 JUnit(1) 首先新建一个项目 单元测试代码把它和被测试代码混在一 起,这显然会照成混乱 建议您分别为单元测试代码与被测试代 码创建单独的目录,并保证测试代码和 被测试代码使用相同的包名。这样既保 证了代码的分离,同时还保证了查找的 方便 遵照这条原则,我们在项目 HelloJUnit 根 目录下添加一个新目录 testsrc ,并把它 加入到项目源代码目录中
在 Eclipse 下使用 JUnit(2) 给新建一个 HelloJunit 类添加一个 abs ()方法 package example; public class HelloJunit { public static int abs(int n){ return n>=0?n:(-n); }
在 Eclipse 下使用 JUnit(3) 在类上右击,选择 “new”->“JUnit Test Case” 新建一个 HelloJUnitTest 类,用来测试 Hello 类 选择刚刚建立的文件夹 “Hello/testscr”, 选 中 setUp() 和 tearDown() ,然后点击 “Next” 选择要测试的方法,我们选中 abs(int) 方 法 点击 “finish” ,跳出需要添加 JUnit.jar 包, 点击 “OK”
在 Eclipse 下使用 JUnit(4) HelloJunitTest.java 文件中输入代码
在 Eclipse 下使用 JUnit(5) 在测试类上点击右键,在弹出菜单 中选择 “Run As”->“ JUnit Test”
表示测试通过 表示测试失败 JUnit 视 图工具栏 移动到下一 个故障点 移动到上一 个故障点 仅显示故障和错误 的测试方法 锁定滚动 重新运行上 次测试 重新运行上次测试 — — 首先解决故障 打开 JUnit 运 行历史记录 停止运行 JUnit
JUnit 最佳实践 (1) 一次只测试一个对象 – 单元测试独立地检查你创建的每个对 象,这样就可以在第一时间把它们隔 离起来。如果一次测试多于一个对象, 那么你就无法预测当这些对象发生改 变时它们会如何交互
JUnit 最佳实践 (2) 在 assert 调用中解释失败原因 – 用到 assert 方法时,使用第一个参数是 String 类型的那个签名,这个参数让你 可以提供一个有意义的文本描述,在 断言失败时 JUnit test runner 会显示这 个描述
JUnit 最佳实践 (3) 选择有意义的测试方法名 – 遵守 testXxx 的命名模式,其中 Xxx 是待 测方法名。若你需要为同一个方法增 加其他的测试,那么可以改用 testXxxYyy 的命名模式,其中 Yyy 描述了 测试的不同之处
JUnit 最佳实践 (4) 一个单元测试等于一个测试方法 – 不要试图把多个测试塞进一个方法, 这样导致的结果就是测试方法变得更 复杂,而且在测试方法中编写的逻辑 越多,测试失败的可能性也就越大, 需要调试的可能性也就越大
JUnit 最佳实践 (5) 同一个包,分离的目录 – 把所有测试和待测类都放在同一个包 中,但使用平行目录结构