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

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

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

其中“针对增删改操作的单元测试”,可以用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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值