2017-06-21 11 views
0

私が直面している問題は、同じキーでassertSameテストに失敗した2つのオブジェクトがキャッシュ可能なメソッドから返されたことです。これらのオブジェクトが同じストレージ領域を共有していないのはなぜですか?キャッシュキャッシング可能なキャッシュされたオブジェクトが等価性チェックに失敗する

詳細: 私はredisキャッシュメカニズムを使用して、スプリングブートREST APIでキャッシュを実装しています。 キャッシュは、外部から提供されたソース(データベースにアクセスするJPSリポジトリ)からデータを最初に取得し、同じキャッシュキーを後で呼び出すとキャッシュからデータを返すように正しく動作します。しかし、JUnitのテストケースでは、この動作を完全に模倣することはできません。私のassertEqualsまたはassertSameは、キャッシュから返された2つのオブジェクトで失敗します。

は私のコードベースは、以下のようになります。 MVN依存関係:

<dependency> 
    <groupId>org.springframework.data</groupId> 
    <artifactId>spring-data-redis</artifactId> 
    <version>1.7.6.RELEASE</version> 
</dependency> 
<dependency> 
    <groupId>redis.clients</groupId> 
    <artifactId>jedis</artifactId> 
    <version>2.9.0</version> 
</dependency> 

Springアプリケーションの設定:

@SpringBootApplication 
@EnableCaching 
public class Application { 
@Value("${redis.host}") 
private String redisHost; 

public static void main(String[] args) { 
    SpringApplication.run(Application.class, args); 
} 

@Bean 
JedisConnectionFactory jedisConnectionFactory() { 
    JedisConnectionFactory jedisConFactory = new JedisConnectionFactory(); 
    jedisConFactory.setHostName(redisHost); 
    jedisConFactory.setPort(6379); 
    return jedisConFactory; 
} 

@Bean 
public RedisTemplate<String, Object> redisTemplate() { 
    RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); 
    template.setConnectionFactory(jedisConnectionFactory()); 

    return template; 
} 

@Bean 
CacheManager cacheManager() { 
    return new RedisCacheManager(redisTemplate()); 
} 

サービスクラス:

@Service 
public class CIDomainService { 
private RedisTemplate<String, Object> redisTemplate; 
private CIDomainDAO ciDomainDAO; 

@Autowired 
public CIDomainService(CIDomainDAO ciDomainDAO, RedisTemplate<String, Object> redisTemplate) { 
    this.ciDomainDAO = ciDomainDAO; 
    this.redisTemplate = redisTemplate; 
} 

@Cacheable(value = "ciDomain", key = "#id") 
public CIDomain getCIDomain(int id) { 
    CIDomain ciDomain = new CIDomain(); 
    ciDomain.setId(id); 
    ciDomain.setName("SomeName"); 
    return ciDomain; 
} 


public void clearAllCache() { 
    redisTemplate.delete("listCIDomains"); 
    redisTemplate.delete("ciDomain"); 
} 

}

上記のサービス内のciDomainDAOは、findAll()メソッドを使用して外部データベースまたはインメモリデータベースからデータを取得するJPSリポジトリインタフェースです。私のテストクラス:

