使用dbUnit,JSON,HSQLDB和JUnit規則進行數據庫單元測試

在本周TDD課程的運行中,我認為編寫一些夾具以簡化dbUnit的使用將很有趣。 我最初的想法只是教dbUnit有關JSON的知識,但事實證明Lieven Doclo已經做到了。 因此,我決定更進一步,還將dbUnit與JUnit Rules結合起來,并提供HSQLDB內存對象存儲的自動引導。

以下測試顯示了我最終得到的結果:
package com.danhaywood.tdd.dbunit.test;import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;import java.sql.ResultSet;
import java.sql.Statement;import org.dbunit.Assertion;
import org.dbunit.dataset.ITable;
import org.hsqldb.jdbcDriver;
import org.junit.Rule;
import org.junit.Test;import com.danhaywood.tdd.dbunit.DbUnitRule;
import com.danhaywood.tdd.dbunit.DbUnitRule.Ddl;
import com.danhaywood.tdd.dbunit.DbUnitRule.JsonData;public class DbUnitRuleExample {@Rulepublic DbUnitRule dbUnit = new DbUnitRule(DbUnitRuleExample.class, jdbcDriver.class,"jdbc:hsqldb:file:src/test/resources/testdb", "SA", "");@Ddl("customer.ddl")@JsonData("customer.json")@Testpublic void update_lastName() throws Exception {// whenStatement statement = dbUnit.getConnection().createStatement();statement.executeUpdate("update customer set last_name='Bloggs' where id=2");// then (verify directly)ResultSet rs2 = dbUnit.executeQuery("select last_name from customer where id = 2");assertThat(rs2.next(), is(true));assertThat(rs2.getString("last_name"), equalTo("Bloggs"));// then (verify using datasets)ITable actualTable = dbUnit.createQueryTable("customer", "select * from customer order by id");ITable expectedTable = dbUnit.jsonDataSet("customer-updated.json").getTable("customer");Assertion.assertEquals(expectedTable, actualTable);}
}

其中customer.ddl是:

drop table customer if exists;
create table customer (id         int         not null primary key,first_name varchar(30) not null,initial    varchar(1)  null,last_name  varchar(30) not null
)

customer.json (初始數據集)為:

{"customer":[{"id": 1,"first_name": "John","initial": "K","last_name": "Smith"},{"id": 2,"first_name": "Mary","last_name": "Jones"}]
}

customer-updated.json (最終數據集)為:

{"customer":[{"id": 1,"first_name": "John","initial": "K","last_name": "Smith"},{"id": 2,"first_name": "Mary","last_name": "Bloggs"}]
}

您可能已經發現, @Ddl注釋可以選擇指定要針對數據庫運行的DDL腳本,而@JsonData定義了JSON格式的數據集。

DbUnitRule類的實際實現是:

package com.danhaywood.tdd.dbunit;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;import org.dbunit.IDatabaseTester;
import org.dbunit.JdbcDatabaseTester;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;import com.google.common.io.Resources;public class DbUnitRule implements MethodRule {@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })public static @interface Ddl {String[] value();}@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.METHOD })public static @interface JsonData {String value();}private final Class<?> resourceBase;private IDatabaseTester databaseTester;private IDatabaseConnection dbUnitConnection;private Connection connection;private java.sql.Statement statement;public DbUnitRule(Class<?> resourceBase, Class<?> driver, String url, String user, String password) {this.resourceBase = resourceBase;try {databaseTester = new JdbcDatabaseTester(driver.getName(), url, user, password);dbUnitConnection = databaseTester.getConnection();connection = dbUnitConnection.getConnection();statement = connection.createStatement();} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic Statement apply(final Statement base, final FrameworkMethod method, final Object target) {return new Statement() {@Overridepublic void evaluate() throws Throwable {try {Ddl ddl = method.getAnnotation(Ddl.class);if (ddl != null) {String[] values = ddl.value();for (String value : values) {executeUpdate(Resources.toString(resourceBase.getResource(value), Charset.defaultCharset()));}}JsonData data = method.getAnnotation(JsonData.class);if (data != null) {IDataSet ds = new JSONDataSet(resourceBase.getResourceAsStream(data.value()));databaseTester.setDataSet(ds);}databaseTester.onSetup();base.evaluate();} finally {databaseTester.onTearDown();}}};}public java.sql.Connection getConnection() {return connection;}public void executeUpdate(String sql) throws SQLException {statement.executeUpdate(sql);}public ResultSet executeQuery(String sql) throws SQLException {return statement.executeQuery(sql);}public IDataSet jsonDataSet(String datasetResource) {return new JSONDataSet(resourceBase.getResourceAsStream(datasetResource));}public ITable createQueryTable(String string, String string2) throws DataSetException, SQLException {return dbUnitConnection.createQueryTable(string, string2);}
}

