ドメインのテストと実装

ドメインのテストと実装を作成します。ここでもデータアクセスオブジェクトと同様、 テストファースト による開発プロセスを採用しています。

テストケース

ドメインに作成した BlogBlogEntryBlogTableBlogEntryTable のテストケースを作成します。複雑な検証ロジックがある場合は検証クラス BlogValidator のテストケースも作成します。ここでは BlogTestBlogTableTest を紹介し ます。完全なソースは BlogTest のソースコードBlogEntryTest のソースコードBlogTableTest のソースコードBlogEntryTableTest のソースコード を参照ください。

BlogTest

BlogTest では、post および store メソッドに対する テストケースを紹介します。store メソッドでは、登録に成功、登録に失敗、更新に成功、更新に 失敗の4ケースをテストする必要があります。

public class BlogTest extends AbstractTransactionalTestsWithInitialData {

    @Autowired BlogTable blogTable;

    @Test
    public void testStore登録() throws BlogCodeUsedByOtherException {
        Blog blog = createBlog();
        blog.store();
        Blog actual = blogTable.find(blog.getId());
        Assert.assertEquals(blog, actual);
    }

    @Test(expected=BlogCodeUsedByOtherException.class)
    public void testStore登録を試みるがコードが重複()
        throws BlogCodeUsedByOtherException {
        Blog blog = createBlog();
        blog.store();
        Blog blog2 = blogTable.prototype(blog);
        blog2.setId(null);
        blog2.store();
    }

    @Test
    public void testStore更新() throws BlogCodeUsedByOtherException {
        Blog blog = createBlog();
        blog.store();
        blog.setCode("new_" + blog.getCode());
        blog.getReadAccessControl().changeTo(AccessControlType.FRIEND_ONLY);
        blog.store();
        Blog actual = blogTable.find(blog.getId());
        Assert.assertEquals(blog, actual);
    }

    @Test(expected=BlogCodeUsedByOtherException.class)
    public void testStore更新を試みるがコードが重複()
        throws BlogCodeUsedByOtherException {
        Blog blog = createBlog();
        blog.store();
        Blog blog2 = blogTable.prototype(blog);
        blog2.setId(null);
        blog2.setCode(blog2.getCode() + "2");
        blog2.store();
        blog2.setCode(blog.getCode());
        blog2.store();
    }

    @Test
    public void testPost() throws BlogCodeUsedByOtherException {
        Blog blog = createBlog();
        blog.store();
        BlogEntry blogEntry = createBlogEntry(blog.getReadAccessControl(),
                blog.getWriteAccessControl());

        blog.retrieveBlogEntries(0, 1);
        Assert.assertSame(0, blog.getBlogEntries().size());
        blog.post(blogEntry);
        blog.retrieveBlogEntries(0, 1);
        Assert.assertSame(1, blog.getBlogEntries().size());
        Assert.assertEquals(blogEntry, blog.getBlogEntries().get(0));
    }

    // other test methods

    private Blog createBlog() {
        // テスト用のブログを生成
    }

    private BlogEntry createBlogEntry(AccessControl readAccessControl,
            AccessControl writeAccessControl) {
        // テスト用のブログ記事を生成
    }
}

BlogTableTest

BlogTableSimpleTableModule を継承したクラスであるため、それに対応する BlogTableTestTableModuleTestCaseSupport を継承することで基本的なテストケースを再利用できます。BlogTable は独自メソッド findByCode を持っていますが、このメソッドは単にデータアクセスオブジェクトに 処理を委譲しているだけですので、テストケースは作成していません。

public class BlogTableTest extends TableModuleTestCaseSupport<Blog> {

    @Autowired private BlogTable blogTable;

    @Override
    protected Blog createDomain() {
        // テスト用のブログを生成
    }

    @Override
    protected SimpleDomainsSupport<Blog> getTableModule() {
        return blogTable;
    }

    @Override
    protected SimpleFindable<Blog> getSimpleDao() {
        return blogTable.getSimpleDao();
    }
}

ドメインの実装

ドメイン設計時に実装を保留した箇所を実装します。全てのテストに合格すれば、実装完了です。ドメインの完全なソー スコードは Blog のソースコードBlogEntry のソースコードBlogTable のソースコードBlogEntryTable のソースコード を参照ください。

Blog

Blogstore メソッドでは、コードが重複しないことを確認してから永続化処理を行っています。また、 delete メソッドでは、関連するブログ記事が存在しないことを確認してから削除処理を行っていま す。

public class Blog ... {

    // ...

    @Override
    public void store() throws BlogCodeUsedByOtherException {
        Blog foundByCode = blogDao.findByCode(code);
        if (foundByCode != null && !foundByCode.identify(this)) {
            String message = "The code '" + code
                + "' has already been used by other.";
            logger.warn(message);
            throw new BlogCodeUsedByOtherException(message);
        }
        if (this.id == null) {
            blogDao.register(this);
        } else {
            blogDao.update(this);
        }
    }

    @Override
    public void delete() throws BlogEntryExistException {
        int count = blogDao.countBlogEntry(id);
        if (count > 0) {
            String message = "The blog [ID=" + id + "] has " + count
                + " entries.";
            logger.warn(message);
            throw new BlogEntryExistException(message);
        }
        blogDao.delete(id);
    }

    public void post(BlogEntry blogEntry) {
        blogEntry.store();
        blogDao.registerBlogEntry(id, blogEntry.getId());
    }

    public void retrieveEntries(int no, int num) {
        entries = blogDao.findBlogEntries(id, no, num);
    }

    // ...
}

BlogEntry

BlogEntrydelete メソッドでは、関連する全てのコメントを削除しています。

public class BlogEntry ... {

    // ...

    @Override
    public void store() {
        try {
            entry.store();
        } catch (MessageCodeUsedByOtherException e) {
            // 起こらない。記事のコードは自動生成される。
            logger.error(e);
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void delete() {
        entry.delete();
    }

    public void retrieveComments() {
        comments = blogDao.findComments(getId());
    }

    public void post(Comment comment) {
        comment.store();
        blogDao.registerComment(getId(), comment.getId());
    }

    // ...
}

Bean 定義

最後に applicationContext-dao.xml および applicationContext-domain.xml に作成したデータアクセスオブジェクトおよびドメインクラスを記載して完了です。未定義の BlogValidatorapplicationContext-domain.xml に追記します。

<bean id="communication.blogValidator"
    class="org.unitedfront2.domain.communication.BlogValidator">
  <property name="blogTable" ref="communication.blogTable"/>
</bean>