Testen von @ NonNull / @ Nullable-Annotationen

Anstelle von "Widmet sich ..."


Die unten beschriebene Aufgabe war nicht innovativ oder verdammt nützlich. Das Unternehmen, in dem ich arbeite, wird keinen Gewinn dafür erhalten, aber ich werde ein Bonus sein.

Aber diese Aufgabe war, und deshalb musste sie gelöst werden.

Intro


In dem Artikel werden Sie oft das Wort Lombok treffen, ich bitte die Hasser, nicht zu Schlussfolgerungen zu eilen.
Ich werde nicht für Lombok oder seine Abwesenheit "ertrinken", ich versuche, wie Geralt Sapkovsky, neutral zu sein, und ich kann Code mit oder ohne Lombok ruhig und ohne Zittern im Jahrhundert lesen.

Aber beim aktuellen Projekt ist die erwähnte Bibliothek vorhanden, und etwas sagt mir, dass unser Projekt nicht das einzige ist.
Also.

Das letzte Mal in Java gibt es sicherlich einen Trend zu Annotashki. Zum Ruhm des Fast-Fail-Konzepts werden die Parameter von Methoden häufig mit der Annotation @NonNull kommentiert (sodass etwas schief geht, wenn etwas schief geht).

Es gibt viele Importoptionen dafür (oder Anmerkungen, die in der Ideologie ähnlich sind), aber wie bereits klar geworden ist, werden wir uns auf die Version konzentrieren

import lombok.NonNull; 

Wenn Sie diese (oder eine ähnliche) Anmerkung verwenden, haben Sie einen Vertrag, den Sie mit einem Test überprüfen müssen, und jeder statische Code-Analysator wird Ihnen dies freundlicherweise mitteilen (Sonar sagt es Ihnen genau).

Das Testen dieser Anmerkung mit einem Komponententest ist recht einfach. Das Problem besteht darin, dass sich solche Tests in Ihrem Projekt mit der Geschwindigkeit von Kaninchen im Frühjahr vermehren und Kaninchen, wie Sie wissen, gegen das DRY-Prinzip verstoßen.

In dem Artikel werden wir ein kleines Test-Framework schreiben, um den Vertrag mit @ NonNull-Annotationen zu testen (und damit Sonar nicht mit einem bösen roten Licht in Ihren Augen leuchtet).

PS Der Name des Songs wurde von dem Song der PowerWolf-Band inspiriert, der (von Golly) gespielt wurde, als ich den Namen schrieb (im Original klingt der Name positiver).

Hauptteil


Zunächst haben wir die Anmerkung wie folgt getestet:

 @Test void methodNameWithNullArgumentThrowException() { try { instance.getAnyType(null); fail("Exception not thrown"); } catch (final NullPointerException e) { assertNotNull(e); } } 

Sie haben die Methode aufgerufen und null als Parameter übergeben, der mit der Annotation @NonNull versehen ist.
Sie bekamen NPE und waren zufrieden (Sonar war auch glücklich).

Dann fingen sie an, dasselbe zu tun, aber mit einer modischeren Behauptung, die durch den Lieferanten funktioniert (wir lieben Lambdas):

 @TestUnitRepeatOnce void methodNameWithNullArgumentThrowException() { assertThrows(NullPointerException.class, () -> instance.getAnyType(null)); } 

Stilvoll. Modisch. Jugend

Es scheint möglich zu beenden, Anmerkungen werden getestet, also was mehr?

Das Problem (nicht das Problem, aber immer noch) dieser Testmethode ist aufgetreten, als ich eines Tages einen Test für eine Methode schrieb. Es hat erfolgreich funktioniert, und dann habe ich festgestellt, dass der Parameter keine @ NonNull-Annotation enthält.