這使用了Lieven Doclo的JSONDataSet(為方便起見在此處復制):

import org.codehaus.jackson.map.ObjectMapper;import org.dbunit.dataset.*;
import org.dbunit.dataset.datatype.DataType;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;/*** DBUnit DataSet format for JSON based datasets. It is similar to the flat XML layout,* but has some improvements (columns are calculated by parsing the entire dataset, not just* the first row). It uses Jackson, a fast JSON processor.* <br/><br/>* The format looks like this:* <br/>* <pre>* {*    "&lt;table_name&gt;": [*        {*             "&lt;column&gt;":&lt;value&gt;,*             ...*        },*        ...*    ],*    ...* }* </pre>* <br/>* I.e.:* <br/>* <pre>* {*    "test_table": [*        {*             "id":1,*             "code":"JSON dataset",*        },*        {*             "id":2,*             "code":"Another row",*        }*    ],*    "another_table": [*        {*             "id":1,*             "description":"Foo",*        },*        {*             "id":2,*             "description":"Bar",*        }*    ],*    ...* }* </pre>** @author Lieven DOCLO*/
public class JSONDataSet extends AbstractDataSet {// The parser for the dataset JSON fileprivate JSONITableParser tableParser = new JSONITableParser();// The tables after parsingprivate List<ITable> tables;/*** Creates a JSON dataset based on a file* @param file A JSON dataset file*/public JSONDataSet(File file) {tables = tableParser.getTables(file);}/*** Creates a JSON dataset based on an inputstream* @param is An inputstream pointing to a JSON dataset*/public JSONDataSet(InputStream is) {tables = tableParser.getTables(is);}@Overrideprotected ITableIterator createIterator(boolean reverse) throws DataSetException {return new DefaultTableIterator(tables.toArray(new ITable[tables.size()]));}private class JSONITableParser {private ObjectMapper mapper = new ObjectMapper();/*** Parses a JSON dataset file and returns the list of DBUnit tables contained in* that file* @param jsonFile A JSON dataset file* @return A list of DBUnit tables*/public List<ITable> getTables(File jsonFile) {try {return getTables(new FileInputStream(jsonFile));} catch (IOException e) {throw new RuntimeException(e.getMessage(), e);}}/*** Parses a JSON dataset input stream and returns the list of DBUnit tables contained in* that input stream* @param jsonStream A JSON dataset input stream* @return A list of DBUnit tables*/@SuppressWarnings("unchecked")public List<ITable> getTables(InputStream jsonStream) {List<ITable> tables = new ArrayList<ITable>();try {// get the base object tree from the JSON streamMap<String, Object> dataset = mapper.readValue(jsonStream, Map.class);// iterate over the tables in the object treefor (Map.Entry<String, Object> entry : dataset.entrySet()) {// get the rows for the tableList<Map<String, Object>> rows = (List<Map<String, Object>>) entry.getValue();ITableMetaData meta = getMetaData(entry.getKey(), rows);// create a table based on the metadataDefaultTable table = new DefaultTable(meta);int rowIndex = 0;// iterate through the rows and fill the tablefor (Map<String, Object> row : rows) {fillRow(table, row, rowIndex++);}// add the table to the list of DBUnit tablestables.add(table);}} catch (IOException e) {throw new RuntimeException(e.getMessage(), e);}return tables;}/*** Gets the table meta data based on the rows for a table* @param tableName The name of the table* @param rows The rows of the table* @return The table metadata for the table*/private ITableMetaData getMetaData(String tableName, List<Map<String, Object>> rows) {Set<String> columns = new LinkedHashSet<String>();// iterate through the dataset and add the column names to a setfor (Map<String, Object> row : rows) {for (Map.Entry<String, Object> column : row.entrySet()) {columns.add(column.getKey());}}List<Column> list = new ArrayList<Column>(columns.size());// create a list of DBUnit columns based on the column name setfor (String s : columns) {list.add(new Column(s, DataType.UNKNOWN));}return new DefaultTableMetaData(tableName, list.toArray(new Column[list.size()]));}/*** Fill a table row* @param table The table to be filled* @param row A map containing the column values* @param rowIndex The index of the row to te filled*/private void fillRow(DefaultTable table, Map<String, Object> row, int rowIndex) {try {table.addRow();// set the column values for the current rowfor (Map.Entry<String, Object> column : row.entrySet()) {table.setValue(rowIndex, column.getKey(), column.getValue());}} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);}}}
}

