From 9846e93aeb77ef065174ff28ffd45b1dab1dc263 Mon Sep 17 00:00:00 2001 From: ma91n Date: Wed, 4 Dec 2024 10:25:56 +0900 Subject: [PATCH] DBUnit --- ...57\343\201\276\343\201\243\343\201\237.md" | 157 ++++++++++++++++++ ...3\203\224\343\203\274.jpg:Zone.Identifier" | 0 source/images/20241204a/dbunit.jpg | Bin 0 -> 13906 bytes source/images/20241204a/thumbnail.jpg | Bin 0 -> 11538 bytes 4 files changed, 157 insertions(+) create mode 100644 "source/_posts/20241204a_DBUnit\343\201\247\343\201\204\343\202\215\343\201\204\343\202\215\343\201\257\343\201\276\343\201\243\343\201\237.md" create mode 100644 "source/images/20241204a/dbunit-logo - \343\202\263\343\203\224\343\203\274.jpg:Zone.Identifier" create mode 100644 source/images/20241204a/dbunit.jpg create mode 100644 source/images/20241204a/thumbnail.jpg diff --git "a/source/_posts/20241204a_DBUnit\343\201\247\343\201\204\343\202\215\343\201\204\343\202\215\343\201\257\343\201\276\343\201\243\343\201\237.md" "b/source/_posts/20241204a_DBUnit\343\201\247\343\201\204\343\202\215\343\201\204\343\202\215\343\201\257\343\201\276\343\201\243\343\201\237.md" new file mode 100644 index 000000000000..ca78a1d73a38 --- /dev/null +++ "b/source/_posts/20241204a_DBUnit\343\201\247\343\201\204\343\202\215\343\201\204\343\202\215\343\201\257\343\201\276\343\201\243\343\201\237.md" @@ -0,0 +1,157 @@ +--- +title: "DBUnitでいろいろはまった" +date: 2024/12/04 00:00:00 +postid: a +tag: + - Java + - DBUnit + - テスト +category: + - Programming +thumbnail: /images/20241204a/thumbnail.jpg +author: 澁川喜規 +lede: "Javaでデータベースを使うプロジェクトだったのでDBUnit使うぜ、と導入したのですが、細かいところで引っかかったりしたので備忘メモです。" +--- + + +本ブログは、[ソフトウェアテスト - Qiita Advent Calendar 2024 - Qiita](https://qiita.com/advent-calendar/2024/softwaretesting) の4日目です。 + +3日目は、ぱいん (pinecandy)さんの[テストについて相談を受けたときにいつもしていること - 飴ブロ(仮](https://pineapplecandy.hatenadiary.jp/entry/test-talk-over)です。 + +Javaでデータベースを使うプロジェクトだったのでDBUnit使うぜ、と導入したのですが、細かいところで引っかかったりしたので備忘メモです。 + +# org.dbunit.database.AmbiguousTableNameException + +このプロジェクトでは複数のスキーマがあり、一部重複したテーブル名があったため、このような例外が出ていました。共通スキーマsystemというのがあったとしたら、それを``JdbcDatabaseTester``に渡して設定します。 + +```java + databaseTester = new JdbcDatabaseTester(this.dataSourceDriver, + this.dataSourceUrl, + this.dataSourceUser, + this.dataSourcePassword, + "system"); // これ + + IDataSet dataSet = new FlatXmlDataSetBuilder().build( + new File("src/test/resources/dbunit/fixture.xml")); + databaseTester.setDataSet(replacementDataSet); + databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); + databaseTester.onSetup(); +``` + +# 複数のスキーマにデータを入れたい! + +1つのデータを定義したけど、一部のデータは別のスキーマに入れたい、という場合です。取込は2回書くのですが、データセットをデータテスターに渡すときに、フィルターでラップしてから渡します。片方は``IncludeTableFilter``、もう片方は``ExcludeTableFilter``を使います。 + +雑にベタに書いたので、もしかしたらFlatXmlDataSetBuilderとかは + +```java + var appOnlyTables = new String[] { + "t_work", + "t_log"}; + databaseTesterApp = new JdbcDatabaseTester(this.dataSourceDriver, + this.dataSourceUrl, + this.dataSourceUser, + this.dataSourcePassword, + "app"); + IDataSet dataSetApp = new FlatXmlDataSetBuilder().build( + new File("src/test/resources/dbunit/fixture.xml")); + + // このフィルターでテーブルを選別 + var dataFilterApp = new IncludeTableFilter(appOnlyTables); + var filterdDatasetApp = new FilteredDataSet(dataFilterApp, dataSetApp); + + databaseTesterApp.setDataSet(filterdDatasetApp); + databaseTesterApp.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); + databaseTesterApp.onSetup(); + + databaseTesterSys = new JdbcDatabaseTester(this.dataSourceDriver, + this.dataSourceUrl, + this.dataSourceUser, + this.dataSourcePassword, + "system"); + IDataSet dataSetSys = new FlatXmlDataSetBuilder().build( + new File("src/test/resources/dbunit/fixture.xml")); + + // このフィルターでテーブルを選別 + var dataFilterSys = new ExcludeTableFilter(appOnlyTables); + var filterdDatasetSys = new FilteredDataSet(dataFilterSys, dataSetSys); + + databaseTesterSys.setDataSet(filterdDatasetSys); + databaseTesterSys.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); + databaseTesterSys.onSetup(); +``` + +# NULLを表現したい! + +DBUnit標準だと、読み込んだとおりに文字列で解釈しますので、空文字列だと空文字列。NULLとか書くと"NULL"という文字列になります。データセットを置換するラッパーを使い、特定のテキストをオブジェクトに変換するラッパーを作成してデータセットにかぶせることで、nullが表現できます。 + +```java + IDataSet dataSet = new FlatXmlDataSetBuilder().build( + new File("src/test/resources/dbunit/fixture.xml")); + + // このフィルターで変換 + var replacementDataSet = new ReplacementDataSet(dataSet); + replacementDataSet.addReplacementObject("[NULL]", null); + + databaseTesterSys.setDataSet(replacementDataSet); + databaseTesterSys.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); + databaseTesterSys.onSetup(); +``` + +# WindowsとLinuxのフォルダを吸収したい + +データベースのログテーブルには処理したファイル名が入るが、WindowsだとC:\になってしまうし、Linuxだと/tmpになってしまうケースに対応する場合も、おなじデータセットを置換するラッパーを使います。 + +先ほどは``addReplacementObject()``でしたが、こちらでは``addReplacementSubstring()``メソッドです。部分一致で変換できるので``[tmp]/deleted-file.txt``みたいにデータセットに書いておくと、OSの作業フォルダに書き換えてくれます。 + +```java + this.tmpFolderPath = Paths.get(System.getProperty("java.io.tmpdir")).resolve("機能ID"); + + IDataSet dataSet = new FlatXmlDataSetBuilder().build( + new File("src/test/resources/dbunit/fixture.xml")); + + var replacementDataSet = new ReplacementDataSet(dataSet); + replacementDataSet.addReplacementSubstring("[tmp]", + this.tmpFolderPath.toString()); + + databaseTesterSys.setDataSet(replacementDataSet); + databaseTesterSys.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); + databaseTesterSys.onSetup(); +``` + +# XMLで、最初のタグにない要素は無視される + +データセットはExcelとかよりもXMLの方が便利ですね。テーブルごとにまとめれば良いかというと、従属テーブルなんかは親テーブルのそばにあった方が分かりやすいですしね。 + +ということでXML型式を好んで使っていたのですが、1つ罠がありました。次のようなデータがあったとします。 +ユーザーを退会した場合にデータが入るNULLABLEな ``unregisterd_at`` カラムがあり、2つ目のユーザーにだけあります。ですが、このデータは無視されます。というのも、なぜかそのテーブルの要素が持っている属性以外は無視されるとのことでした。↑↑に書いたフィルタを追加して `unregisterd_at="[NULL]"` という属性を最初のユーザーにも設定しなければなりません。 + +```xml + + + + + + +``` + +# 新たに作られたパーティションが、careateDataSet()の結果に表れない + +PL/pgSQLで書かれたパーティション改廃関数を実行した後に、``careateDataSet()``を呼んで正しくテーブルが作られたり削除されたか確認するテストを作成したのですが、古いパーティションの削除処理は正しく処理され、結果のテーブルリストから消えていたのだが、新たに分割して作った新しいパーティションが見えなかったということがありました。 + +コネクションプールが悪さしていたみたいです。HikariCPをラップしたミドルウェア経由でPL/pgSQLの関数を呼んでいたのを、Javaの機能を直接呼んで実行してからDBUnitで実行結果を取得したら削除したパーティションも追加したパーティションの情報も反映された結果が取得できました。 + +```java + try (Connection connection = DriverManager.getConnection(this.dataSourceUrl + "?currentSchema=cmn", + this.dataSourceUser, this.dataSourcePassword)) { + try (Statement stmt = connection.createStatement()) { + stmt.execute("SELECT 関数呼び出し('param1', 'param2')"); + } + } +``` + +# まとめ + +単にトラブルシュートの備忘録なので、かっこいい締めの言葉とかオチとかはありません。 + +いろいろハマりはしましたが、なかなか便利なやつなので、Goとか他の言語にも欲しいですね。 diff --git "a/source/images/20241204a/dbunit-logo - \343\202\263\343\203\224\343\203\274.jpg:Zone.Identifier" "b/source/images/20241204a/dbunit-logo - \343\202\263\343\203\224\343\203\274.jpg:Zone.Identifier" new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/images/20241204a/dbunit.jpg b/source/images/20241204a/dbunit.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c090c4652f0fb05eb22d2174d19343d993131e32 GIT binary patch literal 13906 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 literal 0 HcmV?d00001