单元测试---自动化测试查询结果集

来源:互联网 发布:自动阅读的软件 编辑:程序博客网 时间:2024/04/29 04:32

实际上我把自动化单元测试分为了两种

  • 针对增删改操作的单元测试
  • 针对查询的单元测试

其中“针对增删改操作的单元测试”,可以用dbunit和springtestdbunit来编写单元测试,而“针对查询的单元测试”,我孤陋寡闻没有找到什么现成的工具去解决(哪位朋友知道有这样的工具可以指点一下,谢啦)。下面会一步一步的讲述我自行开发的工具包,解决“针对查询的单元测试”问题。

首先还是从dbunit和springtestdbunit说起

dbunit流程大概是这样的:

  1. 单元测试前重置数据库
  2. 单元测试后比对数据库结果与预期结果是否一致

springtestdbunit使用了注解配置,一个springtestdbunit的单元测试大致是这样的

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = {"classpath:com/sztb/dp/test/testContext.xml"})@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,        DirtiesContextTestExecutionListener.class,        TransactionalTestExecutionListener.class,        DbUnitTestExecutionListener.class})@DbUnitConfiguration(dataSetLoader= XmlDataSetLoader.class)public class UserTest {    @Test       @DatabaseSetup("classpath:com/look/test/xml/InputUserDelete.xml")    @ExpectedDatabase(assertionMode = DatabaseAssertionMode.NON_STRICT,                  value = "classpath:com/look/test/xml/ResultUserDelete.xml")    public void testUserDelete() throws Exception {         //dbunit test code        UserService userService= new UserService();        userService.deleteUser(1L);    }}

该代码对删除用户操作进行了单元测试,@DatabaseSetup设置了数据库的数据集,@ExpectedDatabase设置了期望的数据集,单元测试代码执行完毕后,程序会检查数据库最终结果,与期望数据集的一致性,完成断言。

但是如果要测试查询操作,数据库在单元测试前后是无变化的,dbunit也就不适用了,我们需要对查询到的结果集的属性进行断言,秉承dbunit的思路,我们可以设想用如下的方式进行基于查询的单元测试的编写,也就是说,我们开发这个工具包,要达到的效果就是下面这样,通过配置我们自己的监听器和自定义的注解,对查询到的结果进行与xml文件的比对

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = {"classpath:com/sztb/dp/test/testContext.xml"})@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,        DirtiesContextTestExecutionListener.class,        TransactionalTestExecutionListener.class,        DbUnitTestExecutionListener.class,        ProtoResultTestExecutionListener.class})@DbUnitConfiguration(dataSetLoader= XmlDataSetLoader.class)public class UserTest {    @Test    @DatabaseSetup("classpath:com/look/test/xml/InputGetUsers.xml")      @ExpectedProtoResult("classpath:com/look/test/protoResultXml/ResultGetUsers.xml")    public void testGetUsers() throws Exception {        //dbunit test code        UserService userService= new UserService();        User.GetUserResponse resp = userService.getUsers();//      将结果集设置到protoResult容器        ResultContainer.getInstance().setResultSet(resp);    }}

有两点需要注意的地方

  • @ExpectedProtoResult注解,设置预期结果集
  • @TestExecutionListeners的设置中,另外添加了一个监听器ProtoResultTestExecutionListener.class,这是自行编写的监听器,用来在单元测试执行完毕后,处理@ExpectedProtoResult注解,监听器ProtoResultTestExecutionListener监听我们的单元测试,运行我们的自定义代码,自定义代码中需要解析@ExpectedProtoResult注解中配置的预期结果集,并与ResultContainer中我们的查询结果进行一致性断言。

下面逐一介绍我都编写了哪些类

1.首先,我们可能需要一个这样的工具类,能将得到的结果对象转为符合一定规则的xml格式,并且有良好格式化方便阅读和修改。(本文中的bean对象为protoc生成的javabean)

public class XMLWriter {    public static void writeXML(MessageLite protoObject , String dest) throws Exception    {        String xml = Proto2XML.toXml(protoObject,true);        BufferedWriter bufferedWriter = null;        FileWriter fileWriter = null;        try        {            fileWriter = new FileWriter(dest);            bufferedWriter = new BufferedWriter(fileWriter);            bufferedWriter.write(xml);            bufferedWriter.flush();        } catch (IOException e)        {            e.printStackTrace();        } finally        {            try            {                fileWriter.close();                bufferedWriter.close();            } catch (IOException e)            {                e.printStackTrace();            }        }    }}

2.工具类Proto2XML .java

由于这个类涉及到proto javabean到xml的转换,有一些局限性,该处代码省略,思路就是将javaBean转为json格式,再转为带换行和缩进的的xml格式。

public class Proto2XML {    public static org.json.JSONObject proto2JSONObject(MessageLite protoObject) throws org.json.JSONException {        org.json.JSONObject jsonObject = new org.json.JSONObject();        /******* proto javabean cast to json ********/        return jsonObject;    }    public static String toXml(Object protoObject) throws JSONException {        JSONObject jsonObject = proto2JSONObject(protoObject);        return XML.toString(jsonObject,Path.XMLROOT);    }    public static String toXml(Object protoObject,boolean format) throws JSONException {        String xml = toXml(protoObject);        if(format)        {            xml = formatXML(xml);        }        return xml;    }

生成的xml文件形如

<?xml version="1.0" encoding="utf8"?><dataset>    <errCode type="string">ERR_OK</errCode>    <errMsg type="string">ERR_OK</errMsg>    <users class="array">        <e class="object">            <id type="string">1</id>            <username type="string">jack</username>            <age type="string">20</age>        </e>        <e class="object">            <id type="string">2</id>            <username type="string">jim</username>            <age type="string">22</age>        </e>    </users></dataset>

说一点题外话,上面代码中的MessageLite是protobuf中的一种消息类型,通过对proto文件optimize_for选项的设置,可以选择消息类型为MessageLite或Message,使用MessageLite的效率高一点,但是牺牲了Message的反射功能,比如说,Message可以直接使用getAllfields这样的方法,也可以取到Message的name,而MessageLite就不行,因为MessageLite不提供这样的方法,所以基于Message的protobuf进行xml转换的时候,有现成的框架可以完成Message到xml的转换(因为框架可以直接调用Message原生getAllFields、getName等方法),例如protobuf-java-format。而我们项目中使用的是MessageLite,它转xml的部分,只能由我们自己开发,并没有现成的框架。

我选择了proto javabean先转json再转xml的方式,proto javabean转json用到的json包,我选择了org.json 因为我后续会用到JsonAssert框架来进行json一致性的断言,而JsonAssert使用的就是org.json。json转xml用到的json包,我选择了net.sf.json包,因为org.json包下的xml转换有两个缺点,第一,转换为xml后,JSONArray结构不清晰,如果JSONArray中只有一条JSON,xml转换成JSON的时候,这个JSONAraay也会转换为JSONObject。
第二,对数值的处理有些问题,比如xml中有一个属性值为4.0,转为json的时候4.0会变为4,自动转为了整数,可以说是一种失真,不利于单元测试。
net.sf.json的xml转换功能就不存在这两种问题。

详细了解protobuf及其选项(Options)可参考下面链接
http://www.cnblogs.com/dkblog/archive/2012/03/27/2419010.html

3.自定义监听器ProtoResultTestExecutionListener.java

public class ProtoResultTestExecutionListener extends org.springframework.test.context.support.AbstractTestExecutionListener {    @Override    public void afterTestMethod(TestContext testContext) throws org.json.JSONException, IOException {        Method method = testContext.getTestMethod();        if(method.isAnnotationPresent(ExpectedProtoResult.class))        {            ExpectedProtoResult protoResult = method.getAnnotation(ExpectedProtoResult.class);            String dataSetLocation = protoResult.value();            assertNotNull(dataSetLocation);            if (StringUtils.hasLength(dataSetLocation)) {                String xml  = XMLLoader.load(testContext.getClass(),dataSetLocation);                JSONUtil.compareXmlAndProto(xml, ResultContainer.getInstance().getResultSet());            }        }    }}

4.JSONUtil.java

public class JSONUtil {    public static void compareXmlAndProto(String xml , MessageLite protoObject) throws JSONException {        org.json.JSONObject json1 = string2JSON(xml2JSONString(xml));        org.json.JSONObject json2 = Proto2XML.proto2JSONObject(protoObject);        System.out.println("Expected Result : "+json1.toString());        System.out.println("True Result     : " + json2.toString());        JSONAssert.assertEquals(json1, json2, true);    }    public static String xml2JSONString(String xml){        XMLSerializer xmlSerializer =  new XMLSerializer();        xmlSerializer.setRootName(Path.XMLROOT);        return xmlSerializer.read(xml).toString();    }    public static String jsonString2XML(String json){        net.sf.json.JSONObject netJson = net.sf.json.JSONObject.fromObject(json);        XMLSerializer xmlSerializer =  new XMLSerializer();        xmlSerializer.setRootName(Path.XMLROOT);        return  xmlSerializer.write(netJson,"utf8");    }    public static org.json.JSONObject string2JSON(String json) throws JSONException, JSONException {        return new org.json.JSONObject(json);    }}

5.自定义注解@ExpectedProtoResult

@java.lang.annotation.Documented@java.lang.annotation.Inherited@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})public @interface ExpectedProtoResult {    java.lang.String value();}

6.protoXML的解析器

这个解析器用到了spring的core包,解析路径时,与spring使用相同的方式

public class XMLLoader{    protected static ResourceLoader getResourceLoader(Class<?> testClass) {        return new ClassRelativeResourceLoader(testClass);    }    protected static String[] getResourceLocations(String location) {        return new String[] { location };    }    public static String load(Class<?> testClass , String location) throws IOException {        StringBuffer sb = new StringBuffer();        ResourceLoader resourceLoader = getResourceLoader(testClass);        String[] resourceLocations = getResourceLocations(location);        for (String resourceLocation : resourceLocations) {            Resource resource = resourceLoader.getResource(resourceLocation);            if (resource.exists()) {                    sb.append(getProtoXML(resource.getInputStream()));            }else            {                throw new IOException(resource.getURI().getPath());            }        }        return sb.toString();    }    private static String getProtoXML(InputStream is) throws IOException {        return inputStream2String(is);    }    private static String inputStream2String(InputStream is) throws IOException    {        int len = 0;        StringBuffer str=new StringBuffer("");        try {            InputStreamReader isr = new InputStreamReader(is);            BufferedReader in = new BufferedReader(isr);            String line = null;            while( (line=in.readLine())!=null )            {                str.append(line);                len++;            }            in.close();            is.close();        } catch (IOException e) {            e.printStackTrace();        }        return str.toString();    }}

7.装载返回结果对象的ResultContainer

public class ResultContainer {    private static ResultContainer resultContainer = new ResultContainer();    private ResultContainer(){}    public static ResultContainer getInstance()    {        return resultContainer;    }    private MessageLite resultSet = null;    public MessageLite getResultSet() {        return resultSet;    }    public void setResultSet(MessageLite resultSet) {        this.resultSet = resultSet;    }}

可以看到,使用方式与springtestdbunit基本一致,只是多了ResultContainer.getInstance().setResultSet(resp);这样一个步骤,
那是因为springtestdbunit在@ExpectedDatabase的时候,比对的是数据库的结果,使用@ContextConfiguration中配置的数据库就可以,
而@ExpectedProtoResult比对的不是执行完单元测试后数据库的情况,而是执行完单元测试后,比对我们得到的结果对象,所以我们需要自行设置一下比对的对象resp。

0 0
原创粉丝点击