我為此使用的庫(即依賴項)為:

  • hsqldb 2.2.6
  • dbunit 2.4.8
  • 杰克遜1.9.3
  • slf4j-api-1.6.4,slf4j-nop-1.6.4
  • 谷歌番石榴10.0.1
  • junit 4.8

一如既往,歡迎發表評論。

參考:來自Dan Haywood博客的 JCG合作伙伴 Dan Haywood的dbUnit,JSON,HSQLDB和JUnit Rules的數據庫單元測試 。

相關文章 :

  • JUnit 4.9(測試版3)中的規則
  • Spring 3使用JUnit 4進行測試– ContextConfiguration和AbstractTransactionalJUnit4SpringContextTests
  • Java RESTful API集成測試
  • 何時用集成測試替換單元測試
  • 我的測試和代碼分析工具箱

翻譯自: https://www.javacodegeeks.com/2012/01/db-unit-testing-with-dbunit-json-hsqldb.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/374448.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/374448.shtml
英文地址,請注明出處:http://en.pswp.cn/news/374448.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Codeforces Round #321 (Div. 2) E. Kefa and Watch 線段樹hash

E. Kefa and Watch Time Limit: 1 Sec Memory Limit: 256 MB 題目連接 http://codeforces.com/contest/580/problem/EDescription One day Kefa the parrot was walking down the street as he was on the way home from the restaurant when he saw something glittering by…

python文字游戲源代碼求年紀_Python實現猜年齡游戲代碼實例

1. 在猜年齡的基礎上編寫登錄、注冊方法&#xff0c;并且把猜年齡游戲分函數處理&#xff0c;如 2. 登錄函數 3. 注冊函數 4. 猜年齡函數 5. 選擇獎品函數 代碼如下 import json real_age 18 prize_list [好迪洗發水, 綠箭俠, 小豬佩奇, 布娃娃, 再來一次!] import random us…

KVC 與 KVO

一、Key-Value Coding (KVC)鍵值編碼 KVC&#xff0c;即是指 NSKeyValueCoding&#xff0c;一個非正式的 Protocol&#xff0c;提供一種機制來間接訪問對象的屬性。KVO 就是基于 KVC 實現的關鍵技術之一。 一個對象擁有某些屬性。比如說&#xff0c;一個 Person 對象有一個 nam…

使用模擬的單元測試–測試技術5

我的最后一個博客是有關測試代碼方法的一系列博客中的第四篇&#xff0c;演示了如何創建使用存根對象隔離測試對象的單元測試。 今天的博客探討了有時被視為對立的技術&#xff1a;使用模擬對象進行單元測試。 同樣&#xff0c;我使用了從數據庫檢索地址的簡單方案&#xff1a;…

多線程中的volatile和偽共享

偽共享 false sharing&#xff0c;顧名思義&#xff0c;“偽共享”就是“其實不是共享”。那什么是“共享”&#xff1f;多CPU同時訪問同一塊內存區域就是“共享”&#xff0c;就會產生沖突&#xff0c;需要控制協議來協調訪問。會引起“共享”的最小內存區域大小就是一個cache…

C語言代碼規范(一)縮進與換行

一、縮進的空格數為4個。最好配置代碼編輯器將TAB鍵設置為空格替換&#xff0c;避免出現另一個編輯器打開時格式變亂的情況。 例如Notepad設置 KEIL設置 二、“{” 和 “}”各自獨占一行。 不規范例子&#xff1a; for(i 0; i < student_num; i) { if((score[i] > 0…

armv7 cortex a系列編程手冊_AWTK能為現代GUI編程帶來何種改變?

AWTK是一個伸縮性極強的嵌入式圖形框架&#xff0c;它的誕生會給GUI編程研發工程師帶來哪些改變&#xff1f;AWTK是一個伸縮性極強的嵌入式圖形框架&#xff0c;可在Cortex-M3這樣低端的單片機上運行&#xff0c;也可以在Cortex-A7/A8/A9等處理器&#xff0c;甚至DSP以及X86處理…

【轉】各種概念POJO、JAVABEAN、DAO、DTO、PO、VO、BO、SSH、EJB

POJO&#xff08;pure old java object&#xff09; 是普通java類&#xff0c;有一些private的參數作為對象的屬性&#xff0c;然后針對每一個參數定義get和set方法訪問的接口。我看到這個定義&#xff0c;心里就有個疑問了&#xff0c;這個POJO跟JavaBean的定義怎么就這么像&a…

