2017-04-06 7 views
1

この小さなプログラムでは、私はTwitterのツイートストリームを解析しようとしています。私は素晴らしい仕事をするGsonライブラリを使用しています。 Gsonは、Twitterののcreated_at日時フィールドを解析できなかったので、私は次のようにGsonBuilderてパーサに登録する必要がありますカスタムJsonDserializerを書かなければならなかった:Javaで隔離されたカスタムJsonDeserializerをテストする

new GsonBuilder().registerTypeAdatapter(DateTime.class, <myCustomDeserializerType>) 

は今私のデシリアライザはうまく機能し、私はTwitterのストリームを解析することができます。

しかし、私は単体テストで自分のプログラムの多くをカバーしようとしているので、このカスタムデシリアライザを含める必要があります。

良い単体テストはうまく分離されたテストなので、私はGsonオブジェクトに登録したくないのですが、その後json文字列を解析します。私はを実行します。は、自分のデシリアライザのインス​​タンスを作成し、datetimeを表す汎用の文字列を渡すだけで、デシリアライザを他のものと統合することなくテストすることができます。次のように

JsonDeserializerのデシリアライズメソッドのシグネチャは次のとおりです。

deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) 

はの私は、次のデータを解析したいとしましょう:「月3月27日14時09分47秒0000 2017」。デシリアライザを正しくテストするには、入力データをどのように変換する必要がありますか。

この日付を実際に解析するコードは探していません。既にその部分をカバーしています。私はそれはそれはで使用されているGsonでの使用だシミュレートできるように、私はdeserializeメソッドのシグネチャを満たすことができる方法を求めている。

+0

「Gson」インスタンスの一部として間接的にテストしてください。 –

+0

私はこのライブラリを100%信頼する必要があるので、間接的にテストしたくないです(理論的には私はできません)。テストが失敗する可能性がある場合、デシリアライザ、Gsonライブラリ、またはGson内でのデシリアライザの統合のために、ユニットテストが失敗したことを保証することは「不可能」になります。それで、少なくともいくつかのテストでデシリアライザを分離する必要があるような気がするのです。 – Glubus

答えて

3

JsonSerializerJsonDeserializerがしっかりGson JSONツリーモデルにバインドされ、特定Gson設定(デ)シリアライゼーションコンテキストは、(de)シリアライズ可能なタイプのセットを提供します。このため、JsonSerializerJsonDeserializerの単体テストを達成することは容易ではありません。

はどこかsrc/test/resources/.../zoned-date-time.jsonで、次のJSONドキュメントを考えてみましょう:

"Mon Mar 27 14:09:47 +0000 2017" 

をこれは完全に有効なJSON文書であり、それは簡単にするために、単一の文字列の以外は何もしています。以下のようなフォーマットの日付/時刻フォーマッタは、Java 8で実装することができます。

final class CustomPatterns { 

    private CustomPatterns() { 
    } 

    private static final Map<Long, String> dayOfWeek = ImmutableMap.<Long, String>builder() 
      .put(1L, "Mon") 
      .put(2L, "Tue") 
      .put(3L, "Wed") 
      .put(4L, "Thu") 
      .put(5L, "Fri") 
      .put(6L, "Sat") 
      .put(7L, "Sun") 
      .build(); 

    private static final Map<Long, String> monthOfYear = ImmutableMap.<Long, String>builder() 
      .put(1L, "Jan") 
      .put(2L, "Feb") 
      .put(3L, "Mar") 
      .put(4L, "Apr") 
      .put(5L, "May") 
      .put(6L, "Jun") 
      .put(7L, "Jul") 
      .put(8L, "Aug") 
      .put(9L, "Sep") 
      .put(10L, "Oct") 
      .put(11L, "Nov") 
      .put(12L, "Dec") 
      .build(); 

    static final DateTimeFormatter customDateTimeFormatter = new DateTimeFormatterBuilder() 
      .appendText(DAY_OF_WEEK, dayOfWeek) 
      .appendLiteral(' ') 
      .appendText(MONTH_OF_YEAR, monthOfYear) 
      .appendLiteral(' ') 
      .appendValue(DAY_OF_MONTH, 1, 2, NOT_NEGATIVE) 
      .appendLiteral(' ') 
      .appendValue(HOUR_OF_DAY, 2) 
      .appendLiteral(':') 
      .appendValue(MINUTE_OF_HOUR, 2) 
      .appendLiteral(':') 
      .appendValue(SECOND_OF_MINUTE, 2) 
      .appendLiteral(' ') 
      .appendOffset("+HHMM", "+0000") 
      .appendLiteral(' ') 
      .appendValue(YEAR) 
      .toFormatter(); 

} 

ZonedDateTimeための次のようなJSONデシリアライザを考える:私は経由して、文字列をdeserialiazingてる

final class ZonedDateTimeJsonDeserializer 
     implements JsonDeserializer<ZonedDateTime> { 

    private static final JsonDeserializer<ZonedDateTime> zonedDateTimeJsonDeserializer = new ZonedDateTimeJsonDeserializer(); 

    private ZonedDateTimeJsonDeserializer() { 
    } 

    static JsonDeserializer<ZonedDateTime> getZonedDateTimeJsonDeserializer() { 
     return zonedDateTimeJsonDeserializer; 
    } 

    @Override 
    public ZonedDateTime deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) 
      throws JsonParseException { 
     try { 
      final String s = context.deserialize(jsonElement, String.class); 
      return ZonedDateTime.parse(s, customDateTimeFormatter); 
     } catch (final DateTimeParseException ex) { 
      throw new JsonParseException(ex); 
     } 
    } 

} 

注意コンテキスト意図アクセントにもっと複雑なJsonDeserializerインスタンスが重くそれに依存するかもしれません。今度はそれをテストするために、いくつかのJUnitテストを作成してみましょう:各テストはここにいくつかのGsonの設定が必要です