Es ist verständlich: Sie rufen die Testmethode auf, ohne das Verhalten der Moque-Klassen über when () / then () zu beschreiben. Der ausführende Thread tritt sicher in die Methode ein, irgendwo darin fängt er NPE auf einem entsperrten (oder gesperrten, aber ohne when () / then ()) Objekt ab und stürzt jedoch mit NPE ab, wie Sie gewarnt haben, was bedeutet, dass der Test grün ist

Es stellt sich heraus, dass wir in diesem Fall nicht die Anmerkung testen, aber es ist nicht klar, was. Wenn der Test korrekt funktioniert, sollten wir nicht einmal tiefer in die Methode einsteigen müssen (auf die Schwelle fallen).
@NonNull-Annotationen von Lombok haben eine Funktion: Wenn wir von NPE zu Annotationen fallen, wird der Parametername in den Fehler geschrieben.

Wir werden uns darauf einlassen, nachdem wir von NPE gefallen sind, werden wir zusätzlich den Text von Stacktrace wie folgt überprüfen:

 exception.getCause().getMessage().equals(parameter.getName()) 

Und wenn plötzlich ...
Falls Lombok plötzlich aktualisiert und aufhört, den Namen des Parameters zu schreiben, der im Stacktrace null erhalten hat, werden wir Andrei Pangins Vortrag über die JVM-TI überprüfen und ein Plug-In für die JVM schreiben, in das wir den Parameternamen übertragen.

Alles scheint nichts zu sein, jetzt prüfen wir wirklich, was benötigt wird, aber das Problem der „Kaninchen“ ist nicht gelöst.

Ich hätte gerne ein Tool, das zum Beispiel so gesagt werden könnte:

 @TestUnitRepeatOnce @SneakyThrows void nonNullAnnotationTest() { assertNonNullAnnotation(YourPerfectClass.class); } 

und er selbst würde alle öffentlichen Methoden der angegebenen Klasse scannen und alle ihre @ NoNull-Parameter mit einem Test überprüfen.

Sie werden sagen, eine Reflexion erhalten und prüfen, ob die Methode @NonNull aktiviert ist und ob ein Aufzählungszeichen darin null ist.

Alles wäre nichts, aber RetentionPolicy ist nicht dasselbe.

Alle Annotationen haben einen RetentionPolicy-Parameter, der drei Typen haben kann: SOURCE, CLASS und RUNTIME. Daher verfügt Lombok standardmäßig über RetentionPolicy.SOURCE. Dies bedeutet, dass diese Annotation in Runtime nicht sichtbar ist und Sie sie nicht durch Reflexion finden.

In unserem Projekt werden alle Parameter öffentlicher Methoden mit Anmerkungen versehen (ohne Grundelemente). Wenn verstanden wird, dass der Parameter nicht null sein kann, wenn das Gegenteil angenommen wird, wird der Parameter mit spring @Nullable mit Anmerkungen versehen. Sie können sich daran beteiligen, wir werden nach allen öffentlichen Methoden und allen darin enthaltenen Parametern suchen, die nicht mit @Nullable markiert sind und keine Grundelemente sind.
Wir meinen, dass in allen anderen Fällen die Annotation @NonNull auf den Parametern stehen sollte.

Der Einfachheit halber werden wir, wann immer möglich, die Logik durch private Methoden verbreiten. Zunächst werden wir alle öffentlichen Methoden erhalten:

 private List<Method> getPublicMethods(final Class clazz) { return Arrays.stream(clazz.getDeclaredMethods()) .filter(METHOD_FILTER) .collect(toList()); } 

Dabei ist METHOD_FILTER ein reguläres Prädikat, in dem wir Folgendes sagen:

  • Die Methode muss öffentlich sein
  • Es sollte nicht syntetisch sein (und dies passiert, wenn Sie eine Methode mit einem Rohparameter haben)
  • Es sollte nicht abstrakt sein (über abstrakte Klassen separat und unten)
  • Der Methodenname sollte nicht gleich sein (falls eine böse Person beschließt, eine Klasse mit überschriebenen gleich () auf der Eingabe unseres POJO-Frameworks zu füllen).

