SSH框架下單元測(cè)試的實(shí)現(xiàn)

實(shí)現(xiàn)的功能

  • 實(shí)現(xiàn)了部門的增刪改查
  • 對(duì)Action進(jìn)行了單元測(cè)試
  • 對(duì)Service 進(jìn)行了單元測(cè)試,通過(guò)mock的方式實(shí)現(xiàn)。

實(shí)現(xiàn)的步驟

一、對(duì)Action層的單元測(cè)試實(shí)現(xiàn)
1、首先在pom文件中需要引入的依賴

    
     <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>1.10.19</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.apache.struts</groupId>
        <artifactId>struts2-junit-plugin</artifactId>
        <version>2.1.8</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>3.2.8.RELEASE</version>
      </dependency> 

說(shuō)明:
在此處我們引入struts2-junit-plugin.2.1.8.jar,因?yàn)槠淅锩嬗袦y(cè)試Struts2中action的類
StrutsSpringTestCase,用來(lái)測(cè)試ssh中的action。
2、新建一個(gè)測(cè)試類
如果你使用的是idea,那么你可以直接在需要建立測(cè)試類的Action類中
按ctrl+shift+t,新建一個(gè)測(cè)試類。如DepartmentActionTest
3、如果是普通的action的話,
待測(cè)試代碼:

  public String findById() {
                 String did = ServletActionContext.getRequest().getParameter("did");
                 department=departmentService.findByDid(Integer.valueOf(did));
                 return "goEditDepartment";
             }  

測(cè)試代碼:

  @Test
             public void testFindById() throws Exception {
                  /*這個(gè)函數(shù)相當(dāng)@Before注解的函數(shù),是調(diào)用單元測(cè)試后的時(shí)候,
                首先會(huì)執(zhí)行的方法。可以在這里面做一些必要的準(zhǔn)備工作*/
                 request.setParameter("did", "1");
                 ActionProxy proxy = getActionProxy("/department_findById.action");
                 DepartmentAction action = (DepartmentAction) proxy.getAction();
                 String result = action.findById();
                 Assert.assertEquals("goEditDepartment", result);
             }

4、如果是返回json的action的話,待測(cè)試代碼:

   public void  findAll() {
            List<Department> departmentList= departmentService.findAll();
            JSONObject jsonObject = new JSONObject();
            if (CollectionUtils.isEmpty(departmentList)) {
                jsonObject.put("key", "success");
                jsonObject.put("data", JSON.toJSONString(departmentList));
                writeJson(jsonObject);
                return;
            }
            jsonObject.put("key", "success");
            jsonObject.put("data", JSON.toJSONString(departmentList));
            writeJson(jsonObject);
        }

測(cè)試代碼:

  String result = executeAction("/json_findAll.action");
           Assert.assertNotNull(result);
           JSONObject jsonObject = JSON.parseObject(result);
           if ("success".equals(jsonObject.getString("key"))) {
               List<Department> departmentList = JSON.parseArray(jsonObject.getString("data"), Department.class);
               if (CollectionUtils.isEmpty(departmentList)) {
                   return;
               }
               for (Department department : departmentList) {
                   System.out.println(department.toString());
               }
           }

5、關(guān)于lazy問(wèn)題的解決:首先在setUp()函數(shù)中加入下述代碼:

 @Override
            protected void setUp() throws Exception {
                super.setUp();
                SessionFactory sessionFactory = lookupSessionFactory(request);
                Session hibernateSession= getSession(sessionFactory);
                TransactionSynchronizationManager.bindResource(sessionFactory,
                        new SessionHolder(hibernateSession));
                //在只讀模式下(FlushMode.NEVER/MANUAL)寫操作不被允許
                hibernateSession.setFlushMode(FlushMode.AUTO);
            }

然后在添加兩個(gè)私有函數(shù)

 private Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
           Session session = sessionFactory.openSession();
           FlushMode flushMode = FlushMode.NEVER;
           if (flushMode != null) {
               session.setFlushMode(flushMode);
           }
           return session;
       }
       private SessionFactory lookupSessionFactory(HttpServletRequest request) {
           //“sessionFactory”是你spring配置文件(通常是application.xml)中的SessionFactory。
           //如:org.springframework.orm.hibernate4.annotation.AnnotationSessionFactoryBean
           return (SessionFactory)this.applicationContext.getBean("sessionFactory");
       }

說(shuō)明:通過(guò)lookupSessionFactory()方法獲取這個(gè)測(cè)試環(huán)境中的org.hibernate.SessionFactory實(shí)體對(duì)象,其中
applicationContext是org.springframework.context.ApplicationContext的實(shí)現(xiàn)org.springframework.context.support.GenericApplicationContext
我們可以通過(guò)它的getBean方法來(lái)獲取你配置文件中配置的SessionFactory。使用注解是org.springframework.orm.hibernate4.annotation.AnnotationSessionFactoryBean
不需要使用注解的時(shí)候可以使用org.springframework.orm.hibernate.LocalSessionFactoryBean來(lái)實(shí)現(xiàn)。

該單元測(cè)試需要加載spring配置文件信息,默認(rèn)加載路徑是你項(xiàng)目的src目錄下,文件名默認(rèn)為applicationContext.xml,如果路徑不對(duì)或者
文件名不同,則需要重寫getContextLocations()方法,如

   protected String getContextLocations() {
           return "classpath*:applicationContext.xml";
       }