public final class ZonedDateTimeJsonDeserializerTest { 

    private static final TypeToken<ZonedDateTime> zonedDateTimeTypeToken = new TypeToken<ZonedDateTime>() { 
    }; 

    private static final ZonedDateTime expectedZonedDateTime = ZonedDateTime.of(2017, 3, 27, 14, 9, 47, 0, UTC); 

    @Test 
    public void testDeserializeIndirectlyViaAutomaticTypeAdapterBinding() 
      throws IOException { 
     final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer(); 
     final Gson gson = new GsonBuilder() 
       .registerTypeAdapter(ZonedDateTime.class, unit) 
       .create(); 
     try (final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json")) { 
      final ZonedDateTime actualZonedDateTime = gson.fromJson(jsonReader, ZonedDateTime.class); 
      assertThat(actualZonedDateTime, is(expectedZonedDateTime)); 
     } 
    } 

    @Test 
    public void testDeserializeIndirectlyViaManualTypeAdapterBinding() 
      throws IOException { 
     final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer(); 
     final Gson gson = new Gson(); 
     final TypeAdapterFactory typeAdapterFactory = newFactoryWithMatchRawType(zonedDateTimeTypeToken, unit); 
     final TypeAdapter<ZonedDateTime> dateTypeAdapter = typeAdapterFactory.create(gson, zonedDateTimeTypeToken); 
     try (final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json")) { 
      final ZonedDateTime actualZonedDateTime = dateTypeAdapter.read(jsonReader); 
      assertThat(actualZonedDateTime, is(expectedZonedDateTime)); 
     } 
    } 

    @Test 
    public void testDeserializeDirectlyWithMockedContext() 
      throws IOException { 
     final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer(); 
     final JsonDeserializationContext mockContext = mock(JsonDeserializationContext.class); 
     when(mockContext.deserialize(any(JsonElement.class), eq(String.class))).thenAnswer(iom -> { 
      final JsonElement jsonElement = (JsonElement) iom.getArguments()[0]; 
      return jsonElement.getAsJsonPrimitive().getAsString(); 
     }); 
     final JsonParser parser = new JsonParser(); 
     try (final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json")) { 
      final JsonElement jsonElement = parser.parse(jsonReader); 
      final ZonedDateTime actualZonedDateTime = unit.deserialize(jsonElement, ZonedDateTime.class, mockContext); 
      assertThat(actualZonedDateTime, is(expectedZonedDateTime)); 
     } 
     verify(mockContext).deserialize(any(JsonPrimitive.class), eq(String.class)); 
     verifyNoMoreInteractions(mockContext); 
    } 

} 

注デシリアライゼーションのコンテキストの仕事をできるように、または後者は嘲笑されなければならないために建設されます。シンプルなユニットをテストするのはかなり大変です。

GsonのJSONツリーモデルの代わりに、JSONツリー全体を構築する必要がないストリーム指向型アダプタがあります。これにより、JSONストリームから直接読み書きすることができ、直列化の高速化とメモリ消費の削減特に、簡単な文字列のような単純な文字列の場合<==> FooBar変換があります。

final class ZonedDateTimeTypeAdapter 
     extends TypeAdapter<ZonedDateTime> { 

    private static final TypeAdapter<ZonedDateTime> zonedDateTimeTypeAdapter = new ZonedDateTimeTypeAdapter().nullSafe(); 

    private ZonedDateTimeTypeAdapter() { 
    } 

    static TypeAdapter<ZonedDateTime> getZonedDateTimeTypeAdapter() { 
     return zonedDateTimeTypeAdapter; 
    } 

    @Override 
    public void write(final JsonWriter out, final ZonedDateTime zonedDateTime) { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public ZonedDateTime read(final JsonReader in) 
      throws IOException { 
     try { 
      final String s = in.nextString(); 
      return ZonedDateTime.parse(s, customDateTimeFormatter); 
     } catch (final DateTimeParseException ex) { 
      throw new JsonParseException(ex); 
     } 
    } 

} 

そしてここで上記のタイプのアダプターのためのシンプルなユニットテストです:簡単な例については

public final class ZonedDateTimeTypeAdapterTest { 

    private static final ZonedDateTime expectedZonedDateTime = ZonedDateTime.of(2017, 3, 27, 14, 9, 47, 0, UTC); 

    @Test(expected = UnsupportedOperationException.class) 
    public void testWrite() { 
     final TypeAdapter<ZonedDateTime> unit = getZonedDateTimeTypeAdapter(); 
     unit.toJsonTree(expectedZonedDateTime); 
    } 

    @Test 
    public void testRead() 
      throws IOException { 
     final TypeAdapter<ZonedDateTime> unit = getZonedDateTimeTypeAdapter(); 
     try (final Reader reader = getPackageResourceReader(ZonedDateTimeTypeAdapterTest.class, "zoned-date-time.json")) { 
      final ZonedDateTime actualZonedDateTime = unit.fromJson(reader); 
      assertThat(actualZonedDateTime, is(expectedZonedDateTime)); 
     } 
    } 

} 

彼らは実装がやや難しいかもしれしかし、私は間違いなくタイプアダプタで行くだろう。詳細についてはGson unit testsを参照することもできます。

+0

私はあなたの答えが非常に有用であることを知っています、そして、あなたが物事を明確にするために行った長さに感謝します。私はあなたが示した方法でコンテキストを嘲笑しようとしますが、TypeAdaptersも見ていきます。私は明らかに些細なdatetimesより大きなjson構造を解析していますが、同時に彼らは単純なつぶやきであり、これらのTypeAdapterは私が望むように動作するかもしれません。乾杯! – Glubus

関連する問題