Help us understand the problem. What is going on with this article?

Serializable実装クラスのテストについて考える [中締め]

More than 3 years have passed since last update.

java.io.Serializable 実装クラスに対してどのようなテストを行うべきなのかを考えるシリーズの中締めです。

これまで3回にわたり、Serializable 実装クラスに対して必要なテストについて実例を交えて見てきました。

今回は中締めとして、これまでの内容をまとめるとともに、さらなる検討課題について明らかにします。

これまでのまとめ

・Serializable を実装したなら、最低限、一度はシリアライズ/デシリアライズしてみる

ケース3で見たように、単に Serializable を implements しただけでは、オブジェクトがシリアライズ/デシリアライズ可能にならない場合があります。
このような実装不備は、オブジェクトを実際にシリアライズ/デシリアライズしてみることで比較的簡単に見つけることができます。

assertThat(TestUtil.writeAndRead(new OthelloBoard()), instanceOf(OthelloBoard.class));
assertThat(TestUtil.writeAndRead(new Odd(7)).get(), is(7));

・デシリアライズ時のインスタンス制御について確認する

ケース1で見たように、デシリアライズによるオブジェクト生成はコンストラクタを迂回します。
シングルトンやそれに類するインスタンス制御を行っているクラスについては、デシリアライズ時に意図したインスタンス制御が行われることを確認する必要があります。

assertThat(TestUtil.writeAndRead(OnlyOne.getInstance()), sameInstance(OnlyOne.getInstance()));
assertThat(TestUtil.writeAndRead(Point.of(1, 2)), sameInstance(Point.of(1, 2)));

・不正に改竄されたバイト配列からの防御をテストする

ケース2で見たように、シリアライズされたバイト配列を読み解いて改竄するのは比較的簡単であり、そのような不正に備えるのは Serializable 実装クラスの責任です。
実際に不正に改竄したバイト配列を与えてデシリアライズし、意図した防御がなされるかを確認するべきです。

// test1
Function<byte[], byte[]> modifier1 = original -> {
    byte[] modified = Arrays.copyOf(original, original.length);
    modified[original.length - 1] = 0x02;
    return modified;
};
assertThat(Testee.of(() -> TestUtil.writeModifyAndRead(new Odd(7), modifier1)),
        raise(FailToDeserializeException.class)
        .rootCause(InvalidObjectException.class), "illegal value. n == 2");

// test2
Function<byte[], byte[]> modifier2 = original -> {
    return TestUtil.replace(original,
            TestUtil.bytes(MyClass.class.getName() + "$SerializationProxy"),
            TestUtil.bytes(MyClass.class.getName()));
};
assertThat(Testee.of(() -> TestUtil.writeModifyAndRead(new MyClass(), modifier2)),
        raise(FailToDeserializeException.class)
        .rootCause(InvalidObjectException.class, "proxy required"));

なお、上記のコード例で使用した Testee.of, raise, rootCause は、以前の投稿『JUnit4での例外テストを楽チンにする!』で紹介したライブラリの機能です。
TestUtil は今回作成したものであり、別稿で取り上げる予定です。⇒ 投稿しました:『JUnit4でのシリアライズ/デシリアライズ検証を楽チンにする!』

さらなる検討課題

・異なるバージョン間の互換性の検証

古いバージョンで保存したファイルを、新しいバージョンで読み込んだら?
ネットワーク越しの送信先で、古いバージョンのクラスが動いていたら?
異なるバージョン間の互換/非互換を適切に制御することは、Serializable 実装にまつわる大きなテーマのひとつです。

これを JUnit でテストしようとした場合、クラスローダを駆使して異なるバージョンのクラスを同一 JVM 上にロードする、なんてことになるのでしょうか。
現在の私の知識では太刀打ちできないため、このテーマについては将来に譲ることにします。

・セキュリティに関する考慮

ケース2で見たように、シリアライズされたバイト配列では private なフィールドの内容も丸見えです。従って、センシティブなデータはシリアライズ対象外とするか、暗号化してシリアライズする必要があります。
アプリケーションの内容によっては、このような考慮と検証が必要になるのでしょう。

 ~ ~ ~

ここまで、nmby の拙い知識に基づき Serializable 実装クラスに対して行うべきテストについて整理してきました。
nmby はお仕事で Serializable 実装クラスを扱うことが殆ど無いのですが、ソフトウェアを一般販売しているような企業では、きっと専門的な知見が蓄積され、方法論が確立されているのでしょうね。
専門的な知見を持つ皆さまによる間違いの指摘やアドバイス、ツッコミなどをお待ちしております。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした