DbUnitNG プロジェクト日本語トップページ - SourceForge.JPのライセンスをApache License, Version 2.0にきちんとしました(つもりです)。
以下のことをしました。
- LICENSE.txtをトップディレクトリに配置する
- ソースコードにライセンスへのリファレンスを追加する
DbUnitNG プロジェクト日本語トップページ - SourceForge.JPのライセンスをApache License, Version 2.0にきちんとしました(つもりです)。
以下のことをしました。
日曜日に社内勉強会をしました。
僕は株式会社クロノスに所属しているんですが、「クロノスエンジニアの会」略して「エン会」というタイトルで勉強会をしました。
今までの勉強会のスタイルと変えて、デブサミのようなイメージで社内の有志数人で、様々な内容の発表をしました。
幹事的な役割もして、セッション一覧とかもお遊びでデブサミっぽくしたりしました。
僕は「Beyond JUnit3」ということで、JUnit3の弱点と、JUnit4とTestNGの紹介や比較をソースコードを使って解説してみました。
せっかくなので、その資料とソースコードを公開します。
あんまり大したのじゃないんですけど、よかったら活用してください。
SlideShareにアップしました。
http://www.slideshare.net/jyukutyo/beyond-junit3-presentation/
ソースコードはMediaFireにアップしたので、ここからダウンロードしてください。
<!-- TestNGのJARファイルに含まれるtestngtasksファイルを指定する --> <taskdef resource="testngtasks" classpath="lib/testng-5.8-jdk15.jar" /> <!-- 実行時のクラスパスに含めるJARファイルを指定する --> <path id="run.cp"> <fileset dir="lib" includes="*.jar"/> </path> <target name="run-tests"> <testng classpathref="run.cp" haltOnfailure="true"> <!-- テストクラスのクラスファイルをクラスパスに含める --> <classpath> <pathelement location="bin"/> </classpath> <!-- testng.xmlを指定する --> <xmlfileset dir="test" includes="testng.xml" /> </testng> </target>
testng.xmlではなくテストクラスを実行する場合、xmlfilesetではなくclassfileset要素を使います。
<classfileset dir="${test.build.dir}" includes="**/*.class" />
TestNG+DbUnitライブラリであるDbUnitNGのバージョン0.4をリリースしました。
ダウンロード - DbUnitNG - SourceForge.JP
DbUnitNGは、TestNGとDbUnitを連携させ、SetUpやTearDownのアノテーション化、BeanのListをDbUnitのデータセットへ変換といったことをする小さなライブラリです。
今回の追加点は以下のとおり。
DBに接続して、任意のテーブルあるいは全テーブルをDbUnitの形式でファイルに出力するユーティリティクラスを作りました。
org.dbunitng.data.TestDataFileMakerクラスです。初期値や期待値のファイルとしてCSVにも対応しました(他はXMLとExcel)。
期待値のファイルとデータベースのテーブルをアサートするアノテーションを作りました。
@TableAssertです。@TableAssertに機能を追加しました。
結果をDBから取得するクエリをプロパティファイルに記述できるようにしました。AssertionHelperクラスを使ってアサートする場合、
[null]と記述すると、nullとして扱います。
(追記)Wikiも更新しました。ここに書いたことはすべてWikiにまとめています。
DBに接続して、任意のテーブルあるいは全テーブルをDbUnitの形式でファイルに出力するユーティリティクラスを作りました。
org.dbunitng.data.TestDataFileMakerクラスです。
使い方は簡単です。
public TestDataFileMaker(String driver, String url, String userName,
String password, String schema) {
コンストラクタにJDBC接続に必要な情報を渡します。スキーマは特に必要なければnullを渡してください。
メソッドは2つあります。
public void extractTables(String targetDirectory, String[] tableNames, FileType type) public void extractAllTables(String targetDirectory, FileType type)
特定のテーブルだけ出力したい場合extractTables()を、全テーブルを出力する場合はextractAllTables()を呼び出してください。
引数は、targetDirectoryにファイルを出力するディレクトリを渡してください。存在していない場合はディレクトリを作成して出力します。
tableNamesはテーブル名の配列です。
FileTypeはEnumで、XML、EXCEL、CSVから選択してください。この形式でファイルを出力します。
たとえば、こんなテーブルがあるとします。
mysql> select * from dept; +--------+------------+----------+ | DEPTNO | DNAME | LOC | +--------+------------+----------+ | 10 | ACCOUNTING | NEW YORK | | 20 | RESEARCH | DALLAS | 以下省略 mysql> select * from emp; +-------+--------+-----------+------+------------+---------+---------+--------+ | EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | +-------+--------+-----------+------+------------+---------+---------+--------+ | 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | 10.00 | 20 | | 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 | 以下省略
こういうコードでファイルに出力できます。
TestDataFileMaker maker = new TestDataFileMaker( "com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1/testframework", "user", "password", null); String targetDir = "temp/xml/specified"; String[] tableNames = new String[] { "dept", "emp" }; maker.extractTables(targetDir, tableNames, FileType.XML);
するとtemp/xml/specifiedディレクトリにファイルを出力します。
temp/xml/specified -DEPT.xml -EMP.xml
内容もきちんとDbUnit形式です。
<?xml version='1.0' encoding='UTF-8'?> <dataset> <DEPT DEPTNO="10" DNAME="ACCOUNTING" LOC="NEW YORK"/> 以下省略
同様に、Excel形式でもCSV形式でも出力できます。
CSVであれば「table-ordering.txt」も出力します。
TestNG+DbUnitライブラリDbUnitNGで、初期値や期待値のファイルとしてCSVにも対応しました(他はXMLとExcel)。
意外に知らないDbUnitでCSVを使う方法 - Fight the Future じゅくのblogにあるように、DbUnitではCSVファイルの場合「table-ordering.txt」というファイルが必須なので、このファイルをアノテーションに指定した場合、CSVとして処理します。
「table-ordering.txt」は定数としてDbUnitNGConstrantsにあります。
こんな感じです。
@SetUpOperation(pathname = DbUnitNGConstrants.CSV_ORDER_FILE, value = DatabaseOperationType.CLEAN_INSERT) public void testCsvFile() { }
pathname属性にファイル名「table-ordering.txt」だけ指定しているので、テストクラスと同じパッケージに配置します。
「table-ordering.txt」はこんな感じ。
dept emp
テーブル名を記述します。そして、この名前に対応するCSVファイルをテキストファイルと同じパッケージに配置します。dept.csvとemp.csvです。
DEPTNO,DNAME,LOC 10,ACCOUNTING,NEW YORK
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO 7782,CLARK,MANAGER,7839,1981-01-09,2450.0,null,10
1行目は列名、2行目以降にデータを記述します。nullの場合はそのままnullと書きます。
値としてnullとそのまま書くだけでOK。
row[col] = row[col].equals(CsvDataSetWriter.NULL) ? null : row[col];
public static final String NULL = "null";
こんな定数もあるから、きちんとダブルクォートもエスケープしてくれるみたい。
private static final String QUOTE = "\""; private static final String ESCAPE = "\\";
「dbunit csv」で検索してもヒットしなかったので書いておく。
DbUnit2.1から、初期値や期待値をCSVファイルに書くことができる。XMLやExcelの代わりに。
使い方はとても簡単。
たとえば、EMPテーブルがあるとして、emp.csvはこんな感じ。
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,DEPTNO 7782,CLARK,MANAGER,7839,1981-01-09,2450.0,10
DEPTテーブルのdept.csv。
DEPTNO,DNAME,LOC 10,ACCOUNTING,NEW YORK
EMPテーブルのDEPTNOは、DEPTテーブルを参照しているので、データを登録するときは「DEPT→EMP」の順序にしたい。
なので「table-ordering.txt」はこうなる。
dept emp
拡張子.csvは必要なし。
CsvDataSetオブジェクトを生成する。
String pathName = "ディレクトリのクラスパスルートからの位置"; URL url = Thread .currentThread() .getContextClassLoader() .getResource(pathName); CsvDataSet dataSet = new CsvDataSet(new File(url.toURI()));
この話、公式サイトにもドキュメントないような。。。
ソースを読むとわかります。
たとえば。
public CsvDataSet(File dir) throws DataSetException { super(new CsvProducer(dir)); this.dir = dir; }
CsvDataSetのコンストラクタに引数としてdirつまりディレクトリを渡して。
List tableSpecs = CsvProducer.getTables(dir.toURL(), "table-ordering.txt"); for (Iterator tableIter = tableSpecs.iterator(); tableIter.hasNext();) { String table = (String) tableIter.next(); try { produceFromFile(new File(dir, table + ".csv"));
table-ordering.txtを行単位で読んで、その行に.csvをつけたファイルを読み込んでる。
private void produceFromFile(File theDataFile) throws DataSetException, CsvParserException { logger.debug("produceFromFile(theDataFile=" + theDataFile + ") - start"); try { CsvParser parser = new CsvParserImpl(); List readData = parser.parse(theDataFile); List readColumns = ((List) readData.get(0)); Column[] columns = new Column[readColumns.size()]; for (int i = 0; i < readColumns.size(); i++) { columns[i] = new Column((String) readColumns.get(i), DataType.UNKNOWN); }
(List) readData.get(0)つまりファイルの1行目が列名として扱われてる。
TestNG+DbUnitライブラリであるDbUnitNGで、DbUnitライブラリ@TableAssertでDBとアサートできます - Fight the Future じゅくのblogにある@TableAssertに機能を追加しました。
結果をDBから取得するクエリをプロパティファイルに記述できるようにしました。
@TableAssert(names = "dept", pathname = "insert_expected.xml", propertyFilePath="query.properties", keys="test") @SetUpOperation(pathname = "dept.xml", value = DatabaseOperationType.DELETE_ALL) public void testTableAssert3() { insert(); }
クエリを直接Javaコードに記述すると読みづらいので。
@TableAssertのpropertyFilePath属性がプロパティファイルへのパスです。
「/」がない場合、テストクラスと同じパッケージにあるファイルを探します。
「/」があればクラスパスのルートからの位置になります。
keysに指定したキーの値としてクエリを書きます。
test = select dname, deptno, loc from dept
namesはクエリの結果を格納するDbUnitのITableのテーブル名です。
さらに、Googleは「Google Testing Blog」というBlogをやっていた。
内容もかなり興味深い。
ただ、1エントリの文章量が多く、読むのが遅い僕にはつらい。。。
Google Testing Blog
さっきのTestNGの動画で知ったばかりだけど、どうやらGoogleは毎年Google Test Automation Conferenceというのをやってる。
Google Testing Blog: Call for Attendance: GTAC 2008
今年はシアトルで10/23,24にやるみたい。
TestNGの動画は去年のもので、資料もGoogle Groupからダウンロードできる。
Let's talk about China! |
Google Groups
FounderであるCedric BeustのGoogle Test Automation Conferenceのプレゼン。
TestNGのサイトにいきます。
メニューから「Download」を選びます。
「download TestNG here.」のリンクを押すと最新のJARファイルをダウンロードできます。
ダウンロードしたzipファイルを解凍します。
解凍したディレクトリの直下にある「testng-X.X-jdkXX.jar」をクラスパスに追加します。
2種類ありますが、JDKのバージョンに合わせて選択してください。
「testng-X.X-jdk14.jar」をクラスパスに追加します。
アップデートサイトを利用してプラグインをインストールします。
eclipse3.4以降なら、ヘルプ(Help)→ソフトウェアアップデート(Software Updates...)を選択します。
利用できるソフトウェア(Available Software)を選択します。
サイトを追加(Add Site)で「http://beust.com/eclipse」を入力し、OKを押します。
ソフトウェアアップデートの画面に新たに「http://beust.com/eclipse」が表示されます。
それにチェックを入れてインストール(Install)を押します。
あとは表示される画面にOKを押してそのまま進めます。
これでeclipseプラグインのインストールは完了です。
普通のJavaクラスを作成します。継承やインタフェースの実装は必要ありません。クラス名も任意です。
メソッドを作成します。必ず修飾子はpublic、戻り値はvoidにします。メソッド名は任意です。
「org.testng.annotations.Test」クラスをインポートします。Testはアノテーションクラスです。
作成したメソッド(テストメソッド)に@Testアノテーションをつけます。
@Test public void verifySample() { // describe your test program... }
@Testをつけることで、TestNGはそのメソッドをテストメソッドと認識し、テストを実行してくれます。
実行結果が正しいかどうか検証するには、「org.testng.Assert」クラスを利用します。これはJUnitのAssertと同じ役割です。
「org.testng.Assert」クラスのassertXXX()メソッドを利用して検証します。assertXXX()メソッドはすべてstaticメソッドなので、JDK5.0以降であればstaticインポートを利用するとよいです。
import static org.testng.Assert.assertEquals;
assertXXX()には主に4つの種類があります。
メソッド名 | 役割 |
---|---|
assertEquals(実行した結果, 期待する結果) | 実行した結果が期待する結果と同じか比較する |
assertNull(実行した結果) | 実行した結果がnullか比較する |
assertSame(実行した結果, 期待する結果) | 実行した結果が期待する結果と同じインスタンスか比較する |
assertTrue(実行した結果) | 実行した結果がtrueか比較する |
assertEquals()とassertSame()の違いは1つだけです。
assertEquals()は同じかどうかを、equals()メソッドを使って比較します。equals()メソッドはJavaのすべてのクラスのスーパークラスであるObjectクラスに定義されているメソッドです。
assertSame()は同じインスタンスかどうかを比較します。「==」を使って比較するのと同じです。
assertXXXには、比較した結果が違っていたときに、任意のメッセージを表示させることができます。上記の表の引数にプラスString型の引数があるメソッドがあります。
たとえば、assertEquals(実行した結果, 期待する結果, メッセージ)のような形です。
ほかにもassertNotNull(),assertNotSame(),assertFalse()がありますが、上記の表にあるメソッドの意味を反転させただけのものです。
僕の作っているTestNG + DbUnitライブラリでDBとのアサートをアノテーションで指定できるわけですけど。
@TableAssert(names = "dept", pathname = "insert_expected.xml") @SetUpOperation(pathname = "dept.xml", value = DatabaseOperationType.DELETE_ALL) public void testTableAssert() { insert(); }
テーブルとのアサートをアノテーションにしよう、っていう思いつきで作ったんだけど、
よくよく考えると、プロジェクトで作るようなスーパークラスにこういうメソッドを作って、呼び出すというのとそう変わりはない気がした。
アノテーションによってコード量がそこまで減るわけでもないし。。。
ただ、アノテーションというのは処理のインタフェース(実装ではなく仕様の入り口)として優れてると感じた。
この@TableAssertだって処理に必要なデータが属性としてまとまっているから、ここだけ見れば必要なすべてわかる。
この属性を使ってどうこう、という実装はまったく別にあるわけで、目には入らない。
@TableAssertによって、このテストメソッドはDBと比較するんだなとすぐにわかる。
そして、比較するファイルや対象テーブルも属性を見てすぐにわかる。
あとあとのテストのメンテナンスという意味でも。アノテーションにしたことでわかりやすくていいんじゃないかな。
JUnitはTestNGよりも多く採用され拡張されているものの、TestNGの方が多くの機能を提供し、JUnit用のテスト(スクリプト)を実行することも可能なので、テスト・フレームワークとしてTestNGを選択する方が魅力的な選択に思えるようである。
InfoQ: JUnitは死なず
僕は時代の生き証人ではないけど、JUnitって名前が独り歩きし過ぎてる気がする。
たしかにJUnitによってユニットテスト、自動テストといったコンセプトが広く知れ渡るようになったんだし、JUnitの功績は計り知れない。
テスティングフレームワークとしてデファクトの地位を確立しているわけだけど、
その圧倒的なネームバリューのせいか、プロジェクトにおけるテスティングフレームワークは熟考なしにJUnitになってしまう。
別に機能が多ければいいというわけではないので、TestNGを使え!っていうつもりもないんだけど、熟考なし、条件反射的に何も考えず選択をするっていうのは必ずよくないことになる。
つまり、テスティングフレームワークとして今回のプロジェクトでJUnitが適切なのか、TestNGが適切なのか、それとも他のフレームワークが適切なのかといったことをきちんと考えた上で選択しているのか、ということ。
(プロジェクトにおいて最大の罪は前もこうだったとかいつもこれだとか、今回何が適切なのかを深く考えずに決定を下すことだと思ってる。これをなくすだけでしょーもない失敗プロジェクトは減ると個人的に思い込んでる。)
そのためにはJUnitのほかにどんなテスティングフレームワークがあるのか知らないといけないし、どんな特徴があるのか、長短知らないといけない。
JUnitのネームバリューがここんとこを妨げてるんじゃないかなと思う。