2016年1月25日月曜日

[JUnit]ExpectedExceptionを使って例外のアサート

JUnitで提供されるExpectedExceptionを使って例外のアサートをしてみる。

ExpectedExceptionがなかった時代は、try-catchで例外補足してアサートしていたのでかなりの便利機能だと思う。
※@Testアノテーションのexpected属性だと、発生する例外のクラスしかアサートできなかったので、これは使い物にならなかった。

使い方


テストクラスのインスタンス変数で、ExpectedExceptionを宣言する。
ルールであることを示す、@Ruleアノテーションを設定する。
宣言時には、ExpectedException.noneを代入することで、例外が発生しないことを期待するようにしておく。
    @Rule
    public ExpectedException expectedException = ExpectedException.none();

例外をアサートする場合には、例外が発生することを期待するテストケースのメソッドで、アサートする内容をExpectedExceptionに設定する。
このケースだと、NullPointerExceptionが発生してそのメッセージには「error」が保持されていることを期待している。
    @Test
    public void nullPointerException() throws Exception {
        expectedException.expect(NullPointerException.class);
        expectedException.expectMessage("error");
        throw new NullPointerException("error");
    }

元例外(cause)をアサーとしたい場合は、expectCauseにMatcherを登録する。
この例では、causeの例外クラスがNullPointerExceptionであることをアサーとしている。
        expectedException.expect(CustomException.class);
        expectedException.expectCause(instanceOf(NullPointerException.class));
        throw new CustomException(new NullPointerException());

メッセージやcause以外の属性をアサートする場合には、カスタムのMatcherを作ると良い。
例えば、SQLExceptionのベンダーコード(getErrorCode)をアサートする場合には以下のようなMatcherを作る。
    public static class IsSqlErrorCode extends TypeSafeMatcher {

        private final int expectedSqlErrorCode;

        public IsSqlErrorCode(int expectedSqlErrorCode) {
            this.expectedSqlErrorCode = expectedSqlErrorCode;
        }

        public static IsSqlErrorCode isSqlErrorCode(int expectedSqlErrorCode) {
            return new IsSqlErrorCode(expectedSqlErrorCode);
        }

        @Override
        protected boolean matchesSafely(SQLException e) {
            return e.getErrorCode() == expectedSqlErrorCode;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("exception with the error code ")
                       .appendValue(expectedSqlErrorCode);
        }

        @Override
        protected void describeMismatchSafely(SQLException item, Description mismatchDescription) {
            mismatchDescription.appendText("error code was ")
                               .appendValue(item.getErrorCode());
        }
    }

Matcherの使い方は・・・
        expectedException.expect(SQLException.class);
        expectedException.expect(IsSqlErrorCode.isSqlErrorCode(1));
        throw new SQLException("sql error", null, 1);

2016年1月9日土曜日

[NIO2]を使ってZipファイルの読み込みを行う

試してみたのは、郵便番号CSV?をダウンロードしてきて、zip内のcsvデータを標準出力するもの。

NIO2では、zipファイルを仮想ファイルシスムとして扱えるので、Files.walkFileTreeでzip内のファイルだけを対象にした処理を簡単に実装できる。


// zipのダウンロード
String url = "http://www.post.japanpost.jp/zipcode/dl/oogaki/zip/10gumma.zip";
final Path zip = Paths.get("10gumma.zip");
try (InputStream stream = new URL(url).openStream()) {
    Files.copy(stream, zip, StandardCopyOption.REPLACE_EXISTING);
}

// zip内のcsvデータを標準出力に出力
try (FileSystem fileSystem = FileSystems.newFileSystem(zip, null)) {
    Files.walkFileTree(fileSystem.getPath("/"), new SimpleFileVisitor() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            try (BufferedReader reader = Files.newBufferedReader(file, Charset.forName("windows-31j"))) {
                reader.lines().forEach(System.out::println);
            }
            return FileVisitResult.CONTINUE;
        }
    });
}