• 1. Junit培训资料2010·12
  • 2. 一、浅谈TDD 测试驱动开发,它是敏捷开发的最重要的部分。方法主要是先根据客户的需求编写测试程序,然后再编码使其通过测试。在敏捷开发实施中,开发人员主要从两个方面去理解测试驱动开发。 在测试的辅助下,快速实现客户需求的功能。通过编写测试用例,对客户需求的功能进行分解,并进行系统设计。我们发现从使用角度对代码的设计通常更符合后期开发的需求。可测试的要求,对代码的内聚性的提高和复用都非常有益。 在测试的保护下,不断重构代码,提高代码的重用性,从而提高软件产品的质量。可见测试驱动开发实施的好坏确实极大的影响软件产品的质量,贯穿了软件开发的始终。
  • 3. 二、JUnit4的用法介绍 Java 语言现在支持泛型、枚举、可变长度参数列表和注释,这些特性为可重用的框架设计带来了新的可能。 JUnit4利用 Java 5 的新特性(尤其是注释)的优势,使得单元测试比起用最初的 JUnit 来说更加简单。 测试方法 @Test 以前所有版本的 JUnit 都使用命名约定和反射来定位测试。例如,下面的代码测试 1 + 1 等于 2: import junit.framework.TestCase; public class AdditionTest extends TestCase { private int x = 1; private int y = 1; public void testAddition() { int z = x + y; assertEquals(2, z); } }
  • 4. 二、JUnit4的用法介绍测试方法 @Test 而在 JUnit4 中,测试是由 @Test 注释来识别的,如下所示: import org.junit.Test; import junit.framework.TestCase; public class AdditionTest { private int x = 1; private int y = 1; @Test public void testAddition() { int z = x + y; assertEquals(2, z); } }
  • 5. 二、JUnit4的用法介绍测试方法 @Test 使用注释来识别测试方法的优点是不再需要将所有的方法命名为 testAddition()、testXXX()的形式等等。 例如,下面的方法也可以工作: import org.junit.Test; import junit.framework.TestCase; public class AdditionTest { private int x = 1; private int y = 1; @Test public void addition() { int z = x + y; assertEquals(2, z); } }
  • 6. 二、JUnit4的用法介绍测试方法 @Test 使用这种方法的好处是: 允许我们遵循最适合的应用程序的命名约定。 我们可以将测试方法使用与被测试的类相同的名称(由开发组规范约定)。例如,LoginAction.login() 由 LoginActionTest.login()方法测试、LoginAction.check()由LoginActionTest.check()方法测试等等。 使用JUnit4后,测试用例类可以不继承TestCase类,所以我们也就可以扩展被测试类了。这种方法使得测试受保护的方法非常容易,我们只要将测试用例类继承被测试类,就可以测试受保护方法了。
  • 7. 二、JUnit4的用法介绍@Before(SetUp) JUnit 3 测试运行程序会在运行每个测试之前自动调用 setUp() 方法。该方法一般会初始化字段、准备数据等等。例如下面的 setUp() 方法,用于设定要加载的路由文件: public void setUp() { // 加载此测试用例的servicerouting配置文件 ServiceRouting.loadConfig("com/demo/servicerouting.conf"); // ... }
  • 8. 二、JUnit4的用法介绍@Before(SetUp) 在 JUnit4 中,我们仍然可以在每个测试方法运行之前初始化字段或准备数据。然而,完成这些操作的方法不再需要叫做 setUp(),只要用 @Before 注释来指示该方法即可,如下所示: @Before public void initialize() { // 加载此测试用例的servicerouting配置文件 ServiceRouting.loadConfig("com/demo/servicerouting.conf"); // ... }
  • 9. 二、JUnit4的用法介绍@Before(SetUp) JUnit4允许我们使用 @Before 来注释多个方法,这些方法都在每个测试之前运行: @Before public void initialize() { // 加载此测试用例的servicerouting配置文件 ServiceRouting.loadConfig("com/demo/servicerouting.conf"); // ... } @Before public void prepareRetData() { //... }
  • 10. 二、JUnit4的用法介绍@After(TearDown) 清除方法与初始化方法类似。在 JUnit3 中,我们要将方法命名为 tearDown() 才可以实现清除方法,但在JUnit4中,只要给方法添加@After标注即可。 例如: @After public static void clearContext() { ActionContext.getContext().put(StrutsStatics.HTTP_REQUEST, null); ActionContext.setContext(null); }
  • 11. 二、JUnit4的用法介绍@After(TearDown) 测试方法结束后清除为此测试用例准备的一些数据。 与 @Before 一样,也可以用 @After 来注释多个清除方法,这些方法都在每个测试之后运行。 我们不再需要显式调用在超类中的初始化和清除方法,只要它们不被覆盖,测试运行程序将根据需要自动为您调用这些方法。 超类中的 @Before 方法在子类中的 @Before 方法之前被调用(这反映了构造函数调用的顺序)。 @After 方法以反方向运行:子类中的方法在超类中的方法之前被调用。否则,多个 @Before 或 @After 方法的相对顺序就得不到保证。
  • 12. 二、JUnit4的用法介绍@Before和@After小结 假设测试类中有如下方法定义: @Before public void init(){...} @After public void destroy(){...} 则Before、After方法的执行流程如图所示:
  • 13. 二、JUnit4的用法介绍@Before和@After小结 这种方法有明显的缺陷,如果要初始化的是数据库的链接,或者是一个大的对象的话,而这些资源恰恰是整个测试用例类可以共用的,每次都去申请,确实是种浪费。所以JUnit4引入了@BeforeClass和@AfterClass。
  • 14. 二、JUnit4的用法介绍@BeforeClass和@AfterClass JUnit4 也引入了一个 JUnit3 中没有的新特性:类范围的 setUp() 和 tearDown() 方法。任何用 @BeforeClass 注释的方法都将在该类中的测试方法运行之前刚好运行一次,而任何用 @AfterClass 注释的方法都将在该类中的所有测试都运行之后刚好运行一次。 例如,假设类中的每个测试都使用一个数据库连接、一个非常大的数据结构,或者申请其他一些资源。不要在每个测试之前都重新创建它,您可以创建它一次,用完后将其销毁清除。该方法将使得有些测试案例运行起来快得多。 注意:被注释为 BeforeClass和AfterClass 的方法必须为static方法。
  • 15. 二、JUnit4的用法介绍@BeforeClass和@AfterClass 用法如下: @BeforeClass public static void classInit() { Map callRet = new HashMap(); List list = new ArrayList(); list.add(createMsgBean("TDE0001", "第一个错误消息")); … callRet.put(ErrorCodeMessageBean.codeMsgBeanKey, list); ServiceCall.expectLastCallReturn(callRet); } @Test public void oneTestMethod() {…} @AfterClass public static void classDestroy() {…}
  • 16. 二、JUnit4的用法介绍@BeforeClass和@AfterClass 这个特性虽然很好,但是一定要小心对待这个特性。它有可能会违反测试的独立性,并引入非预期的混乱。如果一个测试在某种程度上改变了 @BeforeClass 所初始化的一个对象,那么它有可能会影响其他测试的结果。也就是说,由BeforeClass申请或创建的资源,如果是整个测试用例类共享的,那么尽量不要让其中任何一个测试方法改变那些共享的资源,这样可能对其他测试方法有影响。它有可能在测试套件中引入顺序依赖,并隐藏 bug。
  • 17. 二、JUnit4的用法介绍@BeforeClass和@AfterClass BeforeClass和AfterClass的执行流程如下:
  • 18. 二、JUnit4的用法介绍测试异常@Test(expected=XXXException.class) 异常测试是 JUnit4 中的最大改进。旧式的异常测试是在抛出异常的代码中放入 try 块,然后在 try 块的末尾加入一个 fail() 语句。 例如,该方法测试被零除抛出一个 ArithmeticException: public void testDivisionByZero() { try { int n = 2 / 0; fail("Divided by zero!"); } catch (ArithmeticException success) { assertNotNull(success.getMessage()); } }
  • 19. 二、JUnit4的用法介绍测试异常@Test(expected=XXXException.class) 上面的方法不仅难看,而且写起来也繁琐。在 JUnit 4 中,我们现在可以编写抛出异常的代码,并使用注释来声明该异常是预期的: @Test(expected = BusinessException.class) public void testExecuteNameEmpty() throws Exception { BookList bListAction = new BookList(); bListAction.setName(""); bListAction.execute(); }
  • 20. 二、JUnit4的用法介绍参数化测试 为了保证单元测试的严谨性,我们经常要模拟很多种输入参数,来确定我们的功能代码是可以正常工作的,为此我们编写大量的单元测试方法。可是这些测试方法都是大同小异:代码结构都是相同的,不同的仅仅是测试数据和期望输出值。 JUnit4 的参数化测试方法给我们提供了更好的方法,将测试方法中相同的代码结构提取出来,提高代码的重用度,减少复制粘贴代码的痛苦。
  • 21. 二、JUnit4的用法介绍参数化测试 例如下面的功能代码(格式化字符串) : public class WordDealUtil { public static String wordFormat4DB(String name) { if (name == null) { return null; } Pattern p = Pattern.compile("[A-Z]"); Matcher m = p.matcher(name); StringBuffer sb = new StringBuffer(); while (m.find()) { if (m.start() != 0) { m.appendReplacement(sb, ("_" + m.group()).toLowerCase()); } } return m.appendTail(sb).toString().toLowerCase(); } }
  • 22. 二、JUnit4的用法介绍参数化测试 没有使用参数化的测试用例代码: public class WordDealUtilTest { @Test public void wordFormat4DBNull() { // 测试 null 时的处理情况 assertNull(WordDealUtil.wordFormat4DB(null)); } @Test public void wordFormat4DBEmpty() { // 测试空字符串的处理情况 assertEquals("", WordDealUtil.wordFormat4DB("")); }
  • 23. 二、JUnit4的用法介绍参数化测试 续: @Test public void wordFormat4DBegin() { String result = WordDealUtil.wordFormat4DB(" EmployeeInfo "); // 测试当首字母大写时的情况 assertEquals(“employee_info, result); } @Test public void wordFormat4DBEnd() { String result = WordDealUtil.wordFormat4DB("employeeInfoA"); // 测试当尾字母为大写时的情况 assertEquals("employee_info_a", result); } @Test public void wordFormat4DBTogether() { String result = WordDealUtil.wordFormat4DB("employeeAInfo"); // 测试多个相连字母大写时的情况 assertEquals("employee_a_info", result); } }
  • 24. 二、JUnit4的用法介绍参数化测试 使用参数化的测试用例代码: @SuppressWarnings("unchecked") @RunWith(Parameterized.class) public class WordDealUtilTestWithParam { private String expected; private String target; @Parameters public static Collection words() { return Arrays.asList(new Object[][] { { “employee_info”, “employeeInfo" }, { null, null }, { “”, “” }, { “employee_info”, “EmployeeInfo” }, { “employee_info_a”, “employeeInfoA” }, { “employee_a_info”, “employeeAInfo” } }); }
  • 25. 二、JUnit4的用法介绍参数化测试 续: /** * 参数化测试必须的构造函数 * @param expected 期望的测试结果,对应参数集中的第一个参数 * @param target 测试数据,对应参数集中的第二个参数 */ public WordDealUtilTestWithParam(String expected, String target) { this.expected = expected; this.target = target; } /** * 测试将 Java 对象名称到数据库名称的转换 */ @Test public void wordFormat4DB() { Assert.assertEquals(expected, WordDealUtil.wordFormat4DB(target)); } }
  • 26. 二、JUnit4的用法介绍参数化测试 很明显,代码简单且很清晰了。在静态方法 words 中,我们使用二维数组来构建测试所需要的参数列表,其中每个数组中的元素的放置顺序并没有什么要求,只要和构造函数中的顺序保持一致就可以了。现在如果再增加一种测试情况,只需要在静态方法 words 中添加相应的数组即可,不再需要复制粘贴出一个新的方法出来了。 这种参数化的测试用例写法,很适用于一些共用的功能方法。
  • 27. 三、Mock Object引入 在实际项目中,开发人员自己的代码往往需要和其他的代码模块或系统进行交互,但在测试的过程中,这些需要被调用的真实对象常常很难被实例化,或者这些对象在某些情况下无法被用来测试,例如,真实对象的行为无法预测,真实对象的行为难以触发,或者真实对象的运行速度很慢。这时候,就需要使用模拟对象技术(Mock),利用一个模拟对象来模拟我们的代码所依赖的真实对象,来帮助完成测试,提高测试覆盖率,从而提高代码质量。 在实施测试驱动开发过程中,我们可能会发现需要和系统内的某个模块或系统外某个实体交互,而这些模块或实体在您做单元测试的时候可能并不存在,比如我们开发Action代码,需要调用到Service的代码,有可能Service代码还未完成,或者完成了但并不稳定。这时开发人员就需要使用 Mock 技术来完成单元测试。 现在我们使用的用于Mock Object的库有Spring Mock、jMockit。
  • 28. 三、Mock Object模拟依赖容器的对象 当我们需要用到HttpServletRequest、HttpServletResponse、ServletContext对象时,由于这些都是与容器相关,所以我们需要Mock这些对象。模拟对象技术利用了在面向接口的编程中,由于代码直接对接口进行调用,所以代码并不知道引用的是真实对象还是模拟对象,这样就可以顺利的完成对代码的测试。
  • 29. 三、Mock Object模拟依赖容器的对象 例如以下代码使用Spring mock进行模拟对象: @BeforeClass public static void init() { Map context = new HashMap(); context.put(ActionContext.APPLICATION, new HashMap()); ActionContext actionContext = new ActionContext(context); ActionContext.setContext(actionContext); HttpServletRequest req = new MockHttpServletRequest(); req.setAttribute("KEY1", "LIMING"); actionContext.put(StrutsStatics.HTTP_REQUEST, req); }
  • 30. 三、Mock Object模拟依赖容器的对象 我们模拟了ActionContext、HttpServletRequest,这样我们在测试方法中便可以调用ActionContext.getContext()得到ActionContext对象、getActionContext().get(StrutsStatics.HTTP_REQUEST)得到HttpServletRequest对象。 :
  • 31. 四、jMockit用法简介引言: jMockit 是用以帮助开发人员编写测试程序的一组工具和API,在单元测试中,经常需要进行一些mock操作。 EasyMock、jMock等众多的mock框架当测试的代码包含了一些静态方法,可能就让问题变得难以解决。 jMockit是一个能帮我们解决以上问题的轻量级框架,他允许你动态的改变已有的方法,这样便可以使得jMockit能够适应几乎所有的设计。它允许你重定义private,static and final方法,甚至是Mock构造方法。
  • 32. 四、jMockit用法简介引入jMockit依赖: mockit jmockit 0.999.4 test
  • 33. 四、jMockit用法简介jMockit使用步骤: 一个完整的Mock会有三个步骤, 录制: Expectations 、 NonStrictExpectations ; 回放: 在此阶段,录制的方法可能会被调用; 验证: 如果是Expectations没有必要做Verifications。
  • 34. 四、jMockit用法简介有代表性的示例: 待测试代码: public class BookList extends ActionSupport { private String name; private String orderId; @Override public String execute() throws Exception { if (StringUtils.isEmpty(name)) { throw new BusinessException("~", "name cant't empty."); } if (!StringUtils.equals("liming", name.trim())) { throw new MessageException(name + " have no limits."); } Map ret = new ServiceCall("frm").call(JMockService.queryDtlInfo, null); orderId = (String) ret.get("OrderId"); return SUCCESS; } ... }
  • 35. 四、jMockit用法简介有代表性的示例: 测试用例类: @RunWith(JMockit.class) public class BookListTest { @Mocked 步骤1 private ServiceCall sCall = null; @Test public void testExecuteNormal() throws Exception { new mockit.Expectations() { 步骤二 { new ServiceCall("frm").call(JMockService.queryDtlInfo, null); … returns(ret); } }; BookList bListAction = new BookList(); bListAction.setName("liming"); bListAction.execute(); 步骤三 Assert.assertEquals("9800000000", bListAction.getOrderId()); } }
  • 36. 四、jMockit用法简介有代表性的示例: 示例讲解: 步骤一:声明Mock对象ServiceCall,使ServiceCall的所有方法均被Mock。 步骤二:录制。模拟ServiceCall的call方法,并设定预期值。 步骤三:回放。在Action中执行ServiceCall对象的call方法。 此示例不需要进行验证。因为录制是用Expectations(严谨的)进行的,若使用NonStrictExpectations进行录制,则可以进行验证。
  • 37. 四、jMockit用法简介Mock静态方法: 以模拟UserTokenProcessor来进行演示,jMockit如何Mock静态方法。 @RunWith(JMockit.class) public class MockUsertokenProcessorTest { @SuppressWarnings("unused") @Mocked private UserTokenProcessor processor = null; @Test public void testGetNull() { new Expectations() { { UserTokenProcessor.getUserToken(); returns(null); } }; Assert.assertNull(UserTokenProcessor.getUserToken()); } }
  • 38. 四、jMockit用法简介Mock静态方法: 以模拟UserTokenProcessor来进行演示,jMockit如何Mock静态方法。 @RunWith(JMockit.class) public class MockUsertokenProcessorTest { @SuppressWarnings("unused") @Mocked private UserTokenProcessor processor = null; @Test public void testGetNull() { new Expectations() { { UserTokenProcessor.getUserToken(); returns(null); } }; Assert.assertNull(UserTokenProcessor.getUserToken()); } }
  • 39. (本页无文本内容)