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(1128)
xlsxに対応したものを含めたもの
junit_allInOne_xlsx.zip(767)
- xlsx対応やテーブルの差異すべて表示や項目のトリム処理を含めたもの
junit_allInOne.zip(818)
[カテゴリ: プログラミング言語 > Java]
[通知用URL]
Tweet
最終更新時間:2015年08月23日 21時35分45秒