JUnit
概要
面倒なテストを楽にするもの。
何度も同じテストを自動的に行えるので、プログラムの改修を気兼ねなくできる。
注意
4.1 から awtやswingの実行画面がなくなった。
- junit.awtui.TestRunner
- junit.swingui.TestRunner
がなくなり、
- junit.textui.TestRunner
だけになった。
これしか使わないのであまり気にしなくてイイ。
http://www.ne.jp/asahi/hishidama/home/tech/java/junit.html
4.1からアノテーションを使えるようになった。
| JUnit3.8 | JUnit4.1 | |
|---|---|---|
| ライブラリのインポート | TestCaseクラスをインポートする。 | Assertクラスの各メソッドをstaticインポートする。Testアノテーション等をインポートする。 |
| テストクラスの定義 | TestCaseを継承する必要がある。 | 特になし。 |
| テストメソッドの定義 | 「public void test〜()」testで始まる名前で、public voidで引数なしのメソッドが実行対象。 | 「@Test public void 〜()」@Testアノテーションを付けたpublic voidで引数なしのメソッドが実行対象。 |
| テストメソッド実行前後 | setUp()・tearDown()が各テストメソッドの実行前後に呼ばれる。 | @Before・@Afterを付けたメソッドが、各テストメソッドの実行前後に呼ばれる。 |
| 全テストの実行前後 | なし。 | @BeforeClass・@AfterClassを付けたメソッドが、全テストの実行前後に呼ばれる。 |
- 3.8の実装例
1 |
import junit.framework.TestCase;
public class SampleTest extends TestCase {
public void testNormal() {
対象クラス obj = new 対象クラス();
int r = obj.試験対象メソッド();
assertEquals(123, r);
assertEquals(456, obj.get内部状態());
}
public void testException() {
try {
試験対象実行();
fail("例外が発生するはず");
} catch(発生すべき例外 e) {
// success
assertEquals("文言", e.getMessage());
}
//試験の続き〜
}
} |
- 4.1の実装例
1 |
import static org.junit.Assert.*;
import org.junit.Test;
public class SampleTest {
@Test
public void normal() {
対象クラス obj = new 対象クラス();
int r = obj.試験対象メソッド();
assertEquals(123, r);
assertEquals(456, obj.get内部状態());
}
@Test(expected=発生すべき例外.class)
public void testException() {
試験対象実行();
//文言の確認は無理そう?
//別の試験を続けるのは無理そう?
}
} |
準備
http://www.junit.org/
からダウンロードして、
junit-****.jar
にクラスパスを通すだけ。
サンプル
1 |
import java.util.*;
public class Sample {
public static void main(String[] args){
System.out.println("main");
}
public int add(int a, int b){
return a+b;
}
public int divide(int a, int b){
return a/b;
}
} |
このソースをテストしたい場合
1 |
import junit.framework.TestCase;
public class TestSample extends TestCase {
private Sample sample = null;
// 共通の初期化などはここで行う
public void setUp(){
// System.out.println("テスト前に呼ばれる");
sample = new Sample();
}
public void tearDown(){
// System.out.println("テスト後に呼ばれる");
}
public void testMain(){
// System.out.println(sample);
}
public void testAdd(){
// System.out.println(sample);
assertEquals(2,sample.add(1,1));
assertEquals(3,sample.add(1,2));
}
public void testDivide(){
// System.out.println(sample);
assertEquals(1,sample.divide(1,1));
try {
sample.divide(1,0);
fail();
} catch (ArithmeticException e){
// ArithmeticException が出たら正常
} catch (Exception e){
fail();
}
}
} |
実行は
java junit.textui.TestRunner TestSample
TestSampleの main からテストを実行することもできる。
JUnit4以降の場合
1 |
import junit.framework.Test;
import junit.framework.TestSuite;
// 色んなテストをしたいとき
public class TestSample {
public static void main(String[] args){
JUnitCore.main(TestTest.class.getName());
}
} |
Junit3以前の場合
1 |
import junit.framework.Test;
import junit.framework.TestSuite;
public class TestSample extends TestCase {
public static void main(String[] args){
junit.textui.TestRunner.run(suite());
}
public static Test suite() {
TestSuite suite= new TestSuite("Sample Test");
suite.addTestSuite(TestSample.class);
return suite;
}
// 略
} |
実行は
java TestSample
DBUnit
概要
DBへのアクセスをJUnitでテストするもの。
元データや結果のデータをエクセルファイルで準備することも出来る。
便利。
準備
http://muimi.com/j/test/dbunit/
http://dbunit.sourceforge.net/
からダウンロード。
エクセルを読み書きするには、Jakarta POI が必要。
その他に必要なライブラリは、DBUnit の pom.xml に書かれている。
- slf4j-api-1.6.6.jar
- slf4j-log4j12-1.6.6.jar
- log4j-1.2.15.jar
- poi-3.2-FINAL-20081019.jar
- dbunit-2.4.8.jar
- junit-4.10.jar
あたりがあれば、とりあえず動くはず。
サンプル
テストの対象となるクラス
1 |
import java.util.*;
import java.sql.*;
public class test {
public static void main(String[] args){
System.out.println("hoge");
DBAccessor db = DBAccessor.createInstance("org.sqlite.JDBC","jdbc:sqlite:resource/test.db3","","");
new test().writeDB();
db.commit();
}
public int add(int a, int b){
return a+b;
}
public int divide(int a, int b){
return a/b;
}
public boolean writeDB(){
DBAccessor db = DBAccessor.getInstance();
try {
PreparedStatement st = db.prepareStatement("insert into test1 values ('101', 'test100')");
System.out.println("update num = " + st.executeUpdate());
db.close(st);
} catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
} |
テストを実施するクラス。
1 |
import junit.framework.TestCase;
import java.io.*;
// データベース関連
import org.dbunit.Assertion;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.operation.DatabaseOperation;
import org.dbunit.dataset.excel.*;
public class TestTest extends TestCase {
public void setUp(){
System.out.println("テスト前に呼ばれる");
t = new test();
}
public void tearDown(){
System.out.println("テスト後に呼ばれる");
}
public void testMain(){
System.out.println(t);
}
private test t = null;
public void testAdd(){
System.out.println(t);
assertEquals(2,t.add(1,1));
assertEquals(2,t.add(1,2));
}
public void testDivide(){
System.out.println(t);
assertEquals(1,t.divide(1,1));
// // ただ呼ぶだけでいいとおもう。
// t.divide(1,0);
try {
assertEquals(0,t.divide(1,0));
fail();
} catch (ArithmeticException e){
} catch (Exception e){
fail();
}
}
public void testWriteDB() throws Exception {
// System.out.println("test2");
DBAccessor db = DBAccessor.createInstance("org.sqlite.JDBC","jdbc:sqlite:resource/test.db3","","");
//
// System.out.println("test1");
//準備データをDBに入れる
DatabaseOperation.CLEAN_INSERT.execute(
new DatabaseConnection(db.getConnection()),
new XlsDataSet(new File("resource/export.xls")));
//ロジックの実行
assertEquals(true,t.writeDB());
//DBから実際のデータの取得
IDataSet databaseDataSet = new DatabaseConnection(db.getConnection()).createDataSet();
ITable actualTable = databaseDataSet.getTable("test1");
System.out.println(actualTable.getRowCount());
//期待値データの取得
IDataSet expectedDataSet = new XlsDataSet(new File("resource/writeDB_output.xls"));
ITable expectedTable = expectedDataSet.getTable("test1");
//期待値と実際のデータの比較
Assertion.assertEquals(expectedTable, actualTable);
}
} |
データを作成する
エクセルでデータを作成する。
とりあえず、DBをエクセルにエクスポートしてそれを編集して使う。
エクスポートするために簡単なプログラムを作る。
1 |
import java.io.FileOutputStream;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
public class DataExport {
public static void main(String[] args) throws Exception {
DBAccessor db = new DBAccessor("org.sqlite.JDBC","jdbc:sqlite:resource/test.db3","","");
DatabaseConnection con = new DatabaseConnection(db.getConnection());
IDataSet dataset = con.createDataSet();
// IDataSet dataset = con.createDataSet(new String[] {"test2"});
XlsDataSet.write(dataset, new FileOutputStream("resource/export_all.xls"));
}
} |
これで出力されたエクセルを編集して、テストのインプットや結果の比較対象を作成する。
log4j.properties
log4j を内部で使っているので、設定ファイルが無いと怒られる。
log4j.properties をクラスパスが通った場所に置く。
中身はテキトウ。
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1} - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=mylog.log
log4j.appender.file.Append=true
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %5p %c{1} - %m%n
#log4j.rootLogger=debug, stdout, file
# log4j.rootLogger=warn, stdout, file
log4j.rootLogger=error, stdout, file
実行は
java junit.textui.TestRunner TestSample
処理日付などの値が必要な場合
テストの結果として、処理日付などが必要な場合は ReplacementDataSet を使って
期待値を書き換えることができる。
http://hamasyou.com/archives/000207
1 |
ReplacementDataSet dataSet =
new ReplacementDataSet(new FlatXmlDataSet(new FileInputStream("dataset.xml")));
dataSet.addReplacementObject("[SYSDATE]",
new Timestamp(System.currentTimeMillis())); |
一部のカラムを比較対象から除外する
大きく2つの方法がある。
Assertiont#assertEqualsIgnoreColsを使う方法と
DefaultColumnFilterを使う方法
assertEqualsIgnoreColsを使う
Assertion.assertEqualsIgnoreCols(actualTable, expectedTable, new String[] {"update"});
DefaultColumnFilter を使う
ITable filteredActualTable = DefaultColumnFilter.excludeColumn(actualTable, new String[] {"upd_timestamp"});
ITable filteredExpectedTable = DefaultColumnFilter.excludeColumn(expectedTable, new String[] {"upd_timestamp"});
結果をソートして比較する
SotedTable を使うことでテーブルのデータをソートできる。これを利用して結果をソートして比較できる。
String[] sortColumns = new String[] {"id", "name"}
ITable sortedActualTable = new SortedTable(actualTable, sortColumns);
ITable sortedExpectedTable = new SortedTable(expectedTable, sortColumns);
//期待値と実際のデータの比較
Assertion.assertEquals(sortedExpectedTable, sortedActualTable);
xlsx をデータとして利用する
列数が256個を超えたりする場合、xlsxを使いたくなるがdbUnitがxlsxに対応してくれていない。
対応するための機能は既にあるので、それを利用する。
xssfの利用
dbUnitの
・XlsDataSet
・XlsDataSetWriter
・XlsTable
をxlsxを使えるようにする。
具体的には
org.apache.poi.hssf.usermode
を
org.apache.poi.ss.usermodel
に変更する。
new Workbook
は、
WorkbookFactory.create()
にする。
セルの値にかかわらず、セルを文字列として扱う場合
Apache POIを参考に XlsDataSetWriter.java をいじる。
XlsDataSetWriter#write
1 |
public void write(IDataSet dataSet, OutputStream out)
throws IOException, DataSetException
{
logger.debug("write(dataSet={}, out={}) - start", dataSet, out);
Workbook workbook = new XSSFWorkbook();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
this.dateCellStyle = createDateCellStyle(workbook);
int index = 0;
ITableIterator iterator = dataSet.iterator();
while(iterator.next())
{
// create the table i.e. sheet
ITable table = iterator.getTable();
ITableMetaData metaData = table.getTableMetaData();
Sheet sheet = workbook.createSheet(metaData.getTableName());
// write table metadata i.e. first row in sheet
workbook.setSheetName(index, metaData.getTableName());
Row headerRow = sheet.createRow(0);
Column[] columns = metaData.getColumns();
for (int j = 0; j < columns.length; j++)
{
Column column = columns[j];
Cell cell = headerRow.createCell(j);
cell.setCellValue(new XSSFRichTextString(column.getColumnName()));
}
// write table data
for (int j = 0; j < table.getRowCount(); j++)
{
Row row = sheet.createRow(j + 1);
for (int k = 0; k < columns.length; k++)
{
Column column = columns[k];
Object value = table.getValue(j, column.getColumnName());
if (value != null)
{
Cell cell = row.createCell(k);
DataFormat format = workbook.createDataFormat();
CellStyle style = workbook.createCellStyle();
style.setDataFormat(format.getFormat("text"));
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellStyle(style);
if(value instanceof Date){
// setDateCell(cell, (Date)value, workbook);
cell.setCellValue( dateFormat.format(value) );
}
else if(value instanceof BigDecimal){
// setNumericCell(cell, (BigDecimal)value, workbook);
cell.setCellValue( ((BigDecimal)value).toPlainString() );
}
else if(value instanceof Long){
// setDateCell(cell, new Date( ((Long)value).longValue()), workbook);
Date dateValue = new Date( ((Long)value).longValue());
cell.setCellValue( dateFormat.format(dateValue) );
}
else {
cell.setCellValue(new XSSFRichTextString(DataType.asString(value)));
}
}
}
}
index++;
}
// write xls document
workbook.write(out);
out.flush();
} |
Assertion#assertEquals 時のエラーを全て表示する方法
標準のままだと、比較するテーブルの差異が複数あっても、1つの差異しか表示してくれない。
普通はそれで問題ない。テストをする上では、差異があるかどうかが重要である。
ところが、テストのついでにデバッグ的なこともしたい場合は、どこがどのように異なっているかを全て知りたい。
そこで、差異をすべて表示するAssertionを作成する。
DbUnitAssert.java
一部抜粋
compareDataメソッドの中で
1 |
AssertThrowableList errors = new AssertThrowableList();
for (int i = 0; i < expectedTable.getRowCount(); i++) {
// iterate over all columns of the current row
for (int j = 0; j < comparisonCols.length; j++) {
ComparisonColumn compareColumn = comparisonCols[j];
String columnName = compareColumn.getColumnName();
DataType dataType = compareColumn.getDataType();
Object expectedValue = expectedTable.getValue(i, columnName);
Object actualValue = actualTable.getValue(i, columnName);
// Compare the values
if (skipCompare(columnName, expectedValue, actualValue)) {
if (logger.isTraceEnabled()) {
logger.trace( "ignoring comparison " + expectedValue + "=" +
actualValue + " on column " + columnName);
}
continue;
}
if (dataType.compare(expectedValue, actualValue) != 0) {
Difference diff = new Difference(
expectedTable, actualTable,
i, columnName,
expectedValue, actualValue);
try {
// Handle the difference (throw error immediately or something else)
failureHandler.handle(diff);
} catch (Throwable th){
errors.add(th);
}
}
}
}
if (!errors.isEmpty()){
throw new Error( errors.toString());
} |
Assertion.assertEquals 時に値をトリムする方法
各テーブルの項目が文字列の場合に、右側の空白をトリムして比較したい場合、
ITable を継承して独自の Tableオブジェクトを作ってしまう。
複雑な変換とか ReplacementDataSet ではやりきれない場合は、この方法を使うといいかもしれない。
TrimTable.java
1 |
package trim;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableMetaData;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.datatype.StringDataType;
public class TrimTable implements ITable {
protected ITable table;
protected ITableMetaData metaData;
public TrimTable(ITable table){
this.table = table;
this.metaData = table.getTableMetaData();
}
public int getRowCount() {
return table.getRowCount();
}
public ITableMetaData getTableMetaData() {
return metaData;
}
public Object getValue(int row, String columnName) throws DataSetException {
// Column[] columns = metaData.getColumns();
// Column column = columns[metaData.getColumnIndex(columnName)];
Object value = table.getValue(row, columnName);
// if (column.getDataType() instanceof StringDataType && value != null) {
if (value instanceof String) {
value = rtrim((String)value, " ");
// System.out.println("["+value+"]");
}
return value;
}
private static String rtrim(String str, String del){
int start = 0;
int end = str.length();
int length = del.length();
if (length == 0){
return str;
}
while(start <= end - length){
if (str.substring(end - length, end).equals(del)){
end -= length;
} else {
break;
}
}
return str.substring(start, end);
}
} |
呼出
Assertion.assertEquals(new TrimTable(expectedTable), actualTable);
AmbiguousTableNameExceptionが発生する場合
環境によっては、
AmbiguousTableNameException
が発生する場合がある。複数のスキーマで同名のオブジェクトがあると発生することがある。
その場合は、DatabaseConnectionのコンストラクタにスキーマを指定する。
DatabaseConnection con = new DatabaseConnection(db.getConnection(), "TEST_SCHEMA");
みたいな感じ。
環境まるごと
とりあえず、自分が実験した環境をまるごと置いておく。
- 基本的なもの
junit_allInOne_base.zip(1191)
xlsxに対応したものを含めたもの
junit_allInOne_xlsx.zip(841)
- xlsx対応やテーブルの差異すべて表示や項目のトリム処理を含めたもの
junit_allInOne.zip(885)
[カテゴリ: プログラミング言語 > Java]
[通知用URL]
Tweet
最終更新時間:2015年08月23日 21時35分45秒