SAStrutsでリダイレクトした後に一度だけメッセージを表示する
知識不足からちょっとハマったのでメモ。
SAStrutsでリダイレクトした画面で一度だけメッセージを表示したい場合の処理コード。用途としては、処理完了後の完了メッセージを表示したいというケースで使える。
Actionクラス
public HttpSession session; @Execute public String finish() { ... ActionMessages messages = new ActionMessages(); messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("xxx")); ActionMessagesUtil.saveMessages(session, messages); return "xxx.jsp"; }
JSP
<html:messages id="message" message="true"> <p>メッセージ:${message}</p> </html:messages>
今回、
”普通のメッセージですか?”という属性なのね。個人的にはerror="true"とか指定できたほうが分かりやすい気がする。
「メール見出しジェネレータ」を再び公開
S2UnitでDB削除のテスト
S2UnitによるDBのテストの最後は、削除(DELETE)。今回対象となるのは、論理削除ではなく物理削除。論理削除は、更新系の処理になるのでS2UnitでDB更新のテストを参照のこと。
今回作成したソース
EmployeeService.java
public void deleteByEmployeeId(Integer employeeId) { Employee entity = jdbcManager.from(Employee.class).where("employeeId = ?", employeeId).getSingleResult(); jdbcManager.delete(entity).execute(); }
テスト対象のメソッド。引数で主キーを受け取り、それをキーにレコードを削除している。
EmployeeServiceTest.java
public void test_deleteByEmployeeIdTx() { readXlsWriteDb("EmployeeServiceTestInit.xls"); employeeService.deleteByEmployeeId(1); Employee entity = jdbcManager.from(Employee.class).where("employeeId = ?", 1).getSingleResult(); assertNull(entity); // レコードは削除されたはずなので、nullが取得されること }
テストメソッド。まず、readXlsWriteDb()でDBにテストデータをセットアップした後、テスト対象のメソッドを呼び出している。その後、S2JDBC(jdbcManager)を使用して、削除されたはずのレコードを主キーを元に取得している。そして、その結果がnullであることを確認してテスト終了。
補足
今回のケースでは1件削除するだけであったが、複数のテーブルのレコードを一気に削除するケースも多いと思う(ユーザが退会して〜とか)。そうした場合は、テストコードが冗長になるので、privateなショートカットメソッドを用意して、読みやすくしておくのも手かもしれない。私はテストコードもリファクタリングすべきだと思っている。
assertNull(getEmployeeById(1)); assertNull(getEmployeeById(2)); assertNull(getEmployeeById(3)); assertNull(getDepartmentById(1)); assertNull(getDepartmentById(2));
あるいは、もっと進めて
String[] assertDelete = { "Employee:1,2,3", "Department:1,2" }; assertDeleteRecord(assertDelete);
こんな風に書けるようにするのも悪くないかもしれない。
懸案になっていた問題について
そういえばすっかり忘れていたけど、以前「SAStrtusで初回リクエスト時にクラスからアノテーション情報が取得できない?」という問題を取り上げた。
とりあえず、この問題はCoolDeployの設定だと発生しないことが分かった。うーん、S2ContainerのHotDeploy機能のバグなのかなぁ。
CoolDeployなら問題ないことが分かったので、調査は一旦打ち切ろうと思う。
S2UnitでDB更新のテスト
昨日のエントリーに続き、S2Unitを使ったDBのテスト方法を見ていきたいと思います。今回は、更新(UPDATE)のテストです。
テストの考え方
今回作成したリソース
EmployeeService.java
public void changeDepartmentId(Integer srcDepartmentId, Integer dstDepartmentId) { List<Employee> employeeList = jdbcManager.from(Employee.class) .where("departmentId = ?", srcDepartmentId) .getResultList(); for (Employee employee : employeeList) { employee.departmentId = dstDepartmentId; } jdbcManager.updateBatch(employeeList).execute(); }
今回のテスト対象メソッド。特定の部署の社員を一気に別の部署に移すというレアなケースでの利用を想定したメソッド。
EmployeeServiceTest.java
public void test_changeDepartmentIdTx() { readXlsWriteDb("EmployeeServiceTestInit.xls"); DataSet exp = readXls("EmployeeServiceTest_changeDepartmentId_Assert.xls"); employeeService.changeDepartmentId(1, 2); assertEquals(exp, reload(exp)); // 期待値Excelと実際のDBの内容が一致すること }
テストメソッド。まず検索のテストと同様、readXlsWriteDb()でテスト用データをDBにセットアップしている。その後、readXls()でテストの期待値が定義されたExcelからDataSetを取得している。そして、更新処理を実行。最後に、期待値とDBの内容が一致しているか確認している。
reload()は、主キーを元にして最新のレコードをDBから取得するメソッド。SqlReaderで同様のことができると思ったんだけど、なぜかassertEquals()で失敗してしまった。まぁ、reload()を使ったほうが簡単だからこの方法でいいのだけれど、なぜ失敗するのか気にはなる。
EmployeeServiceTest_changeDepartmentId_Assert.xls
テストの期待値を定義したExcel。変更箇所は分かりやすいように背景を黄色に。
あんまり関係ない余談
社員が特定の部署に所属するという情報を管理する方法として、社員テーブルに部署IDを保持するのはあまり良くないと言われている。所属は関係を示す1つの情報なので、所属テーブルとして抜き出したほうが良い。そうしておけば、所属の履歴を管理したいといった際に、所属テーブルに所属開始日と所属終了日のカラムを増やすだけで対応できる。
S2UnitでDB検索のテスト
CRUD(Create Reference Update Delete)ってなんて読み方が一般的なんだろう。私は”クラッド”なのだけれど”クルド”って読む人もいるらしい。これは、情報は”生成”され、”参照”され、”更新”され、”削除”される、って意味の言葉だけど、この中で何が一番多いかと言えば、言わずもがな”参照”。ということで、今回はS2Unitを用いてDB検索のテストをしたいと思います。
テストの考え方
- テスト用の空のDBを用意する。
- テストの為に必要なデータをExcelから読み取ってテーブルに反映。
- 検索処理を実行(←これが正しいかテスト)
- 検索結果で取得されたエンティティの主キーが意図したものかを確認する。
今回作成したリソース
EmployeeService.java
public class EmployeeService { public JdbcManager jdbcManager; public List<Employee> findByDepartmentId(Integer departmentId) { List<Employee> result = jdbcManager.from(Employee.class) .where("departmentId = ?", departmentId) .getResultList(); return result; } }
部署IDを元に社員レコードを取得しようというこのメソッドが今回のテスト対象。しかし、S2JDBCって知らない人にも、どういう処理をやってるのかが理解しやすいよね。
EmployeeServiceTest.java
public class EmployeeServiceTest extends S2TestCase { private EmployeeService employeeService; public void setUp() { include("test.dicon"); } public void test_findByDepartmentIdTx() { readXlsWriteDb("EmployeeServiceTestInit.xls"); List<Employee> employeeList = employeeService.findByDepartmentId(1); // EmployeeId=1,2のレコードが取得されること assertEquals(2, employeeList.size()); assertEquals(1, employeeList.get(0).employeeId.intValue()); assertEquals(2, employeeList.get(1).employeeId.intValue()); } }
EmployeeServiceのテストクラス。まず、readXlsWriteDb()で、引数に指定されたパスのExcelファイルを読み取ってテーブルに書き込んでいる。その後、テスト対象のメソッドを呼び出し、結果エンティティ(レコード)が正しいことを確認している。カラム全てを確認するのは効率が悪いので、主キーのみ確認している。
そういえば昨日のエントリーでは書き忘れたけど、S2TestCaseを継承したテストクラスでは、メソッドの最後に"Tx"を付けておくとメソッド終了時に自動的にDBをロールバックしてくれる。こうすれば、テーブルを汚さないでテストが行える。”立つ鳥跡を濁さず”ってやつだね。
S2UnitでDB挿入のテスト - DBの内容をExcelに書き出す
Seasar2では、SqlReaderとXlsWriterというクラスが用意されていて、DBのテストが簡単に行えるようになっている。
使い方
重要な部分だけみるとこれだけ。
XlsWriter writer = new XlsWriter("FILE_PATH"); sqlReader.addTable("EMPLOYEE"); sqlReader.addTable("DEPARTMENT"); writer.write(sqlReader.read());
- 出力するExcelファイルのパスを指定してXlsWriterを生成する。
- Excelに出力したいテーブルの名前を、SqlReader.addTable("tableName")で追加。
- SqlReader.read()でDataSetを取得し、それをXlsWriter.write()で書込み。
これはExcelに出力して確認する方法になるので、自動化ではなく目視による確認ということになる。まだ試していないが、SqlReader.read()で取得したDataSetと、テーブルの内容を表現したExcelファイルとの比較(assert)も出来るらしい。テスティングフレームワークでテストを行う一番のメリットはテストの自動化なので、多少手間がかかってもそうすべきかもしれない(このあたりはプロジェクトによりけりだろう)。
今回書いたソース
EmployeeTest(テストクラス)
public class EmployeeTest extends S2TestCase { private static final String TEST_DIR = "c:/aaa/bbb/"; private JdbcManager jdbcManager; private SqlReader reader; public void setUp() { include("test.dicon"); } public void testInsertTx() { Department department = new Department(); department.departmentName = "Dept.A"; jdbcManager.insert(department).execute(); Employee employee = new Employee(); employee.departmentId = department.departmentId; employee.employeeName = "Emp.A"; employee.birthDay = new Date(); jdbcManager.insert(employee).execute(); XlsWriter writer = new XlsWriter(new File(TEST_DIR + "testInsert.xls")); reader.addTable("Department"); reader.addTable("Employee"); writer.write(reader.read()); } }
test.dicon(includeしているdiconファイル)
<components> <include path="app.dicon" /> <component class="org.seasar.extension.dataset.impl.SqlReader" /> </components>
SqlReaderをdiconで定義しているのは、SqlReaderの生成にDataSourceインターフェースが必要なため。DataSourceはjdbc.diconに定義してあるので、こうして単にコンポーネントの定義だけ書いておけば、勝手にインジェクションしてくれる。
Employee(エンティティ)
@Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer employeeId; public Integer departmentId; // public Integer departmentDepartmentId; public String employeeName; @Temporal(TemporalType.DATE) public Date birthDay; @ManyToOne @JoinColumn(name = "DEPARTMENT_ID") public Department department; }
@ManyToOneを指定する場合、自身に外部キーを格納するプロパティ(今回はdepartmentId)を定義しておかなければならないのだが、これはデフォルト(@JoinColumnの指定なし)では、"{テーブル名}_{主キー}"というプロパティとみなされるらしい。今回、部署テーブルの主キーはDEPARTMENT_IDとしている為、デフォルトで動作させようと思ったら、departmentDepartmentIdというプロパティにしなければならないようだ。これを間違えていると以下のようなエラーが出るので注意。
org.seasar.extension.jdbc.exception.ManyToOneFKNotFoundRuntimeException: [ESSR0729]エンティティ(Employees)のプロパティ(departments)が不正です。JoinColumnのnameで指定されている外部キー(DEPARTMENTS_DEPARTMENT_ID)が存在しません。
Department(エンティティ)
@Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer departmentId; public String departmentName; @OneToMany(mappedBy = "department") public List<Employee> employeeList; }
補足
今回は、jdbcManager.insert()をした後、正しくテーブルが更新されているかを確認しているが、実際にこうしたテストはほとんど意味が無い。挿入系で単体テストをやるとしたら、複数のテーブルにレコードを同時に作成するServiceクラスに対するテストなどに限られるだろう。