Dawno, dawno temu, jak pojawił się JUnit5, to popełniłem cykl tekstów o tym narzędziu. W jednym z
nich opisałem czym są założenia, Assumptions. w tej chwili pracując z Quarkusem i mavenem naciąłem się na
bardzo interesujący „błąd”. Nie jest to oczywiście błąd-błąd, ale może prowadzić do dość ciekawych „wyników” w czasie testów.
Czym są założenia – w skrócie
Założenia, to mechanizm podobny do asercji, ale przeznaczony do weryfikowania stanu PRZED testem. Dzięki temu możemy wychwycić nieprawidłowy stan,
który wpływa na testy np. niepoprawny stan testowanego systemu, który pozostał po poprzednich testach.
Bywa to szczególnie przydane, gdy pracujemy z testami na pograniczu jednostkowych i integracyjnych. Możemy na przykład zweryfikować, czy baza danych
nie została zanieczyszczona przez inne testy. W ogólności nieudane sprawdzenie założenia powinno skutkować przerwaniem testu i oznaczeniem go jako
czerwonego.
Jak to działa w Mavenie?
Ano nie tak jak się spodziewamy. Mamy taki oto serwis
Listing 1. Przykładowy serwis
@ApplicationScoped
class
SimpleService
{
private
final
AtomicInteger
counter
=
new
AtomicInteger(0);
public
int
inc()
{
return
counter.incrementAndGet();
}
public
int
get()
{
return
counter.get();
}
}
Oraz dwa testy, które współdzielą stan, co jest oczywiście niepożądane:
Listing 2. Przykładowy test
import
io.quarkus.test.junit.QuarkusTest;
import
jakarta.inject.Inject;
import
org.assertj.core.api.Assertions;
import
org.assertj.core.api.Assumptions;
import
org.junit.jupiter.api.Test;
@QuarkusTest
class
SimpleServiceTest
{
@Inject
SimpleService
simpleService;
@Test
void
test1()
{
Assumptions.assumeThat(simpleService.get()).isEqualTo(0);
Assertions.assertThat(simpleService.inc()).isEqualTo(1);
}
@Test
void
test2()
{
Assumptions.assumeThat(simpleService.get()).isEqualTo(0);
Assertions.assertThat(simpleService.inc()).isEqualTo(1);
}
}
Jeżeli test ten uruchomimy w IDE, to oczywiście jeden nie zostanie wykonany. Dostaniemy błąd:
Listing 3. I jego wynik
org.opentest4j.TestAbortedException:
assumption
was
not
met
due
to:
expected:0
but
was:1
at
java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
at
java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
at
java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
at
org.assertj.core.api.AssumptionExceptionFactory.buildAssumptionException(AssumptionExceptionFactory.java:49)
at
org.assertj.core.api.AssumptionExceptionFactory.assumptionNotMet(AssumptionExceptionFactory.java:32)
at
org.assertj.core.api.Assumptions$AssumptionMethodInterceptor.intercept(Assumptions.java:126)
at
org.assertj.core.api.IntegerAssert$ByteBuddy$Uaxa0C94.isEqualTo(Unknown
Source)
at
org.assertj.core.api.IntegerAssert$ByteBuddy$Uaxa0C94.isEqualTo(Unknown
Source)
at
org.acme.SimpleServiceTest.test2(SimpleServiceTest.java:23)
at
java.base/java.lang.reflect.Method.invoke(Method.java:580)
at
io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:973)
at
io.quarkus.test.junit.QuarkusTestExtension.interceptTestMethod(QuarkusTestExtension.java:823)
at
java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
at
java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
Caused
by:org.opentest4j.AssertionFailedError:
expected:0
but
was:1
at
java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
at
java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
...6more
2024-08-06
14:28:32,386INFO
[io.quarkus](main)q-assume
stopped
in
0.004s
Process
finished
with
exit
code
255
A teraz po uruchomieniu z linii poleceń:
Listing 4. I jego wynik
$ mvn clean verify
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------< org.acme:q-assume >--------------------------
[INFO] Building q-assume 1.0.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ q-assume ---
[INFO] Deleting /home/koziolek/workspace/blog/blog/q-assume/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ q-assume ---
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- quarkus:3.13.0:generate-code (default) @ q-assume ---
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ q-assume ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 2 source files with javac [debug release 21] to target/classes
[INFO]
[INFO] --- quarkus:3.13.0:generate-code-tests (default) @ q-assume ---
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ q-assume ---
[INFO] skip non existing resourceDirectory /home/koziolek/workspace/blog/blog/q-assume/src/test/resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ q-assume ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 2 source files with javac [debug release 21] to target/test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ q-assume ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.SimpleServiceTest
2024-08-06 14:25:15,238 INFO [io.quarkus] (main) q-assume 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.13.0) started in 0.953s.
2024-08-06 14:25:15,241 INFO [io.quarkus] (main) Profile test activated.
2024-08-06 14:25:15,241 INFO [io.quarkus] (main) Installed features: [cdi, picocli]
[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 2.137 s --
in org.acme.SimpleServiceTest
2024-08-06 14:25:15,314 INFO [io.quarkus] (main) q-assume stopped in 0.005s
[INFO]
[INFO] Results:
[INFO]
[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1
[INFO]
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ q-assume ---
[INFO] Building jar: /home/koziolek/workspace/blog/blog/q-assume/target/q-assume-1.0.0-SNAPSHOT.jar
[INFO]
[INFO] --- quarkus:3.13.0:build (default) @ q-assume ---
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 678ms
[INFO]
[INFO] --- failsafe:3.2.5:integration-test (default) @ q-assume ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- failsafe:3.2.5:verify (default) @ q-assume ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.969 s
[INFO] Finished at: 2024-08-06T14:25:16+02:00
[INFO] ------------------------------------------------------------------------
Ups… zielono mi :D
Założenia nie działają?
No nie do końca. Założenia działają trochę inaczej niż intuicyjny opis z początku tekstu.
Z dokumentacji:
In direct contrast to failed assertions, failed assumptions do not result in a test failure; rather, a failed assumption results in a test being
aborted.
Czyli zachowanie w mavenie jest OK. IDE nie do końca jest OK, choć czerwony stacktrace po wykonaniu testu odpowiednio podnosi ciśnienie kawy w krwii i
pozwala na zauważenie problemu. Zielone testy zwykle ignorujemy. W końcu są zielone.
Jak się przed tym bronić?
Na krótszą metę można spróbować uruchamiać test i śledzić czy pokrycie rośnie. Na dłuższą należy przyjąć, iż wszystkie testy powinny się wykonać i za
pomocą odpowiednich reguł ArchUnita ograniczyć czy to użycie @Disable, czy to Assumptions. o ile chcesz sprawdzić środowisko przed uruchomieniem
testu, to użyj asercji. Może nie będzie to zgrabne, ale będzie skuteczne.
Podsumowanie
Dlaczego o tym piszę? Jak wspomniałem na początku, ostatnio dużo pracuję z Quarkusem. Zmienił się mój model pracy. Dotychczas testy puszczałem
bezpośrednio z IDE, a od pewnego czasu przerzuciłem się na automatyczne śledzenie zmian i uruchamianie testów z quarkusowego pluginu. Przy dużej
ilości testów jeden czy dwa, które nie zostały wykonane, mogą umknąć uwadze i mamy problem. Zatem jest to wpis z serii „uczmy się na błędach,
najlepiej na cudzych”.