關(guān)于實(shí)現(xiàn)web session的問(wèn)題,很簡(jiǎn)單,該類提供了一個(gè)MockHttpServletRequest成員變量,我們只要mock一個(gè)session出來(lái),然后加入到這個(gè)request中,就可以實(shí)現(xiàn)session的模擬了。示例代碼如下:

  HttpSession  session = new MockHttpSession();
    String sessionId = UUID.randomUUID().toString();
    session.setAttribute(ConstParameter.USER_SESSION, sessionId);
    //user是一個(gè)用戶信息的類,你可以根據(jù)你的需要自己定義
    UserInfor user = new UserInfo();
    user.setUserId(1);
    user.setName("xxx");
    session.setAttribute(ConstParameter.USER_INFO, user);
    request.setSession(session);

關(guān)于action的單元測(cè)試我們就說(shuō)完了。
二、Service的單元測(cè)試
接下來(lái)我們?cè)谡f(shuō)說(shuō)關(guān)于service的測(cè)試,
同樣,我們先從依賴說(shuō)起,需要添加的依賴:

  <powermock.version>1.7.1</powermock.version>

     <!--********************powermock使用*********************-->
          <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
          </dependency>
          <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
          </dependency>

此處我們采用的是powermock+junit 進(jìn)行mock測(cè)試。為啥使用powermock呢,毋庸置疑,由于他的功能比較強(qiáng)大
1、首先,我們我們看看待測(cè)試的代碼:

  @Override
     public List<MyInvoice> getMyInvoice(String buyerId) {
         if (StringUtils.isBlank(buyerId)) {
             return null;
         }
         List<MyInvoice> myInvoices = new ArrayList<MyInvoice>();
         String url = baseDataUrl + UrlConfig.GET_MYINVOICE_URL + "?t=" + VerifyBaseUtil.getT()
                 + "&token=" + VerifyBaseUtil.getToken() + "&buyerId=" + buyerId;
         System.out.println("MyInvoiceServiceImpl getMyInvoice接口請(qǐng)求參數(shù)為:" + url);
         try {
             String responseInfo = HttpUtil.getHttp(url);
             System.out.println("MyInvoiceServiceImpl getMyInvoice接口返回結(jié)果為:" + responseInfo);
             Map<String, Object> result = JSON.parseObject(responseInfo, Map.class);
             if (DistrictReturnNum.SUCCESS.getValue().equals(result.get("code"))) {
                 myInvoices = JSON.parseArray(JSON.toJSONString(result.get("result")), MyInvoice.class);
                 return myInvoices;
             }
         } catch (Exception e) {
             System.out.println("MyInvoiceServiceImpl getMyInvoice 程序出錯(cuò),查詢發(fā)票失敗"+e.getMessage());
             return null;
         }
         return null;
     }

getMyInvoice方法是一個(gè)調(diào)用外部接口的方法,通過(guò)http協(xié)議進(jìn)行通信。這兒有兩個(gè)問(wèn)題
1.HttpUtil.getHttp(url) 是一個(gè)靜態(tài)方法,我們?nèi)绾蝝ock?
2.我們?nèi)绾蝝ock這個(gè)方法所在的實(shí)現(xiàn)類?因?yàn)樵搶?shí)現(xiàn)類是通過(guò)spring ioc 容器生成并注入的。

要回答這兩個(gè)問(wèn)題,我們首先需要看看,我們的測(cè)試代碼:

 @RunWith(PowerMockRunner.class)
  @PrepareForTest(HttpUtil.class)
  public class MyInvoiceServiceImplTest {
      @InjectMocks
      private MyInvoiceService myInvoiceService = new MyInvoiceServiceImpl();
      @Before
      public void setUp(){
          PowerMockito.mockStatic(HttpUtil.class);
      }
      @Test
      public void testGetMyInvoice() throws Exception {
          String result_http="{\"result\":[{\"addDate\":1509010776000,\"buyerId\":" +
                  "\"9E59A2D27B7748848FB65041B854240E\",\"headName\":\"項(xiàng)偉測(cè)試\"," +
                  "\"headType\":\"0\",\"invoiceId\":\"9747A51B57FF4EA781F1CFDF73A0D9DF\"," +
                  "\"invoiceType\":\"0\",\"isDefault\":0},{\"addDate\":1509092635000,\"" +
                  "buyerId\":\"9E59A2D27B7748848FB65041B854240E\",\"editDate\":1509094177000,\"headName\":\"項(xiàng)偉測(cè)試二\",\"headType\":\"0\",\"invoiceId\":\"720CF6C50E594283B01C79D03D6D52B2\"" +
                  ",\"invoiceType\":\"0\",\"isDefault\":1}],\"msg\":\"成功\",\"code\":104}";
  //      1、  buyerId為空
          String buyerId = null;
          Assert.assertEquals(null, myInvoiceService.getMyInvoice(buyerId));
  //        2、buyerId不為空
          buyerId = "FF8080810F5E601526";
          PowerMockito.when(HttpUtil.getHttp(anyString())).thenReturn(result_http);
          List<MyInvoice> result = myInvoiceService.getMyInvoice(buyerId);
          Assert.assertEquals(2,result.size());
      }
    }

第一個(gè)問(wèn)題:我們通過(guò)PowerMockito.mockStatic(HttpUtil.class); 一個(gè)靜態(tài)方法的實(shí)現(xiàn)類。然后就可以調(diào)用該靜態(tài)方法。
第二個(gè)問(wèn)題:@InjectMocks注解來(lái)mock我們需要測(cè)試的業(yè)務(wù)類。
至此,我們就可以通過(guò)powermock對(duì)service層的方法進(jìn)行單元測(cè)試了。

運(yùn)行環(huán)境

  • 環(huán)境描述:Struts2+Spring4.2.4+hibernate4
    JAVA 1.7
  • 額外依賴第三方j(luò)ar包,請(qǐng)參考pom.xml




  • 作者:碼農(nóng)飛哥

    微信公眾號(hào):碼農(nóng)飛哥