トップ 差分 一覧 ソース 置換 検索 ヘルプ PDF RSS ログイン

JUnit


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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
2
3
4
5
6
7
8
9
10
import junit.framework.Test;
import junit.framework.TestSuite;

// 色んなテストをしたいとき
public class TestSample {
  
  public static void main(String[] args){
    JUnitCore.main(TestTest.class.getName());
  }
}

Junit3以前の場合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
2
3
4
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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(153)

xlsxに対応したものを含めたもの
junit_allInOne_xlsx.zip(171)

  • xlsx対応やテーブルの差異すべて表示や項目のトリム処理を含めたもの

junit_allInOne.zip(220)



[カテゴリ: プログラミング言語 > Java]


  • Hatenaブックマークに追加
  • livedoorクリップに追加
  • del.icio.usに追加
  • FC2ブックマークに追加

最終更新時間:2015年08月23日 21時35分45秒