2.7 使用CSV文件
CSV[1]的全称是Comma Separate Values,即以逗号为分隔符,每条记录占一行的一种文件格式。当然分隔符并不限制于逗号,因此其另一个名字叫做Character Separated Values。这种古老的文件格式早在20世纪60年代就已出现,被使用于IBM OS360上,远早于个人电脑时代的来临。时至今日,CSV文件依然顽强并且广泛地使用着,特别是在程序间交换数据的场合。例如,在某些ERP系统中,作为安装的一部分,在完成了二进制可执行文件的安装之后,需要导入一些基础数据,如工作流的定义、各种对象的定义与属性、变更流程定义等。这些很多时候是使用CSV格式的文件进行的交互。另外,用户列表、组织权限、产品列表等产品使用或者示例的数据等,也是如此。
在进行自动化测试时,测试用例中的数据非常依赖于AUT中的上下文基础数据,而这些基础数据又可以通过CSV文件导入到AUT之中。那么,考虑将这些CSV文件中遴选出部分必需的数据,导入到测试框架中,作为测试框架的基础数据存在并供下游用例使用。据此,则可简单实现所谓的单一数据源(Single Source Of Truth),即使后期CSV文件中的变化了,因为用例和被测应用使用同一套数据源,自动化测试也可以照常执行。这种设计既提高了数据通用性,又降低了维护成本。
2.7.1 CSV文件数据解析思路
从面向对象的角度,如果将一个CSV文件的记录结构类比成一个Java类,那么该CSV文件中的每一条记录就可以理解为同一个类的不同实例,它们有着相同的属性,当然属性值随着记录数据的不同而不同。因此,与通过xstream将XML文件转换成Java对象类似,通过将CSV文件导入并转换成Java对象,然后按照操纵对象的方式来处理CSV文件中的数据,从而避免了通过I/O,反复处理文件操作这种费时费力的方式。熟悉数据库开发的读者可能对这种方式很熟悉,这其实就是借助了数据库中常用的对象关系映射(ORM, Object/Relation Mapping)概念。感兴趣的读者可以查阅相关的资料进行学习。
提供了将CSV文件转换为Java对象的第三方工具包比较多,这里采用的是opencsv。这一工具包的作者是Glen Smith,目前的维护者是Sean Sullivan。整个项目还处在积极的维护当中。在此小节写成的这一周,其官方网站上的单周下载量超过了1000次。另外其采用的是Apache 2.0 许可模式,只要不对它进行修改,即使用于商业用途,也是免费的。当然这一工具除了进行CSV到Java对象的转换,其余的功能如CSV导入导出功能也是比较全的。需要进行更多的CSV操作的读者可以到其官网上详细了解其他功能。
2.7.2 实现泛型解析
Opencsv提供了非常方便的CSV文件解析方法。在此基础上加以简单的封装,就可以实现一个较为通用的CSV文件转换为Java对象的方法。核心代码如下:
public static< T extends BaseBean > List< T > parseCSV2Bean (String filename,Class< T > type){
List< T > list =null;
try {
CSVReader reader = new CSVReader(new FileReader(filename), ',', '\"');
//构造了一个CSV文件的解析器,待解析的CSV文件以逗号分隔记录中的字段,以双引号作为各字段的起止标志。
HeaderColumnNameMappingStrategy< T >mappingStrategy =
new HeaderColumnNameMappingStrategy< T >();
//HeaderColumnNameMappingStrategy实现了MappingStrategy接口,这种匹配策略下会将
//文件第一行的每个字段作为待转换的bean类的成员变量,也就是其属性
mappingStrategy.setType(type);
//此处通过泛型实现了待解析文件和与之对应的Java Bean类之间的动态匹配
CsvToBean< T > csv = new CsvToBean< T >();
list = csv.parse(mappingStrategy, reader);
//完成解析,并将解析的结果返回给一个泛型的list
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return list;
}
至此,通过一段简短的代码,就实现了通用的CSV转换为Java Bean list的功能。当然有一个小窍门就是,所有的Java Bean类都需要继承BaseBean。
2.7.3 对象类案例
下面是一个简单的示例。假设有一个employee.csv的员工信息表,与之对应的是一个EmployeeBean的类,其成员变量可以是id,name,position,salary等,如下面的代码所示:
public classEmployeeBean {
private intid;
private String name;
private String role;
private String salary;
//其余get/set方法省略
}
通过下面的这两行代码,就可以实现两者之间的解析和匹配的工作。
List< EmployeeBean > employees;
employees=CSVDigester.parseCSV2Bean("employee.csv",EmployeeBean.class)
解析得到的结果将返回给一个List employees的变量。据此,如本小节开头所介绍的那样,将繁琐的文件操作转换成了对一个list的访问,简化了操作,提高了速度,并增强了系统鲁棒性。
2.7.4 提供数据源的外部访问
Public classBeans {
private static List < EmployeeBean >employees;
public static voidsetEmployees(List < EmployeeBean > employees) {
Beans.employees = employees;
}
public static List< EmployeeBean >getEmployees() {
returnemployees;
}//提供该Employee数据源的外部访问接口
static {
setEmployees(CSVDigester.parseCSV2Bean("employees.csv",EmployeeBean.class));
//测试框架初始化时读入该数据
}
如果有其他的CSV文件需要解析,则在Beans的静态块中使用类似操作即可。通过以上的操作,已经将针对CSV文件的操作转换成了对List employeeBeans的操作。使用时,直接采用以下语句:
Beans.getEmployees();
既提供了静态方法,也方便了数据的使用。
通过有针对性地将一些通用的操作封装成方法,简化测试用例的自动化实现和代码的复用性,譬如查询某个employee是否存在等。具体的代码例子这里不再列举,读者可以自行实现。
2.7.5 CSV文件通过SQL方式查询结果
CSV文件还可以通过SQL查询的方式通过JDBC进行读取。以2.6.3中所介绍的读取TestLink数据库中的表platforms为例。如果将该表导出到一个CSV当中,则可以将前述的案例程序稍加改动,将driver以及Connection的取得方式更换成CSV文件方式即可,具体程序如下:
package com.dataset.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class CSVDriver {
private static String filepath="D:\\repo\\Playground";
private static final String URL="jdbc:relique:csv:" + filepath;
private static final String Driver="org.relique.jdbc.csv.CsvDriver";
private static Connection conn;
public static Connection getConnection()
{
Connection connection=null;
if(conn!=null){
connection= conn;
}
else {
connection= getConnection(Driver,URL);
conn=connection;
}
return connection;
}
public static Connection getConnection(String driver,String url)
{
Connection conn=null;
try
{
Class.forName(driver);//driver来源已经修改为CSV驱动器
conn = DriverManager.getConnection(url);//连接也已经使用了重载的方法,单以
URL为入参
System.out.println("New db conn created::"+conn.toString());
}
catch (Exception e) {
e.printStackTrace();
}
return conn;
}
//以下closeConnection、select方法与之前无异,略
public static void main(String [] args){
//main方法中无任何变化
}
这个程序运行完毕后,如果输入的数据内容相同,那么可以得到与之前相同的输出结果。这种方式为基础数据的参数化提供了便利,在数据库中完成开发之后,可以将结果导出为CSV,供用例集运行时作为初始基础数据使用。毕竟如本书第1章中所介绍的“快速回归系统”,在测试执行过程中,一遍遍清洗数据库是一个耗时耗力的事情,至少包括数据库的停止、清洗、导入和开启。而将不同的数据集导出为CSV文件,不同的测试集通过使用不同的CSV文件目录作为数据源,可以大大减轻其中的复杂程度。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。