做完基本的web环境 配置和Spring配置,紧接就要填设计entity模型并编写它们在数据层和业务层了吧?先不着急,有很多Web通用的东西的需要准备,比如说,最基本东东,单元测试。好的,今天就让我的Web Application 建立起单元测试模块吧。关于这一点,很快我们会想到两个熟悉的框架,JUnit TestNG。也不知道众高手们喜欢哪一个,笔者选择的是JUnit 

     JUnit 4比起3要灵活好多,用注解的方式规避了一些诸如方法名必须test开头的硬性要求,而且我们本身就是需要去更加熟悉注解的好处的。那么,果断搜罗Junit4的资料吧。Junit 的官网服务器down中,不过还是可以从github下截最新的jar junit-4.11.jar,当然如果需要看源码,可以解junit4.11.zip 这一包找到所有你需要的jars

      首先建立另外的源包testsrc,无论是什么IDE,都可以设置不部署/发布该包中的build,很明显,这样设置的原因在于test代码不需要在product中存在。那么上文所说的jar 也没有必要放在WEB-INF/lib下,只需要设置在IDE或说开发环境的classpath下(笔者是eclipse添加user lib 'SpringJunit4' )。当然,如果需要版本控制系统track测试源码包,就需要将jar包放到合适的位置了。

                                           图1

       接着,在com.xxxx.webmodel.util建个工具class,例如一定会用到的String 加密或者是文件内容摘要的工具类,这里笔者命名它为DataHash. 代码如下


  
  1. package com.xxxxx.webmodel.util; 
  2.  
  3. import java.security.MessageDigest; 
  4. import java.security.NoSuchAlgorithmException; 
  5.  
  6. public class DataHash { 
  7.     private MessageDigest msgDigest; 
  8.     private String d4tAlgorithm="SHA1"
  9.     public MessageDigest getMsgDigest() { 
  10.         return msgDigest; 
  11.     } 
  12.  
  13.     public void setMsgDigest(MessageDigest msgDigest) { 
  14.         this.msgDigest = msgDigest; 
  15.     } 
  16.      
  17.     public String encryptData(byte[] content){ 
  18.         String result =null
  19.         if(msgDigest == null
  20.             try { 
  21.                 msgDigest = MessageDigest.getInstance(d4tAlgorithm); 
  22.                 byte[] resultBytes =msgDigest.digest(content); 
  23.                 result= new String(resultBytes); 
  24.             } catch (NoSuchAlgorithmException e) { 
  25.                 e.printStackTrace(); 
  26.             } 
  27.         return result; 
  28.     } 
  29.      
  30.     public String encryptData(String string){ 
  31.         byte[] bytes = string.getBytes(); 
  32.         return this.encryptData(bytes); 
  33.     } 
  34.      
  35.     public boolean passWords(String typedString,String correctString){ 
  36.         if(typedString!=null && correctString !=null){ 
  37.         String ecStr = this.encryptData(typedString); 
  38.         return ecStr.equals(correctString);} 
  39.         else return false
  40.     } 
  41.  
  42. }   
 

       eclipse 中 右键点击该类文件,New-> JUnit Test Case , 弹出了生成TestCase 的属性对话框,有Jutni3 4 的单选框,这里选择4,将Source folder Browsetestsrc,将package 名中的项目名和模块名间加个test。将setUpBeforeClass, tearDownAfterClass setUp tearDown 四个方法前的checkbox勾上。

图2

        点Next后, 将要测试的方法前checkbox勾上,

图3 

        Finish 后就生成了testsrccom.xxxxx.webmodel.test.util包下的DataHashTest.java. 其中setUpBeforeClass()由@BeforeClass 注解,tearDownAfterClass由@AfterClass注解以及setUp tearDown分别被@BeforeClass@AfterClass注解。 都是什么意思呢?一试便知。在每个方法中写入方法标识的控制台输出语句,顺便加上构造方法一并测试。代码如下:

      


  
  1. package com.xxxxx.webmodel.test.util; 
  2.  
  3. import static org.junit.Assert.fail; 
  4.  
  5. import org.junit.After; 
  6. import org.junit.AfterClass; 
  7. import org.junit.Assert; 
  8. import org.junit.Before; 
  9. import org.junit.BeforeClass; 
  10. import org.junit.Test; 
  11.  
  12. import com.gxino.webmodel.util.DataHash; 
  13.  
  14. public class DataHashTest { 
  15.  
  16.     @BeforeClass 
  17.     public static void init() throws Exception { 
  18.         System.out.println("BeforeClass"); 
  19.     } 
  20.  
  21.     @AfterClass 
  22.     public static void dispose() throws Exception { 
  23.         System.out.println("AfterClass"); 
  24.     } 
  25.      
  26.     public DataHashTest(){ 
  27.         System.out.println("Constructor"); 
  28.     } 
  29.  
  30.     @Before 
  31.     public void startTest() throws Exception { 
  32.         System.out.println("Before"); 
  33.     } 
  34.  
  35.     @After 
  36.     public void endTest() throws Exception { 
  37.         System.out.println("After"); 
  38.     } 
  39.  
  40.     @Test 
  41.     public void testEncryptDataString() { 
  42.         System.out.println("Test1"); 
  43.         fail("Not yet implemented"); 
  44.     } 
  45.  
  46.     @Test 
  47.     public void testPassWords() { 
  48.         System.out.println("Test2"); 
  49.         fail("Not yet implemented"); 
  50.     } 
  51.  

        在Eclipse的编辑器中右键选择Run as或者Debug as, 次级对话框中选择JUnit Test, 控制台会输出

BeforeClass

Constructor

Before

Test1

After

Constructor

Before

Test2

After

AfterClass

       很明显setUpBeforeClass会在整个测试周期的开始被执行,tearDownAfterClass是整个测试周期结束时执行, 每个一方法的测试周期都会生成一个新的测试类对象,然后按以下顺序执行:setUp 测试方法,tearDown.由于JUnit4中方法名不需固定,用注解即可,所以这四个方法的名字是可以自定义的,笔者这里换为了init,dispose, startTest endTest.

       之后,就真正测试一下吧,在本测试类里加入成员变量 testedObj, 类型是DataHash, 并完成那那个方法,代码如下

 


  
  1. private DataHash testedObj; 
  2.     @Test 
  3.     public void testEncryptDataString() { 
  4.         String testedStr = "Test string"
  5.         String testedOut = testedObj.encryptData(testedStr); 
  6.         Assert.assertFalse(testedStr.equals(testedOut)); 
  7.          
  8.         System.out.println("Test1"); 
  9.     } 
  10.  
  11.     @Test 
  12.     public void testPassWords() { 
  13.         String testedStr = "Test string"
  14.         String testedOut = testedObj.encryptData(testedStr); 
  15.         Assert.assertTrue(testedObj.passWords(testedStr, testedOut)); 
  16.         System.out.println("Test2"); 
  17.     } 
 

        初始化testedObj的语句可以放在构造方法里,或是setUp中,当然也可以做成静态变量,在startUpBeforeClass方法中初始化使得整个测试周期只用一个DataHash的对象。同样的方法运行这个写好的测试类,结果如下:

                                                图4

 

        发现第二个方法测出了问题,去Debug发现,在上面DataHashString encryptData(byte[] content)方法中if(msgDigest == null) 写错了地方,应该是作为判断是否初始化msgDigest的,结果写在了try块的外面。

       更改后,再运行,测试通过。

     

        这里有两个测试方法,如果测试方法多了,会出现一个问题,就是有些测试方法还未写完,或说有些方法不需要在整个测试周期中被测试时,却想要集中测试一个或多个方法该怎么办? @Ignore 注解在这里就发挥了作用。在不需要被测试的方法上面标注@Ignore 使其在测试运行时被忽略。

       上述的测试方案只能够运用在单独写那么一个类,可是我们的WebApplication通常是有上下文环境的。例如一个Service类对象中,需要有一个数据访问层的对象,建立这个对象需要连数据库等等的配置,即使都在@BeforeClass注解的方法中做到了,有时也还是需要一些事务类的方法做切面的, OK@Before @After是可以做到,不过要在每个类中去重写这些代码,工程量未免有些太大了?

       说来说去,就是缺少Spring架在Web中的环境嘛,Spring早为我们做好了,那就是org.springframewor.test包的作用。不必啰嗦,直接上代码吧:

 


  
  1. package com.xxxxxx.webmodel.test.entity; 
  2.  
  3. import javax.annotation.Resource; 
  4.  
  5. import org.junit.Assert; 
  6. import org.junit.BeforeClass; 
  7. import org.junit.Test; 
  8. import org.junit.runner.RunWith; 
  9. import org.springframework.test.context.ContextConfiguration; 
  10. import org.springframework.test.context.TestExecutionListeners; 
  11. import org.springframework.test.context.junit4.SpringJUnit4Cla***unner; 
  12. import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; 
  13. import org.springframework.test.context.support.DirtiesContextTestExecutionListener; 
  14.  
  15. import com.gxino.webmodel.util.ApplicationContext; 
  16. import com.gxino.webmodel.util.WebConfiguration; 
  17.  
  18. @RunWith(SpringJUnit4Cla***unner.class
  19. @ContextConfiguration(classes={ApplicationContext.class}) 
  20. @TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) 
  21. public class ComponetSingletonTest { 
  22.     @BeforeClass 
  23.     public static void init() throws Exception{ 
  24.         new WebConfiguration().onStartup(null); 
  25. //      Properties defaultParameters= WebConfiguration.getSysParams(); 
  26. //      defaultParameters.put("jdbc.driver", "com.mysql.jdbc.Driver"); 
  27. //      defaultParameters.put("jdbc.url", "jdbc:mysql://localhost/webmodel"); 
  28. //      defaultParameters.put("jdbc.username", "root"); 
  29. //      defaultParameters.put("jdbc.password", "root"); 
  30. //      defaultParameters.put("hibernate.dialect","org.hibernate.dialect.MySQLDialect"); 
  31.     } 
  32.      
  33.     private org.springframework.context.ApplicationContext appCtx; 
  34.      
  35.     public org.springframework.context.ApplicationContext getAppCtx() { 
  36.         return appCtx; 
  37.     } 
  38.     @Resource 
  39.     public void setAppCtx(org.springframework.context.ApplicationContext appCtx) { 
  40.         this.appCtx = appCtx; 
  41.     } 
  42.  
  43.     @Test 
  44.     public final void testWhetherAccountEntityPrototype() { 
  45.         Object objAccountA=appCtx.getBean("accountEntity"); 
  46.         Object objAccountB=appCtx.getBean("accountEntity"); 
  47.         Assert.assertNotSame(objAccountA,objAccountB); 
  48.         Object persitenceA=appCtx.getBean("hibernatePersistencePin"); 
  49.         Object persitenceB=appCtx.getBean("hibernatePersistencePin"); 
  50.         Assert.assertSame(persitenceA, persitenceB); 
  51.     } 
  52.  

        这里关键就要是类头上的那三个注解了吧。 @RunWithjunit的注解,用以将环境代入,具体的环境的类应该是由 它(上下文环境)方自定义的类,上面用的是Spring 中的一个runner类,这样Spring的上下文环境,就被代到了测试中。@ContextConfiguration将我们写的注解配置引入,另外在测试运行时需要到一些Spring的中的bean,那就由@TestExecutionListeners来完成,这里面配置了 提供依赖注入的Listener和刷新bean配置(一些测试方法会改变bean原来的配置)的 脏位Listener,在测试DAO时还会用到解析事务配置的Listener.

       这个类中,还需要要加载一些sysParam上的配置,本来我是在@BeforeClass中完成了,上面代码段中注释掉的代码,但有时还是会与已经写好的properties文件弄混,就是在想“本来properties已经配置好了,怎么在这里没有?” 毕竟测试环境不是Web环境,为了逼真地将Web环境代入,笔者加了些代码在WebConfigurationonStartUp中,主要目的就是发现加载不到sysParams.properties时,去以非web的方式(开发环境的相对文件路径)找到sysParam.properties 文件并加载。 然后在@BeforeClass的方法中去调用onStartUp方法,没有servletContext,传个null进去就好。

      上面的代码的测试用例,笔者是针对Spring的单例与多例模式做个简单的测试,读者们可以做下这个测试。accountEntity是标注了@Scope(“prototype”)的beanhibernatePersistencePin是未标注的Scope即单例的bean。将springapplicationContext注入到该测试类中的appCtx, 以使上述两种bean可以从中得到。

      关于单元测试的内容,就分享到这里。