Jak testować cache'owanie danych?

0

Cześć, tak jak w tytule, ogarnąłem jak cache'ować dane ale nie wiem jak sprawdzić czy poprawnie to wykonuje, ma ktoś może jakiś pomysł jak sprawdzić czy cache się na pewno wykonał?

1

Microbenchmark. Uderzasz milion razy i sprawdzasz czasy. Jak będą i tak i tak niskie to znaczy, że cache niepotrzebny :)

2

Cacheowanie gdzie? W przeglądarce? Przed bazą? Co to znaczy poprawne cacheowanie? Chodzi o zapis danych, o konfigurację TTL, ...? :)

3

Będę tutaj zgadywać: masz jakąś warstwę cache nad jakąś bazą danych i chcesz napisać testy, które weryfikują czy dane faktycznie lecą z cache?

  1. Wykonujesz request ktróry powinien wrzucić dane do bazy
  2. Psujesz bazę (składasz ją w teście, albo podmieniasz connection na coś innego), podmieniasz połączenie do serwisu z którego normalnie pobierasz dane etc. Robisz coś, co sprawia że nie da się już dostać danych "normalną drogą".
  3. Wykonujesz kolejny request, który może zadziałać tylko jeśli dane przyjdą z cache
0

Nie wiem co za cache masz, ale niektóre cache udostępniają API dające możliwość podpięcia własnego listenera. W takim listenerze możesz obsługiwać zdarzenia typu "entryAdded/Expired/Removed/cacheHit/cachMiss/etc.". Twój listener mógłby zbierać statystyki i pokazywać ile razy miałeś cache hit/miss etc.

Żeby nie być gołosłownym: https://www.ehcache.org/apidocs/2.8.4/net/sf/ehcache/statistics/CacheUsageListener.html

2

Używam mutowania (w testach) i sprawdzam jak często serwis pod spodem jest wywołany - normalnie jak najgorszy mock :-)
https://github.com/neeffect/nee/blob/master/nee-core/src/test/kotlin/pl/setblack/nee/effects/tx/CacheEffectTest.kt
Widać, że jakbym tu użył Mockito, to mógłbym zaoszczędzić jedna linijke kodu :/. A do tego są copy-paste - review nie było.

Czyli - niestety ten case, gdzie poświęcam ideały w imię testowania. Cache jest generalnie przezroczysty z punktu widzenia wartości, czy działa, czy nie działa - funkcja daje ten sam wynik...
i nie widzę sensownego innego sposobu testowania jak na razie. Choć możliwe, że jakiś alternatywny istnieje -pomysł @Shalom jest inny, ale ma ten sam problem z punktu widzenia czystości, jednakowoż chyba widze jak to w jego wersji poprawić... (przyjdzie Reader i wyrówna).

2

Przykład jak gdzieś coś podobnego testowałem:

        // given
        testConfiguration = testHelper.createNewConfiguration()
                .withRandomUser()
                .withRandomFile()
                .grantUserAccessToFiles()
                .setup();

        //when
        Either<RestRequestFailure, ArchiveFiles> result = client.getAccessibleFiles(testConfiguration.getUser().getId(), testConfiguration.getFileIds());
        assertTrue(result.isRight());
        assertFalse(result.get().getFiles().isEmpty());
        testHelper.withDBFailure(() -> {
            Either<RestRequestFailure, ArchiveFiles> cachedResult = client.getAccessibleFiles(testConfiguration.getUser().getId(), testConfiguration.getFileIds());
            // then
            assertTrue(cachedResult.isRight());
            assertFalse(cachedResult.get().getFiles().isEmpty());
        });

A samo failure symulowane przez takiego potworka:

    public void withDBFailure(Runnable testCode) {
        JdbcTemplate fakeJdbc = mock(JdbcTemplate.class);
        Whitebox.setInternalState(fileAccessRepository, JdbcTemplate.class, fakeJdbc);
        when(fakeJdbc.query(anyString(), any(Object[].class), any(RowMapper.class))).thenThrow(new RuntimeException());
        testCode.run();
        Whitebox.setInternalState(fileAccessRepository, JdbcTemplate.class, bookkepingJdbc);
    }

Może sama implementacja nie jest jakoś strasznie "czysta", ale za to sam test jest dość czytelny. Mamy takie closure w którym operacje na bazie failują i możemy sobie w nim sprawdzać zachowanie kiedy baza leży.

0

Cache w API

strzelasz do endpointa i jeżeli response przyjdzie w < 10ms to przyszedł z cache, a jak nie to poszło do prawdziwej bazki

nie ma lepszego testu :D
i faktycznie testuje aplikacje, a nie 10 metrów kodu do fakeowania

a jak chcesz uniknąć false positive to niech bazka będzie wypełniona danymi, wtedy różnica pomiędzy cache i bez będzie coraz większa

0
WeiXiao napisał(a):

strzelasz do endpointa i jeżeli response przyjdzie w < 10ms to przyszedł z cache, a jak nie to poszło do prawdziwej bazki

Ale można też cechować przecież niżej niż cały response i może trwać on więcej niż 10ms

1

Sztuka dla sztuki. Zainspirowany podejściem @Shalom
zmieniłem mój test cache, tak żeby mniej używać mutacji
https://github.com/jarekratajski/nee/blob/develop/nee-core/src/test/kotlin/pl/setblack/nee/effects/cache/CacheEffectTest.kt

internal class CacheEffectTest : BehaviorSpec({
    Given("cache effect and naive implementation") {
        val cacheProvider = NaiveCacheProvider()
        val cache = CacheEffect<Env, Nothing>(cacheProvider)
        When("function called twice using same param and different env") {
            val businessFunction =
                Nee.pure(
                    cache, ::returnEnvIgnoringParam)

            val x1 = businessFunction.perform(env = Env.SomeValue)(1)
            val x2 = businessFunction.perform(env = Env.OtherValue)(1)
            Then("second call should ignore different env") {
                x2.get() shouldBe x1.get()
            }
            Then("second call should return first stored value") {
                x2.get() shouldBe Env.SomeValue
            }
        }
        When("function called twice using different params and env") {
            val businessFunction =
                Nee.pure(
                    cache, ::returnEnvIgnoringParam)

            val x2 = businessFunction.perform(env = Env.SomeValue)(1).flatMap { _ ->
                businessFunction.perform(env = Env.OtherValue)(2)
            }
            Then("second call should return other env value") {
                x2.get() shouldBe Env.OtherValue
            }
        }
    }
})

fun returnEnvIgnoringParam(env:Env) = { _:Int -> env}

sealed class Env {
    object SomeValue : Env()
    object OtherValue: Env()
}

Nadal sam cache (testowy) polega na concurrenthashMap :-(, więc pięknie nie jest. Dla zadających trudne pytania: To nie jest test Cache, ale test efektu/ aspektu cache. Gdzie Implementacja defaultowa to Caffeine.
Przyjąłem, że Caffeine ktoś już potestował....

Da się zrobić to (efekt i testy) zupełnie czysto, ale wtedy zrobiłbym prawdopodobnie bardzo czysty, funkcyjny i zupełnie bezużyteczny cache - w Kotlinie nie będe się w to bawił.

1 użytkowników online, w tym zalogowanych: 0, gości: 1