zoukankan      html  css  js  c++  java
  • Java系列--第五篇 基于Maven的SSME之Token及Parameterized单元测试

    本来在第四篇要说完的,但是写着写着,我觉得内容有点多起来了,所以就另开这篇,在这里专门讲述Token的定义,JSP自定义标签以及如何用Parameterized的来做单元测试。

    1,新建包com.vanceinfo.javaserial.handlerinterceptors,新加类TokenHandler,这个类就是Token的Helper类了,包含三个方法:

    generateGUID当进入页面加载时,产生一个GUID,分别存入Session和Constant里,说明一下,Constant是用于页面的hidden值保存用的。。。。,顺便打个预防针的是sesseion里面存的是map对象,使用的key叫SPRINGMVC.TOKEN,map里面的一条对象以"springMVC_token.GUID:GUID"形式保存。而客户端的hidden框的name使用的是小写的springMVC_token

    getInputToken获取客户端hidden里面的guid值。

    validToken这个方法用于验证客户端Hidden里的guid里值,与服务端Session里面对应的值是否一致,完全相同而返回true,否则返回false, 并且先会remove掉session里面的对应的这条token值。

    源码如下:

    package com.vanceinfo.javaserial.handlerinterceptors;
    
    import java.math.BigInteger;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.apache.log4j.Logger;
    import org.springframework.ui.ModelMap;
    
    import com.vanceinfo.javaserial.constants.Constant;
    
    public class TokenHandler {
        private static Logger LOGGER = Logger.getLogger(TokenHandler.class);
    
        static Map<String, String> springmvc_token = new HashMap<String, String>();
    
        /**
         * generate the unique token, and store into both server, client side.
         * 
         * @param session
         * @return
         */
        @SuppressWarnings("unchecked")
        public synchronized static String generateGUID(HttpSession session, ModelMap map) {
            String token = "";
            try {
                Object obj = session.getAttribute("SPRINGMVC.TOKEN");
                if (obj != null) {
                    springmvc_token = (Map<String, String>) session.getAttribute("SPRINGMVC.TOKEN");
                }
    
                token = new BigInteger(165, new Random()).toString(36).toUpperCase();
                springmvc_token.put(Constant.DEFAULT_TOKEN_NAME + "." + token, token);
                session.setAttribute("SPRINGMVC.TOKEN", springmvc_token);
                Constant.TOKEN_VALUE = token;
    
            } catch (IllegalStateException e) {
                LOGGER.error("generateGUID() mothod find bug,by token session...");
            }
            return token;
        }
    
        /**
         * validate the form token value and session token value.
         * 
         * @param request
         * @return true if both token value are the same,otherwise false
         */
        @SuppressWarnings("unchecked")
        public static boolean validToken(HttpServletRequest request) {
            String inputToken = getInputToken(request);
    
            if (inputToken == null) {
                LOGGER.warn("token is not valid!inputToken is NULL");
                return false;
            }
    
            HttpSession session = request.getSession();
            Map<String, String> tokenMap = (Map<String, String>) session.getAttribute("SPRINGMVC.TOKEN");
            if (tokenMap == null || tokenMap.size() < 1) {
                LOGGER.warn("token is not valid!sessionToken is NULL");
                return false;
            }
            String sessionToken = tokenMap.get(Constant.DEFAULT_TOKEN_NAME + "." + inputToken);
            if (!inputToken.equals(sessionToken)) {
                LOGGER.warn("token is not valid!inputToken='" + inputToken + "',sessionToken = '" + sessionToken + "'");
                return false;
            }
            tokenMap.remove(Constant.DEFAULT_TOKEN_NAME + "." + inputToken);
            session.setAttribute("SPRINGMVC.TOKEN", tokenMap);
    
            return true;
        }
    
        /**
         * Get the token value from the form. assume it store in the hidden field
         * 
         * @param request
         * @return
         */
        @SuppressWarnings("unchecked")
        public static String getInputToken(HttpServletRequest request) {
            Map<String, String[]> params = request.getParameterMap();
    
            if (!params.containsKey(Constant.DEFAULT_TOKEN_NAME)) {
                LOGGER.warn("Could not find token name in params.");
                return null;
            }
    
            String[] tokens = (String[]) (String[]) params.get(Constant.DEFAULT_TOKEN_NAME);
    
            if ((tokens == null) || (tokens.length < 1)) {
                LOGGER.warn("Got a null or empty token name.");
                return null;
            }
    
            return tokens[0];
        }
    }
    TokenHandler.java

    通过上面的描述,聪明你可以很自然的猜到Constant.java里面会有两个常量值

        public static String DEFAULT_TOKEN_NAME = "springMVC_token";
        public static String TOKEN_VALUE;    

    Interceptor里面其实编写代码起来要容易的多,实现HandlerInterceptor接口,其实主要是在preHandler和postHandler就好了。

    /* (non-Javadoc)
         * @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
         */
        public void postHandle(HttpServletRequest request, HttpServletResponse response,
                Object handler, ModelAndView modelAndView) throws Exception {
            if ("get".equalsIgnoreCase(request.getMethod())) {
                TokenHandler.generateGUID(request.getSession(), modelAndView.getModelMap());
            } 
            
        }
    
        /* (non-Javadoc)
         * @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
         */
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                Object handler) throws Exception {
            if ("get".equalsIgnoreCase(request.getMethod())) {
                return true;
            } else {
                if (!TokenHandler.validToken(request)) {
                    response.sendRedirect("index.do");
                    return false;
                }
                return true;
    
            }
        }
    preHandler_PostHandler

    以上代码是接合我的一些情况,如果二次请求,我是跳转至index.do这个方法的。

    2, 将刚写的这个interceptor注册至spring-mvc.xml里面。

    <mvc:interceptors>
            <!-- use Token to void multi post when user press F5 or Ctrol+F5 -->
            <mvc:interceptor>
                <mvc:mapping path="/**/*.do" />
                <bean
                    class="com.vanceinfo.javaserial.handlerinterceptors.TokenHandlerInterceptor" />
            </mvc:interceptor>
    </mvc:interceptors>
    mvc:interceptors

    3, 接下来就是要写tag了。4步实现自定义tag:

    a,先定义一个BaseTag,让其继承于TagSupport,这是最简单的实现方式

    package com.vanceinfo.javaserial.tags;
    
    import javax.servlet.jsp.tagext.TagSupport;
    
    public class BaseTag extends TagSupport {
    
        private static final long serialVersionUID = -4886769810825854364L;
        protected String name;
        protected String type;
        protected String id;
        protected String onclick;
        protected String onfocus;
        protected String onblur;
        protected String onchange;
        protected String cssStyle;
        protected String cssClass;
        protected String size;
        
        public void generateAttribute(StringBuilder sb) {
            if (id != null) {
                sb.append(" id='").append(id).append("'");
            }
            if (onclick != null) {
                sb.append(" onclick='").append(onclick).append("'");
            }
            if (onfocus != null) {
                sb.append(" onfocus='").append(onfocus).append("'");
            }
            if (onblur != null) {
                sb.append(" onblur='").append(onblur).append("'");
            }
            if (onchange != null) {
                sb.append(" onchange='").append(onchange).append("'");
            }
            if (cssStyle != null) {
                sb.append(" style='").append(cssStyle).append("'");
            }
            if (cssClass != null) {
                sb.append(" class='").append(cssClass).append("'");
            }
            if (size != null) {
                sb.append(" size='").append(size).append("'");
            }
        }
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @param name the name to set
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * @return the type
         */
        public String getType() {
            return type;
        }
    
        /**
         * @param type the type to set
         */
        public void setType(String type) {
            this.type = type;
        }
    
        /**
         * @return the id
         */
        public String getId() {
            return id;
        }
    
        /**
         * @param id the id to set
         */
        public void setId(String id) {
            this.id = id;
        }
    
        /**
         * @return the onclick
         */
        public String getOnclick() {
            return onclick;
        }
    
        /**
         * @param onclick the onclick to set
         */
        public void setOnclick(String onclick) {
            this.onclick = onclick;
        }
    
        /**
         * @return the onfocus
         */
        public String getOnfocus() {
            return onfocus;
        }
    
        /**
         * @param onfocus the onfocus to set
         */
        public void setOnfocus(String onfocus) {
            this.onfocus = onfocus;
        }
    
        /**
         * @return the onblur
         */
        public String getOnblur() {
            return onblur;
        }
    
        /**
         * @param onblur the onblur to set
         */
        public void setOnblur(String onblur) {
            this.onblur = onblur;
        }
    
        /**
         * @return the onchange
         */
        public String getOnchange() {
            return onchange;
        }
    
        /**
         * @param onchange the onchange to set
         */
        public void setOnchange(String onchange) {
            this.onchange = onchange;
        }
    
        /**
         * @return the cssStyle
         */
        public String getCssStyle() {
            return cssStyle;
        }
    
        /**
         * @param cssStyle the cssStyle to set
         */
        public void setCssStyle(String cssStyle) {
            this.cssStyle = cssStyle;
        }
    
        /**
         * @return the cssClass
         */
        public String getCssClass() {
            return cssClass;
        }
    
        /**
         * @param cssClass the cssClass to set
         */
        public void setCssClass(String cssClass) {
            this.cssClass = cssClass;
        }
    
        /**
         * @return the size
         */
        public String getSize() {
            return size;
        }
    
        /**
         * @param size the size to set
         */
        public void setSize(String size) {
            this.size = size;
        }
    }
    BaseTag

    自定义的TokenTag 继承于BaseTag

    package com.vanceinfo.javaserial.tags;
    
    import java.io.IOException;
    
    import javax.servlet.jsp.JspException;
    
    import com.vanceinfo.javaserial.constants.Constant;
    
    public class TokenTag extends BaseTag {
        private static final long serialVersionUID = 1495609370076247263L;
    
        /* (non-Javadoc)
         * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
         */
        @Override
        public int doStartTag() throws JspException {
            StringBuilder sb = new StringBuilder();
            
            sb.append("<input type='text' name='").append(
                    Constant.DEFAULT_TOKEN_NAME).append("'").append(" value='").append(Constant.TOKEN_VALUE).append("'");
            sb.append(" /> ");
            try {
                pageContext.getOut().print(sb.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
            return EVAL_BODY_INCLUDE;
        }
    
    }
    TokenTag

    b,接着就去WEB-INF下面创建标签库描述文件,注意这个文件只能放在这个文件夹下,当然为了区分,你可以在其下自建一个文件夹,至于放其它地方会出错的原因还不明。

    <?xml version="1.0" encoding="UTF-8"?>
    <taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd">
    <tlib-version>1.0</tlib-version>
    <jsp-version>2.0</jsp-version>
    <short-name>token</short-name>
    <uri>/mytaglib</uri>
    <tag>
        <name>token</name>
        <tag-class>com.vanceinfo.javaserial.tags.TokenTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>user</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
     </tag>
    </taglib>
    /tlds/Token.tld

    至于c、d两步,我在第四篇中已经讲述,故不在重复,不清楚的可以返回上篇查阅一下

    以上就是关于自定义类Struts2的Token的完整过程,Struts2是经历了很多人的检验的,我们的自造的,为了表示出我们的信心和诚意,自然对我们的Interceptor进行单元测试喽。当然,从浏览器现象来看个现象也是好的,所谓有图有真相有说服力

    http://images.cnblogs.com/cnblogs_com/SLKnate/515382/o_200-302.png

    4,关于单元测试,我们利用Parameterized参数化集中测试,没弄这种单元测试之前,我也很悚这种模式,不过一来二去之后,对其还是持拥抱态度的。其实,单元测试针对的是单元,看你对单元的单位是如何定义的,一般来讲一个方法就是一个单元来测最合适。因为我是这样子认同的,所以,对于每一个方法,我们需要创建一个JUnit4的Test Case.并且勾选setUpBeforeClass,tearDownAfterClass,setUp,tearDown,这4个方法一般用于记一些描述性步骤等内容,下面是TokenHandlerInterceptorTest_postHandleTest里的一个示例。

        /**
         * @throws java.lang.Exception
         */
        @BeforeClass
        public static void setUpBeforeClass() throws Exception {
            LOGGER.debug("Starting test class : " + TokenHandlerInterceptorTest_postHandleTest.class.getName());
        }
    
        /**
         * @throws java.lang.Exception
         */
        @AfterClass
        public static void tearDownAfterClass() throws Exception {
            LOGGER.debug("Ending test class : " + TokenHandlerInterceptorTest_postHandleTest.class.getName());
        }
    
        /**
         * @throws java.lang.Exception
         */
        @Before
        public void setUp() throws Exception {
            LOGGER.debug("Starting test: " + testName);
        }
    
        /**
         * @throws java.lang.Exception
         */
        @After
        public void tearDown() throws Exception {
            LOGGER.debug("Ending test: " + testName);
        }
    JUnit Test Case 四大金刚前后执行方法

    还有一个约定成俗的定义一个变量,用于对每次执行一时输出你的test case名字出来。

    private String testName;

    除了这个约定以外,如果你的测试方法的入参也请列在private列表里面,作为变量,也就是参数。最后,测试方法的返回值也请以参数的形式定义。这一次,我举TokenHandlerInterceptor_preHandleTest作为例子,因为他是有返回值的。

        private String testName;
        private HttpServletRequest httpRequest;
        private HttpServletResponse httpResponse;
        private boolean returnBoolean;
    入参和出参列表

    然后,你还要定义一个带所有参数的构造函数出来。例如:

        public TokenHandlerInterceptor_preHandleTest(String testName, HttpServletRequest httpRequest,
                HttpServletResponse httpResponse, Boolean returnBoolean) {
            this.testName = testName;
            this.httpRequest = httpRequest;
            this.httpResponse = httpResponse;
            this.returnBoolean = returnBoolean;
        }
    带全部入出参数的构造函数

    最后,准备测试数据,写在testData()函数里面,一定要以这个命名,返回Collection<Object[]>,并且打上@org.junit.runners.Parameterized.Parameters标签,

    准备完这些之后中,在class前面也一定不要忘了打上@RunWith(Parameterized.class)标签。

    一个完整的TokenHandlerInterceptor_preHandleTest如下:

    package com.vanceinfo.javaserial.handlerinterceptors;
    
    import static org.junit.Assert.*;
    
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.log4j.Logger;
    import org.junit.After;
    import org.junit.AfterClass;
    import org.junit.Before;
    import org.junit.BeforeClass;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.junit.runners.Parameterized.Parameters;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.mock.web.MockHttpServletResponse;
    import org.springframework.mock.web.MockHttpSession;
    
    @RunWith(Parameterized.class)
    public class TokenHandlerInterceptor_preHandleTest {
    
        private static final Logger LOGGER = Logger.getLogger(TokenHandlerInterceptor_preHandleTest.class);
    
        private String testName;
        private HttpServletRequest httpRequest;
        private HttpServletResponse httpResponse;
        private boolean returnBoolean;
    
        public TokenHandlerInterceptor_preHandleTest(String testName, HttpServletRequest httpRequest,
                HttpServletResponse httpResponse, Boolean returnBoolean) {
            this.testName = testName;
            this.httpRequest = httpRequest;
            this.httpResponse = httpResponse;
            this.returnBoolean = returnBoolean;
        }
    
        @Parameters
        public static Collection<Object[]> testData() throws Exception {
            // *******************
            // test case 1
            // *******************
            String testNameTC1 = "Get Method";
            MockHttpServletRequest httpRequest1 = new MockHttpServletRequest("get", "http://testRequestUrl");
            httpRequest1.addParameter("htid", "1001");
    
            MockHttpServletResponse httpResponse1 = new MockHttpServletResponse();
    
            // *******************
            // test case 2
            // *******************
            String testNameTC2 = "Post Method unvalid: no param(client) token";
            MockHttpServletRequest httpRequest2 = new MockHttpServletRequest("post", "http://testRequestUrl");
            httpRequest2.addParameter("htid", "1001");
    
            MockHttpServletResponse httpResponse2 = new MockHttpServletResponse();
    
            // *******************
            // test case 3
            // *******************
            String testNameTC3 = "Post Method unvalid: has param(client) token but is null";
            MockHttpServletRequest httpRequest3 = new MockHttpServletRequest("post", "http://testRequestUrl");
            httpRequest3.addParameter("htid", "1001");
            httpRequest3.addParameter("springMVC_token", new String[] {});
    
            MockHttpServletResponse httpResponse3 = new MockHttpServletResponse();
    
            // *******************
            // test case 4
            // *******************
            String testNameTC4 = "Post Method unvalid: has param(client) token but no session token";
            MockHttpServletRequest httpRequest4 = new MockHttpServletRequest("post", "http://testRequestUrl");
            httpRequest4.addParameter("htid", "1001");
            httpRequest4.addParameter("springMVC_token", new String[] { "abcdef" });
    
            MockHttpServletResponse httpResponse4 = new MockHttpServletResponse();
    
            // *******************
            // test case 5
            // *******************
            String testNameTC5 = "Post Method unvalid: has param(client) token and session token,but not the same value";
            MockHttpServletRequest httpRequest5 = new MockHttpServletRequest("post", "http://testRequestUrl");
            httpRequest5.addParameter("htid", "1001");
            httpRequest5.addParameter("springMVC_token", new String[] { "abcdef" });
            MockHttpSession session5 = new MockHttpSession();
            Map<String, String> tokenMap5 = new HashMap<String, String>();
            tokenMap5.put("springMVC_token.abcdef", "abcdefg");
            session5.putValue("SPRINGMVC.TOKEN", tokenMap5);
            httpRequest5.setSession(session5);
    
            MockHttpServletResponse httpResponse5 = new MockHttpServletResponse();
    
            // *******************
            // test case 6
            // *******************
            String testNameTC6 = "Post Method valid";
            MockHttpServletRequest httpRequest6 = new MockHttpServletRequest("post", "http://testRequestUrl");
            httpRequest6.addParameter("htid", "1001");
            httpRequest6.addParameter("springMVC_token", new String[] { "abcdef" });
            MockHttpSession session6 = new MockHttpSession();
            Map<String, String> tokenMap6 = new HashMap<String, String>();
            tokenMap6.put("springMVC_token.abcdef", "abcdef");
            session6.putValue("SPRINGMVC.TOKEN", tokenMap6);
            httpRequest6.setSession(session6);
    
            MockHttpServletResponse httpResponse6 = new MockHttpServletResponse();
    
            return Arrays
                    .asList(new Object[][] { { testNameTC1, httpRequest1, httpResponse1, true },
                            { testNameTC2, httpRequest2, httpResponse2, false },
                            { testNameTC3, httpRequest3, httpResponse3, false },
                            { testNameTC4, httpRequest4, httpResponse4, false },
                            { testNameTC5, httpRequest5, httpResponse5, false },
                            { testNameTC6, httpRequest6, httpResponse6, true } });
        }
    
        /**
         * @throws java.lang.Exception
         */
        @BeforeClass
        public static void setUpBeforeClass() throws Exception {
            LOGGER.debug("Starting test class : " + TokenHandlerInterceptor_preHandleTest.class.getName());
        }
    
        /**
         * @throws java.lang.Exception
         */
        @AfterClass
        public static void tearDownAfterClass() throws Exception {
            LOGGER.debug("Ending test class : " + TokenHandlerInterceptor_preHandleTest.class.getName());
        }
    
        /**
         * @throws java.lang.Exception
         */
        @Before
        public void setUp() throws Exception {
            LOGGER.debug("Starting test: " + testName);
        }
    
        /**
         * @throws java.lang.Exception
         */
        @After
        public void tearDown() throws Exception {
            LOGGER.debug("Ending test: " + testName);
        }
    
        /**
         * Test method for
         * {@link com.expedia.lux.drr.handlerinterceptors.TokenHandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)}
         * .
         * 
         * @throws Exception
         */
        @Test
        public void testPreHandle() {
            TokenHandlerInterceptor pre = new TokenHandlerInterceptor();
            try {
                boolean actual = pre.preHandle(httpRequest, httpResponse, new Object());
                assertEquals(this.returnBoolean, actual);
    
            } catch (Exception e) {
                fail("Should not throw exception!");
            }
        }
    
    }
    TokenHandlerInterceptor_preHandleTest

    注意我在上面设计了6个case. 大部分期望的是返回false,也就是负case的测试。而assert 的时候,只需写一次就可以了。

    这就是参数化单元测试的好处了。耗时就耗在准备数据上面。

    希望把自定义标签的制作说明白了。

  • 相关阅读:
    jQuery实现“回到顶部”按钮功能
    围绕DOM元素节点的增删改查
    jQuery ajax
    对JSON的理解
    Replacing Accented characters(Diacritic) .NET
    全球主要城市经纬度api
    无标题
    Use Razor for Email Template outside ASP.NET MVC
    .NET and php
    knockoutjs
  • 原文地址:https://www.cnblogs.com/SLKnate/p/Java_Serial_SSME_Token_ParameterizedUnitTest.html
Copyright © 2011-2022 走看看