@RunWith(SpringJUnit4ClassRunner.class) 
@ActiveProfiles("local") 
@SpringBootTest 
public class CIDomainServiceIntegrationTest { 

@Autowired 
CIDomainService ciDomainServiceSpy; 

@Before 
public void setUp(){ 
    ciDomainServiceSpy.clearAllCache(); 
} 

@Test 
public void listCIDomains_ShouldRetrieveCIDomainsWithCachingPluggedIn() { 
    CIDomain domain1 = ciDomainServiceSpy.getCIDomain(1); 
    CIDomain domain2 = ciDomainServiceSpy.getCIDomain(2); 
    CIDomain domain3 = ciDomainServiceSpy.getCIDomain(1); 
    assertSame(domain1, domain3); //fails 
} 

マイドメインクラス:

私は、オブジェクトが非常に最初の呼び出しのためのリポジトリから取得され、後に通話が提供キャッシュからこのオブジェクトを取得することを理解し this postに基づいて
@Entity 
@Table(name = "CI_DOMAIN") 
public class CIDomain implements Serializable{ 
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
@Column(name = "id") 
private int id; 

@Column(name = "name") 
private String name; 

public int getId() { 
    return id; 
} 

public void setId(int id) { 
    this.id = id; 
} 

public String getName() { 
    return name; 
} 

public void setName(String name) { 
    this.name = name; 
} 

同じ「キー」が提供される。私は上記のテストケースで同じことをしていますが、assertSameは失敗しています。キャッシング可能なSpringは、指定された要求に対してフェッチされるメモリ内のオブジェクトをキャッシュしている必要があります。同じリクエストされたキーのたびに異なるオブジェクトを毎回送信するのはなぜですか?

私は、サービスクラスでスパイを使用し、同じキーリクエストに基づいてメソッド呼び出しを検証する代わりのソリューションを用意しようとしました。しかし、私はそれを行う際に別の問題に遭遇しました。サービスクラスでスパイを作成すると、キャッシュメカニズムも使用されず、同じキーが提供されていてもサービスgetCIDomainメソッドを呼び出します。私はthisthisthisthisthis、さらに詳しい分析のために多くの他の投稿を続けましたが、スパイのassertSameのどちらかを使って動作させることができませんでした。

本当にありがとうございます。

+0

それらが同じである場合は、チェックしている( 'assertSame'(、そうではありません、彼らが必要。後者を渡すには、あなたのドメインオブジェクトに 'equals'メソッドと' hashCode'メソッドが必要です。 –

+0

@MeDeinumあなたの返事をありがとうございます。その理由は、あなたのコメントは、シリアライズされ、デシリアライズされた "同じ"(等しいパス)のjavaオブジェクトh 2つの異なる参照またはID、したがってobject1 == object2はfalseとなります。しかし、assertSameの代わりにequalsを使用すると、「私はassertSameを使用する必要があります。そうでなければ、コードは表示されていない.equals(Object)に依存していると考えられます。すべてのプロパティを比較し、キャッシュされていない場合でもこのテストは常に成功します。 – Vishal

+0

と同じではありません。 assertSameは 'domain1 == domain3'を実行しますが、これは別のオブジェクトインスタンスであるため、そうではありません。あなたは本当に 'equals.'を使用するべきであり、何か他のものではありません。前述したように、自分では(Redisの使用による)キャッシュはオブジェクトをシリアライズしてシリアライズするため、決して同じではありません。 java直列化を使用しても、オブジェクトは決して同じではありません。あなたが本当にオブジェクトの平等をチェックしなければならないことを信じてください。 –

答えて

0

私はこの問題を解決し、Springキャッシャブルメカニズムを検証するためのテストケースを設計することができました。

この同じ問題に直面している人を助けるために、以下の分析と解決策を提示するだけです。

上記の私のコメントと元々の質問では、シリアライゼーションの動作とassertEqualsのためにassertSameは機能しませんでしたが、動作していましたが、テスト要件を満たしていませんでした。

結論私は(コメントに基づいて)私は実際にメソッド呼び出しの数をテストし、その結果をテストすべきではないことを立てました。私は私の質問と同じようにCIDomainDAOリポジトリDAOを模倣しようとしましたが、私はカップルの問題に直面しました。 CIDomainDAOの疑似オブジェクトを作成し、それをCIDomainServiceコンストラクタに渡すと、スプリングキャッシュがトリガされず、テストが失敗しました。私はCIDomainDAOを模擬し、メソッド呼び出しの何をチェックするためにCIDomainServiceにスパイを試していないし、私のテストを実行しない場合、私は、これはモックはそのCIDomainDAOがかもしれない最終的な方法で動作するようには思えないよう明らかだった

org.mockito.exceptions.misusing.UnfinishedVerificationException: Missing 
method call for verify(mock). 

を得て終わるました春にJPARepository実装を生成しました。 This投稿は私がmockitoのこの動作を理解するのを助けました。

私はCIDomainDAOを何とか模倣する必要があると結論づけて、CIDomainDAOレポジトリの模擬バージョンをCIDomainServiceクラスに注入しました。私はCIDomainDAOセッターをCIDomainServiceクラスに定義する必要がありました。その後、私はメソッド呼び出しを試しておらず、期待通りに機能しました。つまり、サービスは2回呼び出されましたが、2回目の呼び出しでキャッシュからデータが返されたときにCIDomainDAOが一度呼び出されました。

上記の私のオリジナルの質問の修正されたクラスの下に提供されています。

サービスクラス:

@Service 
public class CIDomainService { 
    private RedisTemplate<String, Object> redisTemplate; 
    private CIDomainDAO ciDomainDAO; 

    @Autowired 
    public CIDomainService(CIDomainDAO ciDomainDAO, RedisTemplate<String, 
    Object> redisTemplate) { 
     this.ciDomainDAO = ciDomainDAO; 
     this.redisTemplate = redisTemplate; 
    } 

    @Cacheable(value = "ciDomain", key = "#id") 
    public CIDomain getCIDomain(int id) { 
     CIDomain ciDomain = new CIDomain(); 
     ciDomain.setId(id); 
     ciDomain.setName("SomeName"); 
     return ciDomain; 
    } 


    public void clearAllCache() { 
     redisTemplate.delete("listCIDomains"); 
     redisTemplate.delete("ciDomain"); 
    } 

    public void setCIDomainDAO(CIDomainDAO ciDomainDAO) { 
     this.ciDomainDAO = ciDomainDAO; 
    } 
} 

そして、これで更新されたテストケース:

@RunWith(SpringJUnit4ClassRunner.class) 
@ActiveProfiles("local") 
@SpringBootTest 
public class CIDomainServiceIntegrationTest { 

    @Autowired 
    @InjectMocks 
    CIDomainService ciDomainService; 

    @Mock 
    CIDomainDAO ciDomainDAO; 

    @Before 
    public void setUp() { 
     Mockito.reset(ciDomainDAO); 
     ciDomainService.clearAllCache(); 
    } 


    @Test 
    public void listCIDomains_ShouldNotAttemptToCallRepositoryWhenCachingEnabledAfterFirstCallOfRetrievingCIDomains() { 
     List<CIDomain> domains1 = ciDomainService.listCIDomains(); 
     List<CIDomain> domains2 = ciDomainService.listCIDomains(); 
     Mockito.verify(ciDomainDAO, Mockito.times(1)).findAll(); 
    } 

    @Test 
    public void listCIDomains_ShouldAttemptToCallRepositoryWhenCachingIsClearedAfterFirstCallOfRetrievingCIDomains() { 
    List<CIDomain> domains1 = ciDomainService.listCIDomains(); 
     ciDomainService.clearAllCache(); 
     List<CIDomain> domains2 = ciDomainService.listCIDomains(); 
     Mockito.verify(ciDomainDAO, Mockito.times(2)).findAll(); 
    } 

    @After 
    public void postSetUp() { 
     Mockito.validateMockitoUsage(); 
     ciDomainService.clearAllCache(); 
     Mockito.reset(ciDomainDAO); 
     } 
    } 
関連する問題