為什么要編寫單元測試–測試技巧8

我對最近在“您應該測試什么”上的博客有很多反應&#xff0c;有些人出于各種原因同意我的想法&#xff0c;另一些人則認為建議某些類可能不需要單元測試是非常危險的。 已經處理了什么測試&#xff0c;今天的博客涉及為什么要編寫單元測試&#xff0c;而今天的示例代碼是基于一…

Git遷移 從SVN到Git

Migrating from SVN to Git 首先我們需要在Stach或者GitHub上新建一個Repository, 拿到它的URL。 接下來參照如下步驟 : At first we should create a new git repository at Stash and get the repository URL, and then follow below steps: 1. 切換到本地git工作目錄 chang…

C語言代碼規范(二)空格

一、逗號, 之后加空格 printf("error! score[%d] %d\n", i, score[i]); 二、分號; 之后加空格 for(i 0; i < student_num; i) 三、關系運算符<、<、>、>、、! 前后加空格 if( (score[i] > 0) && (score[i] < 100) ) 四、賦值運算符…

c++ 多重背包狀態轉移方程_動態規劃入門——詳解經典問題零一背包

本文始發于個人公眾號&#xff1a;TechFlow&#xff0c;原創不易&#xff0c;求個關注今天是周三算法與數據結構專題的第12篇文章&#xff0c;動態規劃之零一背包問題。在之前的文章當中&#xff0c;我們一起探討了二分、貪心、排序和搜索算法&#xff0c;今天我們來看另一個非…

Discuz! 的編碼規范

前言 本規范由編程原則組成&#xff0c;融合并提煉了開發人員長時間積累下來的成熟經驗&#xff0c;意在幫助形成良好一致的編程風格。適用范圍 如無特殊說明&#xff0c;以下規則要求完全適用于Discuz!項目&#xff0c;同時也可大部分適用于COMSENZ旗下其他PHP項目。標準化的重…

C語言代碼規范(三)if語句

一、整型變量與0比較 許多人為了一時之便&#xff0c;模仿布爾變量風格寫為如下代碼 if(value) {... }if(!value) {... } 應當用 或 ! 來與0比較 if(0 value) {... }if(0 ! value) {... } 二、當if內的語句是與常量進行比較時&#xff0c;常量為左值&#xff0c;變量為右…

6月24 面向對象的設計原則-----工廠模式和單列模式

工廠模式&#xff1a; 工廠模式就是專門負責將大量有共同接口的類實例化&#xff0c;而且不必事先知道每次是要實例化哪一個類的模式。它定義一個用于創建對象的接口&#xff0c;由子類決定實例化哪一個類。 工廠模式相當于創建實例對象的new&#xff0c;經常要根據類Class生成…

LeetCode Subsets

原題鏈接在這里&#xff1a;https://leetcode.com/problems/subsets/ 題目&#xff1a; Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset must be in non-descending order.The solution set must not contain duplicate su…

使用ThreadPoolExecutor并行化獨立的單線程任務

Java SE 5.0中引入的任務執行框架是簡化多線程應用程序的設計和開發的巨大飛躍。 該框架提供了用于管理任務概念&#xff0c;管理線程生命周期及其執行策略的工具。 在此博客文章中&#xff0c;我們將描述該框架的功能&#xff0c;靈活性和簡單性&#xff0c;以展示一個簡單的用…

python定義一個圓_Python-矩形和圓形

原博文 2019-11-11 12:34 ? Exercise 15.1. 定義一個叫做Circle 類&#xff0c;類的屬性是圓心 (center) 和半徑 (radius) , 其中&#xff0c;圓心 (center) 是一個 Point 類&#xff0c;而半徑 (radius) 是一個數字。 實例化一個圓心 (center) 為 (150, 100) &#xff0c;半…

C語言代碼規范(四)命名規則

一、宏定義全部字母大寫&#xff0c;單詞間下劃線間隔 #define FLASH_PAGE_SIZE 256 #define FLASH_SECTOR_SIZE (4 * 1024) #define FLASH_BLOCK_SIZE (64 * 1024) #define FLASH_SIZE (16 * 1024 * 1024) 二、const修飾的常量全部字母大寫&#xff0c;單詞間…

Forbidden You don't have permission to access / on this server PHP

Forbidden You dont have permission to access / on this server PHP 在新安裝的谷歌游覽器里&#xff0c;打不了PHP網站了&#xff0c;錯誤顯示&#xff1a; Forbidden You dont have permission to access / on this server. 原因還是配置權限問題 解決辦法&#xff1a; wa…