玩转单元测试之DBUnit
DBunit 是一种扩展于JUnit的数据库驱动测试框架,它使数据库在测试过程之间处于一种已知状态,如果一个测试用例对数据库造成了破坏性影响,它可以帮助避免造成后面的测试失败或者给出错误结果。
虽然不是什么新鲜货,但最近正好用到,就把学到的跟大家分享一下。
关键词:数据库层测试,DAO层测试,DBUnit教程,DBUnit入门,DBUnit实例,Sring中结合DBUnit对Dao层测试
目录
简介
前提条件
Maven配置
准备工作
实例详解
测试基类
关于数据集
Example 1 FlatXmlDataSet
Example 2 ReplacementDataSet
Example 3 XlsDataSet
Example 4 QueryDataSet
Example 5 other
Troubleshooting
参考
简介
DBunit通过维护真实数据库与数据集(IDataSet)之间的关系来发现与暴露测试过程中的问题。IDataSet 代表一个或多个表的数据。此处IDataSet可以自建,可以由数据库导出,并以多种方式体现,xml文件、XLS文件和数据库查询数据等。
基于DBUnit 的测试的主要接口是IDataSet,可以将数据库模式的全部内容表示为单个IDataSet 实例。这些表本身由Itable 实例来表示。
IDataSet 的实现有很多,每一个都对应一个不同的数据源或加载机制。最常用的几种 IDataSet 实现为:
FlatXmlDataSet :数据的简单平面文件 XML 表示
QueryDataSet :用 SQL 查询获得的数据
DatabaseDataSet :数据库表本身内容的一种表示
XlsDataSet :数据的excel 表示
前提条件
- JDK 1.7
- Maven 3
Maven配置
pom里添加以下的dependencies
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.5.1</version>
</dependency>
实例详解
测试流程大概是这样的,建立数据库连接-> 备份表 -> 调用Dao层接口 -> 从数据库取实际结果-> 事先准备的期望结果 -> 断言 -> 回滚数据库 -> 关闭数据库连接
因为每个测试都有很多共性,所以提取成抽象基类如下。
测试基类:
package com.demo.test.dao.impl; import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException; import javax.sql.DataSource; import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.DefaultDataSet;
import org.dbunit.dataset.DefaultTable;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.ext.mysql.MySqlDataTypeFactory;
import org.dbunit.operation.DatabaseOperation;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration; /**
* @Description: BaseDaoTest class
* @author wadexu
*
* @updateUser
* @updateDate
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "file:src/test/resources/mvc-dispatcher-servlet.xml")
@TransactionConfiguration(defaultRollback = true)
public abstract class BaseDaoTest extends AbstractTransactionalJUnit4SpringContextTests { @Autowired
private DataSource dataSource; private static IDatabaseConnection conn; private File tempFile; public static final String ROOT_URL = System.getProperty("user.dir") + "/src/test/resources/"; @Before
public void setup() throws Exception {
//get DataBaseSourceConnection
conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource)); //config database as MySql
DatabaseConfig dbConfig = conn.getConfig();
dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory()); } @After
public void teardown() throws Exception {
if (conn != null) {
conn.close();
} } /**
*
* @Title: getXmlDataSet
* @param name
* @return
* @throws DataSetException
* @throws IOException
*/
protected IDataSet getXmlDataSet(String name) throws DataSetException, IOException {
FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
builder.setColumnSensing(true);
return builder.build(new FileInputStream(new File(ROOT_URL + name)));
} /**
* Get DB DataSet
*
* @Title: getDBDataSet
* @return
* @throws SQLException
*/
protected IDataSet getDBDataSet() throws SQLException {
return conn.createDataSet();
} /**
* Get Query DataSet
*
* @Title: getQueryDataSet
* @return
* @throws SQLException
*/
protected QueryDataSet getQueryDataSet() throws SQLException {
return new QueryDataSet(conn);
} /**
* Get Excel DataSet
*
* @Title: getXlsDataSet
* @param name
* @return
* @throws SQLException
* @throws DataSetException
* @throws IOException
*/
protected XlsDataSet getXlsDataSet(String name) throws SQLException, DataSetException,
IOException {
InputStream is = new FileInputStream(new File(ROOT_URL + name)); return new XlsDataSet(is);
} /**
* backup the whole DB
*
* @Title: backupAll
* @throws Exception
*/
protected void backupAll() throws Exception {
// create DataSet from database.
IDataSet ds = conn.createDataSet(); // create temp file
tempFile = File.createTempFile("temp", "xml"); // write the content of database to temp file
FlatXmlDataSet.write(ds, new FileWriter(tempFile), "UTF-8");
} /**
* back specified DB table
*
* @Title: backupCustom
* @param tableName
* @throws Exception
*/
protected void backupCustom(String... tableName) throws Exception {
// back up specific files
QueryDataSet qds = new QueryDataSet(conn);
for (String str : tableName) { qds.addTable(str);
}
tempFile = File.createTempFile("temp", "xml");
FlatXmlDataSet.write(qds, new FileWriter(tempFile), "UTF-8"); } /**
* rollback database
*
* @Title: rollback
* @throws Exception
*/
protected void rollback() throws Exception { // get the temp file
FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
builder.setColumnSensing(true);
IDataSet ds =builder.build(new FileInputStream(tempFile)); // recover database
DatabaseOperation.CLEAN_INSERT.execute(conn, ds);
} /**
* Clear data of table
*
* @param tableName
* @throws Exception
*/
protected void clearTable(String tableName) throws Exception {
DefaultDataSet dataset = new DefaultDataSet();
dataset.addTable(new DefaultTable(tableName));
DatabaseOperation.DELETE_ALL.execute(conn, dataset);
} /**
* verify Table is Empty
*
* @param tableName
* @throws DataSetException
* @throws SQLException
*/
protected void verifyTableEmpty(String tableName) throws DataSetException, SQLException {
Assert.assertEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
} /**
* verify Table is not Empty
*
* @Title: verifyTableNotEmpty
* @param tableName
* @throws DataSetException
* @throws SQLException
*/
protected void verifyTableNotEmpty(String tableName) throws DataSetException, SQLException {
Assert.assertNotEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
} /**
*
* @Title: createReplacementDataSet
* @param dataSet
* @return
*/
protected ReplacementDataSet createReplacementDataSet(IDataSet dataSet) {
ReplacementDataSet replacementDataSet = new ReplacementDataSet(dataSet); // Configure the replacement dataset to replace '[NULL]' strings with null.
replacementDataSet.addReplacementObject("[null]", null); return replacementDataSet;
}
}
##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
我这里介绍的测试案例都是基于Spring项目的,如果是普通的项目,如何配置数据库连接如下:
public static void init() throws Exception { // get DataBaseSourceConnection
testDataSource = new BasicDataSource();
testDataSource.setDriverClassName("com.mysql.jdbc.Driver");
testDataSource.setUrl("jdbc:mysql://10.52.26.11:3306/Test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true");
testDataSource.setUsername("xxx");
testDataSource.setPassword("xxxxx");
connection = new DatabaseDataSourceConnection(testDataSource);
ImporterManager.setJdbcTemplate(new JdbcTemplate(testDataSource));
}
关于数据集
DBUnit可以把所有表的记录存在一个数据集中:既可以是数据库中的表,也可以是文件中的数据。我们在此用FlatXmlDataSet来讲述。
在FlatXmlDataSet对应的XML文件里,元素名称对应数据库表名,元素的属性(attribute)对应表的列。如:
<dataset>
<Person Name="Kirin" Age="31" Location="Beijing"/>
<Person Name="Jade" Age="30"/>
</dataset>
要注意,如果数据库中某一条字段为null,在flat XML中将不会显示该attribute。另外,FlatXmlDataSet用XML文件中该表的第一行数据来制定表的结构。因此,如果数据库中某个字段所有记录都为null,或者恰巧第一条记录为null,那么得到的表结构与原数据库的表结构就不一致了,测试就会失败。FlatXmlDataSet中存在一个column sensing的概念,在从文件加载数据时,将该属性设置为true,就会根据第一行展现出来的表结构,自动将别的行的列补齐。
顺便提一句,DBUnit中还存在另一种格式的数据集XmlDataSet,在XmlDataSet对应的XML文件里,用元素的子元素对应表的列。如:
<dataset>
<Person>
<Name>Kirin</Name>
<Age>31</Age>
<Location>Beijing</Location>
</Person>
<Person>
<Name>Jade</Name>
<Age>30</Age>
<Location/>
</Person>
</dataset>
null的表示方法如红色部分。
##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
Example 1
关于FlatXmlDataSet
package com.demo.test.dao.impl; import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List; import org.dbunit.Assertion;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import com.demo.test.dao.BundleDao;
import com.demo.test.dao.VersionDao;
import com.demo.test.entity.InfoEntity;
import com.demo.test.exception.DBException;/**
* @Description: BundleDaoImpl Test via DBUnit
* @author wadexu
*
* @updateUser
* @updateDate
*/
public class BundleDaoImplDBUnitTest_Demo extends BaseDaoTest { @Autowired
private BundleDao bundleDao; @Autowired
private VersionDao versionDao;private static final String TABLE_DOCUMENTS_MASTER = "Documents_Master";
private static final String TABLE_FILE_VERSION = "FILE_VERSION";
private static final String VERSION_VALUE = "11.0.3"; @Test
public void testInsertBundles_1() throws Exception {
backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.deleteAll(); bundleDao.insertBundle(getBundles()); //get actual tableInfo from DB
IDataSet dbDataSet = getDBDataSet();
ITable dbTable = dbDataSet.getTable(TABLE_DOCUMENTS_MASTER); //get expect Information from xml file
IDataSet xmlDataSet = getXmlDataSet("expect_documents_master.xml");
ITable xmlTable = xmlDataSet.getTable(TABLE_DOCUMENTS_MASTER); //exclude some columns which don't want to compare result
dbTable = DefaultColumnFilter.excludedColumnsTable(dbTable, new String[]{"IndexId", "DM_VERSION_ID"});
xmlTable = DefaultColumnFilter.excludedColumnsTable(xmlTable, new String[]{"IndexId", "DM_VERSION_ID"}); Assertion.assertEquals(xmlTable, dbTable); rollback();
}
}
首先我备份了两张用到的表,当然也可以备份所有的表,基类都有写这些方法 (backupCustom, backupAll)
然后调用Dao层提供的方法,删除所有数据,接着插入Bundle数据, getBundles()是我的私有方法,构造insertBundle方法所需的数据
接下来,从DB里取实际数据, 用ITable的形式来表示表的实际内容
期望结果是从已准备好的xml文件读取, getxmlDataSet方法里用到了我上文所述的column sensing的概念, setColumnSensing=true, 前提是xml文件的第一行数据的列字段要全,和数据里的表结构一致。
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<Documents_Master IndexId="77" No="1" Retired="Y" GeneralCategory="Financial" DM_VERSION_ID="13" SOURCE="DBUnit"/>
<Documents_Master IndexId="78" No="0" Retired="N" GeneralCategory="test" DM_VERSION_ID="13"/>
</dataset>
在断言两张表之前,因为有些字段我不想比较,比如ID字段,它的值是动态的,无法事先定义好期望结果,所以可以用DefaultColumnFilter里的excludedColumnsTable方法来将指定字段给排除在比较范围之外。
同样还有includedColumnsTable方法可以指定想要比较的字段。
最后回滚数据库。
Example 2
如果插入数据库的数据很多字段的值都是null, FlatXmlDataSet所
对应的XML文件里的数据该怎么定义第一行呢?
这时候ReplacementDataSet就可以登场了。
@Test
public void testInsertBundles_2() throws Exception {
backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.deleteAll(); bundleDao.insertBundle(getBundles()); //get actual tableInfo from DB
IDataSet dbDataSet = getDBDataSet();
ITable dbTable = dbDataSet.getTable(TABLE_DOCUMENTS_MASTER); //get expect Information from xml file
IDataSet xmlDataSet = getXmlDataSet("expect_documents_master_2.xml");
// handle null value, replace "[null]" strings with null
ReplacementDataSet replacementDataSet = createReplacementDataSet(xmlDataSet);
ITable xmlTable = replacementDataSet.getTable(TABLE_DOCUMENTS_MASTER); //exclude some columns which don't want to compare result
dbTable = DefaultColumnFilter.excludedColumnsTable(dbTable, new String[]{"IndexId", "DM_VERSION_ID"});
xmlTable = DefaultColumnFilter.excludedColumnsTable(xmlTable, new String[]{"IndexId", "DM_VERSION_ID"}); Assertion.assertEquals(xmlTable, dbTable); rollback();
}
我的expect_documents_master_2.xml 文件如下:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<Documents_Master IndexId="77" No="1" Retired="Y" GeneralCategory="[null]" DM_VERSION_ID="13" SOURCE="[null]"/>
<Documents_Master IndexId="78" No="0" Retired="N" DM_VERSION_ID="13"/>
</dataset>
空元素的字段需要一个"[null]"占位符,然后用 replacementDataSet.addReplacementObject("[null]", null) 替换成null, 详见基类BaseDaoTest里的方法createReplacementDataSet.
Example 3
关于XlsDataSet
@Test
public void testInsertBundles_Excel() throws Exception {
backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.deleteAll();
bundleDao.insertBundle(getBundles());
//get actual tableInfo from DB
IDataSet dbDataSet = getDBDataSet();
ITable dbTable = dbDataSet.getTable(TABLE_DOCUMENTS_MASTER); //get expect result from xls file
XlsDataSet xlsDataSet = getXlsDataSet("expect_documents_master.xls");
// table name is sheet name
ITable xlsTable = xlsDataSet.getTable("Sheet1"); //column filter, only compare the column in xls
dbTable = DefaultColumnFilter.includedColumnsTable(dbTable, xlsTable.getTableMetaData().getColumns()); Assertion.assertEquals(xlsTable, dbTable); rollback();
}
这个例子的期望结果是定义在excel里的,目前只支持xls文件,即Excel97-2003
这里用到了includedColumnsTable,只比较excel里定义的那些字段。
##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
Example 4
QueryDataSet
@Test
public void testQueryBundles() throws Exception {
backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.insertBundle(getBundles()); List<InfoEntity> list = bundleDao.queryBundles(); //get expect result from DB
QueryDataSet queryDataSet = getQueryDataSet();
queryDataSet.addTable("test", "select * from Documents_Master");
ITable dbTable = queryDataSet.getTable("test"); Assert.assertEquals(dbTable.getRowCount(), list.size()); rollback();
}
通过自己的query语句查到的结果作为期望结果与调用Dao层取得的实际结果比较断言。
Example 5
@Test
public void testDeleteAll() throws Exception {
backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.insertBundle(getBundles());
verifyTableNotEmpty(TABLE_DOCUMENTS_MASTER); bundleDao.deleteAll();
verifyTableEmpty(TABLE_DOCUMENTS_MASTER); rollback();
}
Run as JUnit
测试结果如下图,毕竟是实际读写数据库,速度还是比较慢的, 49秒多。(慢跟我的本地环境连远程数据库也有很大关系)
##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
Troubleshooting
1. java.lang.NoSuchMethodError: org.apache.poi.hssf.usermodel.HSSFDateUtil.isCellDateFormatted(Lorg/apache/poi/hssf/usermodel/HSSFCell;
--用最新的包2.5.1可以解决这个问题
2. 控制台报警
WARN org.dbunit.dataset.AbstractTableMetaData - Potential problem found: The configured data type factory 'class org.dbunit.dataset.datatype.DefaultDataTypeFactory' might cause problems with the current database 'MySQL' (e.g. some datatypes may not be supported properly).
In rare cases you might see this message because the list of supported database products is incomplete (list=[derby]). If so please request a java-class update via the forums.If you are using your own IDataTypeFactory extending DefaultDataTypeFactory, ensure that you override getValidDbProducts() to specify the supported database products.
--需要配置如下属性:
DatabaseConfig dbConfig = conn.getConfig();
dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());
3. 如遇到这个错误
Extra columns on line x. Those columns will be ignored. Please add the extra columns to line 1, or use a DTD to make sure the value of those columns are populated.
则需要用setColumnSensing
FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
builder.setColumnSensing(true);
IDataSet dataSet = builder.build(new File("test.xml"));
4. 有关联的表 需要一起backup
5. 基类BaseDaoTest 因为没有@Test测试方法,所以需要写成抽象类,不然会出现 java.lang.Exception: No runnable methods
6. 如果想让DBUnit支持Excel2007 xlsx格式的文件的话,需要自己下载源码,把org.apache.poi.hssf 改成 xssf, 或者ss支持新老格式,重新编译, 再依赖进来。
参考
官方文档: http://dbunit.sourceforge.net/
感谢阅读,如果您觉得本文的内容对您的学习有所帮助,您可以点击右下方的推荐按钮,您的鼓励是我创作的动力。
##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
玩转单元测试之DBUnit的更多相关文章
- 玩转单元测试之Testing Spring MVC Controllers
玩转单元测试之 Testing Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html The Spri ...
- 玩转单元测试之WireMock -- Web服务模拟器
玩转单元测试之WireMock -- Web服务模拟器 WireMock 是一个灵活的库用于 Web 服务测试,和其他测试工具不同的是,WireMock 创建一个实际的 HTTP服务器来运行你的 We ...
- iOS 单元测试之XCTest详解(一)
iOS 单元测试之XCTest详解(一) http://blog.csdn.net/hello_hwc/article/details/46671053 原创blog,转载请注明出处 blog.csd ...
- 单元测试之NSNull 检测
本文主要讲 单元测试之NSNull 检测,在现实开发中,我们最烦的往往就是服务端返回的数据中隐藏着NSNull的数据,一般我们的做法是通过[data isKindOfClass:[NSNull cla ...
- [转载]单元测试之道(使用NUnit)
首先来看下面几个场景你是否熟悉 1.你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而又忐忑的心情点击界面上的 ...
- 单元测试之道(使用NUnit)
首先来看下面几个场景你是否熟悉 1.你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而 又忐忑的心情点击界面上 ...
- 使用VisualStudio进行单元测试之二
借着工作忙的借口,偷了两天懒,今天继续单元测试之旅.前面说了如何进行一个最简单的单元测试,这次呢就跟大家一起来熟悉一下,在visual studio中如何进行数据驱动的单元测试. 开始之前先来明确一下 ...
- 单元测试之Qunit
单元测试之Qunit 前言 因为公司开发了一套javascript SDK需要测试,在网上找了很久,找到了JQuery团队开发的QUnit,和基于JUnit的JsUnit,还有一些还没有看,先讲讲QU ...
- Visual Studio 单元测试之二---顺序单元测试
原文:Visual Studio 单元测试之二---顺序单元测试 此文是上一篇博文:Visual Studio 单元测试之一---普通单元测试的后续篇章.如果读者对Visual Studio的单元测试 ...
随机推荐
- Java中类型的长度
介绍: Java中有8种基本类型,分别是boolean, char, byte, short, int, long, float, double.他们的长度固定,不是对象.对于有必要将基本类型作为对象 ...
- GIT(分布式版本控制系统)
Git是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目.[1] Git的读音为/gɪt/. Git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本 ...
- IT公司100题-tencent-打印所有高度为2的路径
问题描述: 打印所有到叶子节点长度为2的路径 10 / \ 6 16 / \ / \ 4 8 14 18 / \ / \ \ 2 5 12 15 20 / 11 ...
- sql2008 无法附加数据库
sql2008 因为数据库正在使用,所以无法获得对数据库的独占访问权---还原或删除数据库的解决方法 数据库还原出现 3154错误 --主备份 --RESTORE DATABASE [NET_CN] ...
- WebStorm phpStorm 注册码
WebStorm User or company Name: EMBRACE ===== LICENSE KEY===== 24718-12042010 00001h6wzKLpfo3gmjJ8xoT ...
- python import其他文件夹下的模块
模块的路径不在默认搜索路径中,需要在sys.path中添加 import syssys.path.append('需要模块的文件夹路径')
- css2----单行过长的省略处理
li{ width:300px; white-space:nowrap;表示怎样处理li容器中的空白部分,nowrap表强制不换行,直到文本结束或碰到</br> text-overflow ...
- IP地址的分类——a,b,c 类是如何划分的
现在的IP网络使用32位地址,以点分十进制表示,如172.16.0.0.地址格式为:IP地址=网络地址+主机地址 或 IP地址=主机地址+子网地址+主机地址. IP地址类型 最初设计互联网络时,为了便 ...
- Caffe 源碼閱讀(一) Blob.hpp
Blob 四維度(N K H W) N : SGD 一次 mini-batch 個數 K : 如果是圖片表示圖片通道數 如果是中間結果 則理解爲 feature map 個數 H.W : 如果是圖片理 ...
- JQuery Jsonp 跨域
需求:两个不同域的网站想利用ajax交互数据 客户端:ajax的dataType参数设置成jsonp,然后设置一个回调函数(jsonCallBack) 服务器端:返回callfunName([{a:& ...