• 1. Junit单元测试技术讲解人:邱百超 Email: badqiu(a)gmail.com
  • 2. 讲解内容测试技术介绍 JUnit3.8单元测试及实例 单元测试最佳实践 Mock Objects技术 JUnit相关工具及其扩展 Spring对测试的支持 JUnit4.0介绍 测试驱动开发(TDD) Q&A
  • 3. 软件测试类型1
  • 4. 软件测试类型2单元测试:测试单个对象是否工作正确 集成测试:测试多个对象之间,多个子系统之间以及系统环境配置是否工作正确 功能测试:测试整个应用是否工作正确 性能测试:测试系统的性能 验收测试:验证用户是否喜欢我的程序
  • 5. 单元测试由谁编写程序员本身 程序员Pairs
  • 6. JUnit开源的Java单元测试框架 结构: TestSuites TestCase [Java class] Test [Java method] Fixtures: common setup/teardown code
  • 7. JUnit3.8例子import junit.framework.TestCase; public class StringTest extends TestCase { public void testReplace() { String result = "abc".replace('a', 'b'); assertEquals("bbc",result); } public void testSubstring() { String result = "abc".substring(1); assertEquals("bc",result); } }
  • 8. 断言(assert)assertFalse assertEquals assertNotEquals assertNull assertSame …
  • 9. 运行JUnit測試(TestRunner)命令行 JUnit自帶的Swing界面 IDE集成(Eclipse,Netbean) Ant,Maven
  • 10. 最佳实践1(Test命名及存放)1.相同的package不同的存放位置 2.Calucator的测试类为CalucatorTest 3.add()的测试方法为testAdd()
  • 11. 最佳实践2细粒度的测试 一个测试只测试一个方法(或多个测试测试一个方法) 测试隔离 测试之间相互独立,没有依赖性 為assert增加描述
  • 12. TestCase生命周期(例子)
  • 13. TestSuites(一组测试)public class AllTests { public static void main(String[] args) { junit.textui.TestRunner.run(AllTests.suite()); } public static Test suite() { TestSuite suite = new TestSuite("Test for junitdemo"); //$JUnit-BEGIN$ suite.addTestSuite(StringTest.class); suite.addTestSuite(CalucatorTest.class); //$JUnit-END$ return suite; } }
  • 14. 评估测试结果测试结果报告 代码测试覆盖率报告
  • 15. 测试报告(例子)
  • 16. 测试覆盖率报告(例子)
  • 17. 最佳实践自动化运行测试 持续集成(CruiseControl)
  • 18. 测试技巧异常(exception)测试 Mock Objects
  • 19. 技巧:Exception测试
  • 20. Mock Objects测试技术允许你测试依赖于其它对象的方法,但那个对象非常难实例化或是太慢等. 例1 要测试Servlet的doGet()请求:HttpRequest,HttpResponse可以使用Mock 例2 要测试Service层的逻辑,但dao层要依赖数据库或是dao层只提供一个接口没有实现类
  • 21. Mock转帐实例1
  • 22. Mock转帐实例2(Account对象)public class Account{ private String accountId; private long balance; public Account(String accountId, long initialBalance){ this.accountId = accountId; this.balance = initialBalance; } public void debit(long amount){ this.balance -= amount; } public void credit(long amount){ this.balance += amount; } public long getBalance(){ return this.balance; } }
  • 23. Mock转帐实例3public interface AccountManager{ Account findAccountForUser(String userId); void updateAccount(Account account); }public class AccountService{ private AccountManager accountManager; public void setAccountManager(AccountManager manager){ accountManager = manager; } public void transfer(String senderId, String beneficiaryId,long amount){ Account sender = accountManager.findAccountForUser(senderId); Account beneficiary = accountManager.findAccountForUser(beneficiaryId); sender.debit(amount); beneficiary.credit(amount); this.accountManager.updateAccount(sender); this.accountManager.updateAccount(beneficiary); } }
  • 24. Mock转帐实例4(Mock Object)public class MockAccountManager implements AccountManager{ private Hashtable accounts = new Hashtable(); public void addAccount(String userId, Account account){ this.accounts.put(userId, account); } public Account findAccountForUser(String userId){ return (Account) this.accounts.get(userId); } public void updateAccount(Account account){ // do nothing } }
  • 25. Mock转帐实例5(测试)public class TestAccountService extends TestCase{ public void testTransferOk(){ //使用MockAccountManager MockAccountManager mockAccountManager = new MockAccountManager(); Account senderAccount = new Account("1", 200); Account beneficiaryAccount = new Account("2", 100); mockAccountManager.addAccount("1", senderAccount); mockAccountManager.addAccount("2", beneficiaryAccount); AccountService accountService = new AccountService(); accountService.setAccountManager(mockAccountManager); //转帐操作 accountService.transfer("1", "2", 50); //验证 assertEquals(150, senderAccount.getBalance()); assertEquals(150, beneficiaryAccount.getBalance()); } }
  • 26. JMock快速创建mock objects 使用动态生成代理避免需要手工编写Mock Object实现
  • 27. JMock2.4.0转帐实现public class JMockAccountServiceTest extends TestCase { Mockery context = new Mockery(); public void testTransferOk(){ final Account senderAccount = new Account("1", 200); final Account beneficiaryAccount = new Account("2", 100); //使用MockAccountManager final AccountManager mockAccountManager = context.mock(AccountManager.class); context.checking((new Expectations() {{ one(mockAccountManager).findAccountForUser("1"); will(returnValue(senderAccount)); one(mockAccountManager).findAccountForUser("2"); will(returnValue(beneficiaryAccount)); one(mockAccountManager).updateAccount(senderAccount); one(mockAccountManager).updateAccount(beneficiaryAccount); }})); AccountService accountService = new AccountService(); accountService.setAccountManager(mockAccountManager); //转帐操作 accountService.transfer("1", "2", 50); //验证 assertEquals(150, senderAccount.getBalance()); assertEquals(150, beneficiaryAccount.getBalance()); } }
  • 28. 为何没有测试Account对象
  • 29. 最佳实践只测试有可能导致失败的原因(如简单的值对象不用测试) 將private的方法改為package以便測試 为测试修改代码以便测试
  • 30. 测试驱动开发(TDD)极限编程的重要特点 基本思想就是在开发功能代码之前,先编写测试代码。
  • 31. 为什么编写单元测试更好的测试覆盖率(如某个if条件) 提升对你编写的代码信心 为重构提供支持 代码即是文档 提升设计 团队协作 可以快速的测试你的代码(避免启动服务器等)
  • 32. 单元测试类型逻辑单元测试 (JUnit) 集成单元测试 功能单元测试 (Selenium)
  • 33. 单元测试的使用普通的单元测试使用流程 修正一个bug或重构代码 运行测试确保修改正常 提交修改至svn or cvs
  • 34. 单元测试的基本原则为了能够自动化运行测试 容易编写 容易运行 容易识别(可以容易查看那里pass,那里failure) 可以重现
  • 35. Spring对测试的支持Web mock Jndi mock 数据库测试支持
  • 36. Spring对数据库测试的支持AbstractTransactionalDataSourceSpringContextTests
  • 37. Spring对Mock的支持MockHttpServletRequest MockHttpServletResponse SomeServlet.service(MockHttpServletRequest,MockHttpServletResponse) Jndi Mock
  • 38. Selenimu 功能测试工具可以模拟用户的操作 验证结果
  • 39. JUnit4.0介绍兼容3.8.1 不用强制继承TestCase类 使用注解(Annotation)来标注测试 对异常(exception)测试改进 最新版本为4.4
  • 40. JUnit4实例package junitdemo.junit4; import org.junit.After; import org.junit.Before; import org.junit.Test; // java1.5的 static import import static org.junit.Assert.*; public class Junit4Test { @Before public void init() throws Exception { System.out.println("init()"); } @Before public void setUp() throws Exception { System.out.println(“setUp()”); //两个Before } @After public void destroy() throws Exception { System.out.println("destroy()"); } //增加@Test表示为测试方法 @Test public void replace() { String result = "abc".replace('a', 'b'); assertEquals("bbc",result); } @Test(expected=ArithmeticException.class) public void divide() { int num = 9/0; //异常测试 } }
  • 41. 相關工具介紹“xUnit” - cppUnit,jsUnit DBUnit - 提供對數據庫测试数据的支持 代碼覆蓋工具 - Clover 持續集成工具 - CruiseControl 自动化测试工具 - Ant 功能单元測試工具 - Selenium
  • 42. 参考http://www.junit.org http://jmock.codehaus.org http://www.openqa.org JUnit in Action – Vincent Massol
  • 43. Q&A