Nachdem wir alle benötigten Methoden erhalten haben, sortieren wir sie in einer Schleife aus.
Wenn die Methode überhaupt keine Parameter hat, ist dies nicht unser Kandidat:

 if (method.getParameterCount() == 0) { continue; } 

Wenn es Parameter gibt, müssen wir verstehen, ob sie mit @NonNull kommentiert sind (genauer gesagt, sollten sie laut sein

Logik
  • öffentliche Methode
  • nicht @Nullable
  • nicht primitiv


Erstellen Sie dazu eine Karte und fügen Sie unsere Parameter entsprechend der Reihenfolge in der Methode ein. Gegenüber setzen wir ein Flag, das angibt, ob die Annotation @NonNull über oder nicht liegen soll:

 int nonNullAnnotationCount = 0; int index = 0; val parameterCurrentMethodArray = method.getParameters(); val notNullAnnotationParameterMap = new HashMap<Integer, Boolean>(); for (val parameter : parameterCurrentMethodArray) { if (isNull(parameter.getAnnotation(Nullable.class)) && isFalse(parameter.getType().isPrimitive())) { notNullAnnotationParameterMap.put(index++, true); nonNullAnnotationCount++; } else { notNullAnnotationParameterMap.put(index++, false); } } if (nonNullAnnotationCount == 0) { continue; } 

Diese Zuordnung ist nützlich, um dann die Methode aufzurufen und sie mit der Annotation @NonNull an alle Parameter zu übergeben, und nicht nur an die erste.

Der Parameter nonNullAnnotationCount zählt, wie viele Parameter in der Methode mit @NonNull kommentiert werden sollen. Er bestimmt die Anzahl der Aufrufintegrationsinteraktionen für jede Methode.

Übrigens, wenn es keine @ NoNull-Annotationen gibt (es gibt Parameter, aber alle sind primitiv oder @ Nullable), gibt es nichts zu besprechen:

 if (nonNullAnnotationCount == 0) { continue; } 

Wir haben eine Karte mit Parametern zur Hand. Wir wissen, wie oft eine Methode aufgerufen werden muss und an welchen Positionen Null gesetzt werden soll. Die Angelegenheit ist klein (wie ich naiv ohne Verständnis dachte). Wir müssen eine Instanz der Klasse erstellen und Methoden aufrufen.

Probleme beginnen, wenn Sie erkennen, wie unterschiedlich eine Instanz ist: Es kann eine private Klasse sein, es kann eine Klasse mit einem Standardkonstruktor sein, mit einem Konstruktor mit Parametern, mit einem solchen und einem solchen Konstruktor, einer abstrakten Klasse, einer Schnittstelle (mit ihren Standardmethoden, die auch öffentlich sind und die auch getestet werden müssen).

Und wenn wir die Instanz per Hook oder Crook erstellt haben, müssen wir die Parameter an die Aufrufmethode übergeben und auch hier erweitern: Wie erstelle ich eine Instanz der letzten Klasse? und Enum? und primitiv? und ein Array von Grundelementen (das auch ein Objekt ist und auch mit Anmerkungen versehen werden kann).

Nun, lass es uns in der richtigen Reihenfolge tun.

Der erste Fall ist eine Klasse mit einem privaten Konstruktor:

 if (ONLY_ONE_PRIVATE_CONSTRUCTOR_FILTER.test(clazz)) { notNullAnnotationParameterMap.put(currentNullableIndex, false); method.invoke(clazz, invokeMethodParameterArray); makeErrorMessage(method); } 

Dann rufen wir einfach unsere Aufrufmethode auf, übergeben den von außen kommenden Clazz an den Test und ein Array von Parametern, in denen null bereits an die erste Position mit dem Flag für die Annotation @NonNull geladen wird (denken Sie daran, oben haben wir die Map @ NonNulls erstellt), die wir beginnen Führen Sie eine Schleife aus und erstellen Sie ein Array von Parametern. Ändern Sie abwechselnd die Position des Nullparameters und setzen Sie das Flag auf Null, bevor Sie die Methode aufrufen, sodass der andere Parameter bei der nächsten Integration null wird.

Im Code sieht es so aus:

 val invokeMethodParameterArray = new Object[parameterCurrentMethodArray.length]; boolean hasNullParameter = false; int currentNullableIndex = 0; for (int i = 0; i < invokeMethodParameterArray.length; i++) { if (notNullAnnotationParameterMap.get(i) && isFalse(hasNullParameter)) { currentNullableIndex = i; invokeMethodParameterArray[i] = null; hasNullParameter = true; } else { mappingParameter(parameterCurrentMethodArray[i], invokeMethodParameterArray, i); } } 

Die erste Option der Instanziierung wurde aussortiert.

Bei weiteren Schnittstellen ist es unmöglich, eine Instanz einer Schnittstelle zu erstellen (es gibt nicht einmal einen Konstruktor).

Daher wird es mit der Schnittstelle folgendermaßen aussehen:

 if (INTERFACE_FILTER.test(clazz)) { notNullAnnotationParameterMap.put(currentNullableIndex, false); method.invoke(createInstanceByDynamicProxy(clazz, invokeMethodParameterArray), invokeMethodParameterArray); makeErrorMessage(method); } 

Mit createInstanceByDynamicProxy können wir eine Instanz für eine Klasse erstellen, wenn sie mindestens eine Schnittstelle implementiert oder selbst eine Schnittstelle ist

Nuance
Beachten Sie, dass es hier grundsätzlich darum geht, welche Schnittstellen die Klasse implementiert. Die Typschnittstelle ist wichtig (und nicht vergleichbar). Es gibt Methoden, die Sie in der Zielklasse implementieren. Andernfalls wird Sie die Instanz mit ihrem Typ überraschen

aber drinnen ist es so:

 private Object createInstanceByDynamicProxy(final Class clazz, final Object[] invokeMethodParameterArray) { return newProxyInstance( currentThread().getContextClassLoader(), new Class[]{clazz}, (proxy, method1, args) -> { Constructor<Lookup> constructor = Lookup.class .getDeclaredConstructor(Class.class); constructor.setAccessible(true); constructor.newInstance(clazz) .in(clazz) .unreflectSpecial(method1, clazz) .bindTo(proxy) .invokeWithArguments(invokeMethodParameterArray); return null; } ); } 

Rechen
Übrigens gab es hier auch einige Rechen, ich weiß nicht mehr, welche, es gab viele, aber Sie müssen einen Proxy über Lookup.class erstellen

Die nächste Instanz (mein Favorit) ist eine abstrakte Klasse. Und hier hilft uns der dynamische Proxy nicht mehr, denn wenn eine abstrakte Klasse eine Art Schnittstelle implementiert, ist dies eindeutig nicht der Typ, den wir möchten. Und einfach so können wir newInstance () nicht aus einer abstrakten Klasse nehmen und erstellen. Hier hilft uns CGLIB, eine Spring Lib, die Proxys basierend auf Vererbung erstellt. Das Problem ist jedoch, dass die Zielklasse einen Standardkonstruktor (ohne Parameter) haben muss

Klatsch
Obwohl CGLIB seit dem 4. Frühjahr nach Klatsch im Internet urteilt, kann es ohne CGLIB funktionieren, und so: Also funktioniert es nicht!
Die Option zum Instanziieren einer abstrakten Klasse wäre folgende:
 if (isAbstract(clazz.getModifiers())) { createInstanceByCGLIB(clazz, method, invokeMethodParameterArray); makeErrorMessage(); } 

makeErrorMessage (), das bereits in den Codebeispielen zu sehen war, löscht den Test. Wenn wir die Methode mit dem mit Anmerkungen versehenen @ NonNull-Parameter aufgerufen haben, der null übergeben hat und nicht gefallen ist, hat der Test nicht funktioniert. Sie müssen ihn löschen.

Für die Parameterzuordnung haben wir eine gemeinsame Methode, mit der sowohl Konstruktor- als auch Methodenparameter zugeordnet und gesperrt werden können. Sie sieht folgendermaßen aus:

 private void mappingParameter(final Parameter parameter, final Object[] methodParam, final int index) throws InstantiationException, IllegalAccessException { if (isFinal(parameter.getType().getModifiers())) { if (parameter.getType().isEnum()) { methodParam[index] = Enum.valueOf( (Class<Enum>) (parameter.getType()), parameter.getType().getEnumConstants()[0].toString() ); } else if (parameter.getType().isPrimitive()) { mappingPrimitiveName(parameter, methodParam, index); } else if (parameter.getType().getTypeName().equals("byte[]")) { methodParam[index] = new byte[0]; } else { methodParam[index] = parameter.getType().newInstance(); } } else { methodParam[index] = mock(parameter.getType()); } } 

Achten Sie auf die Erstellung von Enum (Kirsche auf dem Kuchen). Im Allgemeinen können Sie Enum nicht einfach nehmen und erstellen.

Hier für die endgültigen Parameter Ihre eigene Zuordnung, für nicht endgültige Ihre eigene und dann einfach im Text (Code).

Nachdem wir die Parameter für den Konstruktor und für die Methode erstellt haben, bilden wir unsere Instanz:

 val firstFindConstructor = clazz.getConstructors()[0]; val constructorParameterArray = new Object[firstFindConstructor.getParameters().length]; for (int i = 0; i < constructorParameterArray.length; i++) { mappingParameter(firstFindConstructor.getParameters()[i], constructorParameterArray, i); } notNullAnnotationParameterMap.put(currentNullableIndex, false); createAndInvoke(clazz, method, invokeMethodParameterArray, firstFindConstructor, constructorParameterArray); makeErrorMessage(method); 

Wir wissen bereits mit Sicherheit, dass wir, da wir diese Phase des Codes erreicht haben, mindestens einen Konstruktor haben. Wir können jeden zum Erstellen einer Instanz verwenden. Nehmen wir also den ersten, den wir sehen, prüfen Sie, ob er Parameter im Konstruktor enthält, und rufen Sie ihn auf, wenn nicht so:

 method.invoke(spy(clazz.getConstructors()[0].newInstance()), invokeMethodParameterArray); 


Nun, wenn es so etwas gibt:
 method.invoke(spy(clazz.getConstructors()[0].newInstance()), invokeMethodParameterArray); 

Dies ist die Logik, die in der Methode createAndInvoke () auftritt, die Sie etwas höher gesehen haben.
Die Vollversion der Testklasse unter dem Spoiler habe ich nicht auf git hochgeladen, wie ich in einem Arbeitsprojekt geschrieben habe, aber tatsächlich ist es nur eine Klasse, die in Ihren Tests vererbt und verwendet werden kann.

Quellcode
 public class TestUtil { private static final Predicate<Method> METHOD_FILTER = method -> isPublic(method.getModifiers()) && isFalse(method.isSynthetic()) && isFalse(isAbstract(method.getModifiers())) && isFalse(method.getName().equals("equals")); private static final Predicate<Class> ONLY_ONE_PRIVATE_CONSTRUCTOR_FILTER = clazz -> clazz.getConstructors().length == 0 && isFalse(clazz.isInterface()); private static final Predicate<Class> INTERFACE_FILTER = clazz -> clazz.getConstructors().length == 0; private static final BiPredicate<Exception, Parameter> LOMBOK_ERROR_FILTER = (exception, parameter) -> isNull(exception.getCause().getMessage()) || isFalse(exception.getCause().getMessage().equals(parameter.getName())); protected void assertNonNullAnnotation(final Class clazz) throws Throwable { for (val method : getPublicMethods(clazz)) { if (method.getParameterCount() == 0) { continue; } int nonNullAnnotationCount = 0; int index = 0; val parameterCurrentMethodArray = method.getParameters(); val notNullAnnotationParameterMap = new HashMap<Integer, Boolean>(); for (val parameter : parameterCurrentMethodArray) { if (isNull(parameter.getAnnotation(Nullable.class)) && isFalse(parameter.getType().isPrimitive())) { notNullAnnotationParameterMap.put(index++, true); nonNullAnnotationCount++; } else { notNullAnnotationParameterMap.put(index++, false); } } if (nonNullAnnotationCount == 0) { continue; } for (int j = 0; j < nonNullAnnotationCount; j++) { val invokeMethodParameterArray = new Object[parameterCurrentMethodArray.length]; boolean hasNullParameter = false; int currentNullableIndex = 0; for (int i = 0; i < invokeMethodParameterArray.length; i++) { if (notNullAnnotationParameterMap.get(i) && isFalse(hasNullParameter)) { currentNullableIndex = i; invokeMethodParameterArray[i] = null; hasNullParameter = true; } else { mappingParameter(parameterCurrentMethodArray[i], invokeMethodParameterArray, i); } } try { if (ONLY_ONE_PRIVATE_CONSTRUCTOR_FILTER.test(clazz)) { notNullAnnotationParameterMap.put(currentNullableIndex, false); method.invoke(clazz, invokeMethodParameterArray); makeErrorMessage(method); } if (INTERFACE_FILTER.test(clazz)) { notNullAnnotationParameterMap.put(currentNullableIndex, false); method.invoke(createInstanceByDynamicProxy(clazz, invokeMethodParameterArray), invokeMethodParameterArray); makeErrorMessage(method); } if (isAbstract(clazz.getModifiers())) { createInstanceByCGLIB(clazz, method, invokeMethodParameterArray); makeErrorMessage(); } val firstFindConstructor = clazz.getConstructors()[0]; val constructorParameterArray = new Object[firstFindConstructor.getParameters().length]; for (int i = 0; i < constructorParameterArray.length; i++) { mappingParameter(firstFindConstructor.getParameters()[i], constructorParameterArray, i); } notNullAnnotationParameterMap.put(currentNullableIndex, false); createAndInvoke(clazz, method, invokeMethodParameterArray, firstFindConstructor, constructorParameterArray); makeErrorMessage(method); } catch (final Exception e) { if (LOMBOK_ERROR_FILTER.test(e, parameterCurrentMethodArray[currentNullableIndex])) { makeErrorMessage(method); } } } } } @SneakyThrows private void createAndInvoke( final Class clazz, final Method method, final Object[] invokeMethodParameterArray, final Constructor firstFindConstructor, final Object[] constructorParameterArray ) { if (firstFindConstructor.getParameters().length == 0) { method.invoke(spy(clazz.getConstructors()[0].newInstance()), invokeMethodParameterArray); } else { method.invoke(spy(clazz.getConstructors()[0].newInstance(constructorParameterArray)), invokeMethodParameterArray); } } @SneakyThrows private void createInstanceByCGLIB(final Class clazz, final Method method, final Object[] invokeMethodParameterArray) { MethodInterceptor handler = (obj, method1, args, proxy) -> proxy.invoke(clazz, args); if (clazz.getConstructors().length > 0) { val firstFindConstructor = clazz.getConstructors()[0]; val constructorParam = new Object[firstFindConstructor.getParameters().length]; for (int i = 0; i < constructorParam.length; i++) { mappingParameter(firstFindConstructor.getParameters()[i], constructorParam, i); } for (val constructor : clazz.getConstructors()) { if (constructor.getParameters().length == 0) { val proxy = Enhancer.create(clazz, handler); method.invoke(proxy.getClass().newInstance(), invokeMethodParameterArray); } } } } private Object createInstanceByDynamicProxy(final Class clazz, final Object[] invokeMethodParameterArray) { return newProxyInstance( currentThread().getContextClassLoader(), new Class[]{clazz}, (proxy, method1, args) -> { Constructor<Lookup> constructor = Lookup.class .getDeclaredConstructor(Class.class); constructor.setAccessible(true); constructor.newInstance(clazz) .in(clazz) .unreflectSpecial(method1, clazz) .bindTo(proxy) .invokeWithArguments(invokeMethodParameterArray); return null; } ); } private void makeErrorMessage() { fail("  @NonNull     DefaultConstructor  "); } private void makeErrorMessage(final Method method) { fail("    " + method.getName() + "   @NonNull"); } private List<Method> getPublicMethods(final Class clazz) { return Arrays.stream(clazz.getDeclaredMethods()) .filter(METHOD_FILTER) .collect(toList()); } private void mappingParameter(final Parameter parameter, final Object[] methodParam, final int index) throws InstantiationException, IllegalAccessException { if (isFinal(parameter.getType().getModifiers())) { if (parameter.getType().isEnum()) { methodParam[index] = Enum.valueOf( (Class<Enum>) (parameter.getType()), parameter.getType().getEnumConstants()[0].toString() ); } else if (parameter.getType().isPrimitive()) { mappingPrimitiveName(parameter, methodParam, index); } else if (parameter.getType().getTypeName().equals("byte[]")) { methodParam[index] = new byte[0]; } else { methodParam[index] = parameter.getType().newInstance(); } } else { methodParam[index] = mock(parameter.getType()); } } private void mappingPrimitiveName(final Parameter parameter, final Object[] methodParam, final int index) { val name = parameter.getType().getName(); if ("long".equals(name)) { methodParam[index] = 0L; } else if ("int".equals(name)) { methodParam[index] = 0; } else if ("byte".equals(name)) { methodParam[index] = (byte) 0; } else if ("short".equals(name)) { methodParam[index] = (short) 0; } else if ("double".equals(name)) { methodParam[index] = 0.0d; } else if ("float".equals(name)) { methodParam[index] = 0.0f; } else if ("boolean".equals(name)) { methodParam[index] = false; } else if ("char".equals(name)) { methodParam[index] = 'A'; } } } 


Fazit


Dieser Code funktioniert und testet Anmerkungen in einem realen Projekt. Derzeit ist nur eine Option möglich, bei der alles Gesagte reduziert werden kann.

Deklarieren Sie einen Lombock-Setter in der Klasse (wenn es einen Spezialisten gibt, der den Setter nicht in der Pojo-Klasse setzt, obwohl dies einfach nicht der Fall ist) und das Feld, auf dem der Setter deklariert wird, ist nicht endgültig.

Dann wird das Framework freundlicherweise sagen, dass es eine öffentliche Methode gibt und es einen Parameter gibt, für den es keine @ NonNull-Annotation gibt. Die Lösung ist einfach: Deklarieren Sie den Setter explizit und kommentieren Sie seinen Parameter basierend auf dem Kontext der Logik @ NonNull / @ Nullable.

Beachten Sie, dass Sie arg [0] und arg [1] usw. finden, wenn Sie möchten, dass ich in Ihren Tests an den Namen des Methodenparameters in Ihren Tests gebunden bin (oder an etwas anderes). In Runtime sind die Namen von Variablen in Methoden standardmäßig nicht verfügbar. .
Verwenden Sie das Maven-Plugin, um die Anzeige von Methodennamen in Runtime zu aktivieren:

 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven.compiler.plugin.version}</version> <configuration> <source>${compile.target.source}</source/> <target>${compile.target.source}</target> <encoding>${project.build.sourceEncoding}</encoding> <compilerArgs><arg>-parameters</arg></compilerArgs> </configuration> </plugin> 

und insbesondere diesen Schlüssel:

 <compilerArgs><arg>-parameters</arg></compilerArgs> 

Ich hoffe du warst interessiert.

Source: https://habr.com/ru/post/de464939/


All Articles