Hoe test ik een privéfunctie of een klasse met privé-methoden, velden of binnenklassen?

Hoe test ik eenheid (XUNIT) een klasse met interne privémethoden, velden of geneste klassen? Of een functie die privé is gemaakt door interne koppeling (staticin C / C++) of is in een privé (anoniem ) NAMESPACE?

Het lijkt slecht om de toegangsmodificator voor een methode of functie te wijzigen om een ​​test te kunnen uitvoeren.


1, Autoriteit 100%

update:

Zo’n 10 jaar later misschien de beste manier om een ​​privé-methode te testen, of een ontoegankelijk lid, is via @Jailbreakvan de Manifold framework.

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

Op deze manier blijft uw code type veilig en leesbaar. Geen ontwerpcompromissen, geen overbelemde methoden en velden omwille van de tests.

Als u iets van een erfenis Java -toepassing hebt, mag u de zichtbaarheid van uw methoden niet wijzigen, de beste manier om privémethoden te testen, reflectie .

Intern gebruiken we helpers om privateen private staticvariabelen te krijgen/in te stellen en om privateen private staticmethoden. Met de volgende patronen kunt u vrijwel alles doen met betrekking tot de privémethoden en -velden. Natuurlijk kun je private static finalvariabelen niet veranderen door middel van reflectie.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

En voor velden:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Opmerkingen:
1. Met TargetClass.getDeclaredMethod(methodName, argClasses)kunt u private-methoden bekijken. Hetzelfde geldt voor
getDeclaredField.
2. De setAccessible(true)is vereist om met privates te spelen.


Antwoord 2, autoriteit 41%

De beste manier om een privémethode te testen, is via een andere openbare methode. Als dit niet mogelijk is, is er sprake van een van de volgende voorwaarden:

  1. De privémethode is dode code
  2. Er hangt een ontwerpgeur in de buurt van de klas die je aan het testen bent
  3. De methode die u probeert te testen, mag niet privé zijn

Antwoord 3, autoriteit 20%

Als ik privémethoden in een klasse heb die zo ingewikkeld zijn dat ik de behoefte voel om de privémethoden rechtstreeks te testen, is dat een codegeur: mijn klasse is te ingewikkeld.

Mijn gebruikelijke aanpak om dergelijke problemen aan te pakken, is door een nieuwe klasse uit te plagen die de interessante stukjes bevat. Vaak zijn deze methode en de velden waarmee het interageert, en misschien kunnen een andere methode of twee worden geëxtraheerd in een nieuwe klasse.

De nieuwe klasse stelt deze methoden bloot als ‘public’, zodat ze toegankelijk zijn voor unit-testing. De nieuwe en oude klassen zijn nu allebei eenvoudiger dan de oorspronkelijke klasse, wat geweldig is voor mij (ik moet het simpel houden, anders verdwaal ik!).

Merk op dat ik niet suggereer dat mensen lessen maken zonder hun hersens te gebruiken! Het punt hier is om de krachten van unit testing te gebruiken om je te helpen goede nieuwe klassen te vinden.


Antwoord 4, autoriteit 17%

Ik heb in het verleden reflectiongebruikt om dit voor Java te doen, en naar mijn mening was het een grote fout.

Strikt genomen zou u geeneenheidstests moeten schrijven die privémethoden rechtstreeks testen. Wat je moettesten is het openbare contract dat de klasse heeft met andere objecten; je moet nooit rechtstreeks de binnenkant van een object testen. Als een andere ontwikkelaar een kleine interne wijziging in de klasse wil aanbrengen, die geen invloed heeft op het openbare contract van de klassen, moet hij/zij uw op reflectie gebaseerde test aanpassen om ervoor te zorgen dat deze werkt. Als je dit tijdens een project herhaaldelijk doet, zijn unit-tests niet langer een nuttige meting van de gezondheid van de code en worden ze een belemmering voor de ontwikkeling en een ergernis voor het ontwikkelteam.

Wat ik in plaats daarvan aanbeveel, is het gebruik van een codedekkingstool zoals Cobertura, om ervoor te zorgen dat de eenheidstests die u schrijft een behoorlijke dekking van de code bieden in privémethoden. Op die manier test u indirect wat de privémethoden doen en behoudt u een hoger niveau van behendigheid.


Antwoord 5, autoriteit 13%

Uit dit artikel: Privémethoden testen met JUnit en SuiteRunner(Bill Venners) , je hebt in principe 4 opties:

  1. Test geen privémethoden.
  2. Geef het methodepakket toegang.
  3. Gebruik een geneste testklasse.
  4. Gebruik reflectie.

Antwoord 6, autoriteit 8%

Over het algemeen is een unit-test bedoeld om de openbare interface van een klasse of unit te oefenen. Daarom zijn privémethoden implementatiedetails waarvan u niet zou verwachten dat ze expliciet worden getest.


Antwoord 7, autoriteit 4%

Slechts twee voorbeelden van waar ik een privémethode zou willen testen:

  1. Decoderingsroutines– ik zou het niet doen
    wil ze voor iedereen zichtbaar maken, alleen voor
    omwille van het testen, anders kan iedereen
    gebruik ze om te decoderen. Maar zij zijn
    intrinsiek aan de code, ingewikkeld,
    en moeten altijd werken (de voor de hand liggende uitzondering is reflectie die in de meeste gevallen kan worden gebruikt om zelfs privémethoden te bekijken, wanneer SecurityManagerniet is geconfigureerd om dit te voorkomen).
  2. Een SDK makenvoor de community
    consumptie. Hier neemt het publiek een
    geheel andere betekenis, aangezien dit
    is code die de hele wereld mag zien
    (niet alleen intern voor mijn applicatie). ik zet
    codeer in privémethoden als ik dat niet doe
    wil dat de SDK-gebruikers het zien – I
    zie dit niet als codegeur, alleen
    zoals hoe SDK-programmering werkt. maar van
    natuurlijk moet ik mijn . nog testen
    privé-methoden, en ze zijn waar
    de functionaliteit van mijn SDK eigenlijk
    leeft.

Ik begrijp het idee om alleen het ‘contract’ te testen. Maar ik zie niet dat iemand kan pleiten voor het niet testen van code – je kilometers kunnen variëren.

Dus mijn afweging houdt in dat ik de JUnits ingewikkelder moet maken met reflectie, in plaats van mijn veiligheid & SDK.


Antwoord 8, autoriteit 4%

De privémethoden worden aangeroepen door een openbare methode, dus de invoer voor uw openbare methoden moet ook de privémethoden testen die door die openbare methoden worden aangeroepen. Als een openbare methode faalt, kan dat een fout zijn in de privémethode.


Antwoord 9, autoriteit 2%

Een andere benadering die ik heb gebruikt, is om een privémethode te wijzigen om privé of beschermd te verpakken en deze vervolgens aan te vullen met de annotatie @VisibleForTestingvan de Google Guava-bibliotheek.

Dit vertelt iedereen die deze methode gebruikt om voorzichtig te zijn en er niet rechtstreeks toegang toe te krijgen, zelfs niet in een pakket. Ook hoeft een testklasse niet fysiekin hetzelfde pakket te zitten, maar in hetzelfde pakket onder de map test.

Als een te testen methode bijvoorbeeld in src/main/java/mypackage/MyClass.javastaat, dan moet uw testoproep in src/test/java/mypackage/MyClassTest.java. Op die manier kreeg je toegang tot de testmethode in je testklas.


Antwoord 10, autoriteit 2%

In het Spring Frameworkkun je privémethoden testen met deze methode:

ReflectionTestUtils.invokeMethod()

Bijvoorbeeld:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Antwoord 11, autoriteit 2%

Om verouderde code te testen met grote en eigenzinnige klassen, is het vaak erg handig om de enige privé (of openbare) methode te kunnen testen die ik nuaan het schrijven ben.

Ik gebruik het juntx.util.PrivateAccessor-pakket voor Java . Veel handige oneliners voor toegang tot privémethoden en privévelden.

import junitx.util.PrivateAccessor;
PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

Antwoord 12, autoriteit 2%

Nadat ik de oplossing van Cem Catikkas met behulp van reflectievoor Java zou ik moeten zeggen dat hij een elegantere oplossing was dan ik hier heb beschreven. Als je echter op zoek bent naar een alternatief voor het gebruik van reflectie en toegang hebt tot de bron die je aan het testen bent, is dit nog steeds een optie.

Het testen van privémethoden van een klasse is mogelijk nuttig, vooral met testgestuurde ontwikkeling, waar u kleine tests wilt ontwerpen voordat u code schrijft.

Door een test te maken met toegang tot privéleden en methoden, kunnen codegebieden worden getest die moeilijk specifiek te targeten zijn met alleen toegang tot openbare methoden. Als een openbare methode meerdere stappen omvat, kan deze uit verschillende privémethoden bestaan, die vervolgens afzonderlijk kunnen worden getest.

Voordelen:

  • Kan testen tot een fijnere granulariteit

Nadelen:

  • Testcode moet in dezelfde staan
    bestand als broncode, wat kan zijn
    moeilijker te onderhouden
  • Net als bij .class uitvoerbestanden, moeten ze binnen hetzelfde pakket blijven als aangegeven in de broncode

Als continu testen deze methode echter vereist, kan dit een signaal zijn dat de privémethoden moeten worden geëxtraheerd, die op de traditionele, openbare manier kunnen worden getest.

Hier is een ingewikkeld voorbeeld van hoe dit zou werken:

// Import statements and package declarations
public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }
    // Constructor and the rest of the class
    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }
        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }
        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

De innerlijke klasse zou worden gecompileerd tot ClassToTest$StaticInnerTest.

Zie ook: Java Tip 106: Statische innerlijke lessen voor plezier en winst


Antwoord 13, autoriteit 2%

Zoals anderen al hebben gezegd… test privémethoden niet rechtstreeks. Hier zijn een paar gedachten:

  1. Houd alle methoden klein en gericht (gemakkelijk te testen, gemakkelijk te vinden wat er mis is)
  2. Gebruik tools voor codedekking. Ik hou van Cobertura(oh fijne dag, het lijkt erop dat er een nieuwe versie uit is!)

Voer de CODE-dekking uit op de tests. Als u ziet dat deze methoden niet volledig worden getest, draagt ​​u aan de tests om de dekking omhoog te krijgen. Streef naar 100% -code-dekking, maar realiseer je dat je het waarschijnlijk niet zult krijgen.


14, Autoriteit 2%

Private methoden worden door de openbare wijze geconsumeerd. Anders zijn ze dead-code. Daarom test u de openbare methode, beweert u de verwachte resultaten van de openbare methode en daardoor, de privémethoden die het verbruikt.

Testen Particuliere methoden moeten worden getest door debugging voordat u uw eenheidstests uitvoert op openbare methoden.

Mogelijk kunnen ze ook worden gedebugd met behulp van testgedreven ontwikkeling, het debuggen van uw eenheidstests totdat al uw beweringen worden voldaan.

Ik ben persoonlijk van mening dat het beter is om klassen te maken met behulp van TDD; Het maken van de openbare methode-stubs en vervolgens het genereren van eenheidstests met alle de vooraf gedefinieerde beweringen, dus het verwachte resultaat van de methode wordt bepaald voordat u het codeert. Op deze manier gaat u niet op het verkeerde pad van het maken van de testbehandelingen van de eenheid passen in de resultaten. Uw klas is dan robuust en voldoet aan de vereisten wanneer al uw eenheidstests passeren.


15

Als het gebruik van de lente, ReflectionStestutils biedt enkele handige tools die hier helpen met minimale inspanning. Bijvoorbeeld om een ​​spot op een privé-lid op te zetten zonder gedwongen te worden om een ​​ongewenste openbare setter toe te voegen:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

16

Als u de bestaande code probeert te testen die u terughoudend bent of niet in staat bent om te veranderen, is reflectie een goede keuze.

Als het ontwerp van de klasse nog steeds flexibel is en je een gecompliceerde privémethode hebt die je apart wilt testen, raad ik je aan om deze in een aparte klasse te plaatsen en die klasse afzonderlijk te testen. Dit hoeft de openbare interface van de originele klasse niet te veranderen; het kan intern een instantie van de helper-klasse maken en de helper-methode aanroepen.

Als je moeilijke foutcondities wilt testen die voortkomen uit de helper-methode, kun je een stap verder gaan. Extraheer een interface uit de helperklasse, voeg een openbare getter en setter toe aan de originele klasse om de helperklasse te injecteren (gebruikt via de interface), en injecteer vervolgens een nepversie van de helperklasse in de originele klasse om te testen hoe de originele klasse reageert op uitzonderingen van de helper. Deze aanpak is ook handig als je de originele klasse wilt testen zonder ook de helperklasse te testen.


Antwoord 17

Het testen van privémethoden verbreekt de inkapseling van uw klasse, omdat elke keer dat u de interne implementatie wijzigt, u de clientcode verbreekt (in dit geval de tests).

Test dus geen privémethoden.


18

Als u privémethoden van een LEVAY-toepassing wilt testen waar u de code niet kunt wijzigen, is een optie voor Java Jmockit , waarmee u mals kunt maken met een object, zelfs wanneer ze privé zijn voor de klas.


19

Ik heb de neiging geen privé-methoden te testen. Er ligt waanzin. Persoonlijk geloof ik dat je alleen je publiekelijk blootgestelde interfaces moet testen (en die beschermde en interne methoden omvat).


20

Als u Junit gebruikt, bekijk dan junit-addons . Het heeft de mogelijkheid om het Java-beveiligingsmodel te negeren en toegang te krijgen tot particuliere methoden en kenmerken.


21

Ik zou u aanraden u uw code een beetje te refereren. Wanneer je moet gaan nadenken over het gebruik van reflectie of andere soort dingen, voor het testen van je code, gaat er iets mis met je code.

U noemde verschillende soorten problemen. Laten we beginnen met privévelden. In het geval van privévelden zou ik daar een nieuwe constructor en geïnjecteerde velden in hebben toegevoegd. In plaats van dit:

public class ClassToTest {
    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Ik had dit gebruikt:

public class ClassToTest {
    private final String first;
    private final List<String> second;
    public ClassToTest() {
        this("first", new ArrayList<>());
    }
    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Dit is geen probleem, zelfs niet met wat verouderde code. Oude code gebruikt een lege constructor en als je het mij vraagt, ziet de gerefactorde code er schoner uit en kun je de nodige waarden in de test injecteren zonder nadenken.

Nu over privémethoden. In mijn persoonlijke ervaring, wanneer je een privémethode moet afstoten om te testen, heeft die methode niets te maken in die klasse. Een gebruikelijk patroon zou in dat geval zijn om het in een interface te verpakken, zoals Callableen dan geef je die interface ook door in de constructor (met die meervoudige constructortruc ):

public ClassToTest() {
    this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

Meestal lijkt alles wat ik heb geschreven op een afhankelijkheidsinjectiepatroon. In mijn persoonlijke ervaring is het erg handig tijdens het testen, en ik denk dat dit soort code schoner is en gemakkelijker te onderhouden is. Ik zou hetzelfde zeggen over geneste klassen. Als een geneste klasse zware logica bevat, zou het beter zijn als je het als een privéklasse van een pakket had verplaatst en het in een klasse had geïnjecteerd die het nodig had.

Er zijn ook verschillende andere ontwerppatronen die ik heb gebruikt bij het herstructureren en onderhouden van legacy-code, maar het hangt allemaal af van de gevallen waarin je code moet worden getest. Het gebruik van reflectie is meestal geen probleem, maar als je een bedrijfsapplicatie hebt die zwaar wordt getest en tests worden uitgevoerd voor elke implementatie, wordt alles erg traag (het is gewoon vervelend en ik hou niet van dat soort dingen).

Er is ook een setter-injectie, maar ik zou het niet aanbevelen om het te gebruiken. Ik kan maar beter bij een constructor blijven en alles initialiseren wanneer het echt nodig is, zodat de mogelijkheid overblijft om noodzakelijke afhankelijkheden te injecteren.


Antwoord 22

Zie hieronder voor een voorbeeld;

Het volgende importstatement moet worden toegevoegd:

import org.powermock.reflect.Whitebox;

Je kunt nu direct het object doorgeven dat de private methode, de naam van de methode die moet worden aangeroepen en aanvullende parameters heeft, zoals hieronder.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

Antwoord 23

Hier is mijn algemene functie om privévelden te testen:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    return (F)field.get(obj);
}

Antwoord 24

Een privémethode is alleen toegankelijk binnen dezelfde klasse. Er is dus geen manier om een “private” methode van een doelklasse uit een testklasse te testen. Een uitweg is dat u unit-tests handmatig kunt uitvoeren of uw methode kunt wijzigen van “privé” in “beschermd”.

En dan is een beveiligde methode alleen toegankelijk binnen hetzelfde pakket waarin de klasse is gedefinieerd. Het testen van een beveiligde methode van een doelklasse betekent dus dat we uw testklasse in hetzelfde pakket moeten definiëren als de doelklasse.

Als al het bovenstaande niet aan uw eisen voldoet, gebruik dan de reflectiemanierom toegang te krijgen tot de privémethode.


Antwoord 25

Zoals velen hierboven hebben gesuggereerd, is een goede manier om ze te testen via uw openbare interfaces.

Als je dit doet, is het een goed idee om een codedekkingstool (zoals Emma) te gebruiken om te zien of je privémethoden daadwerkelijk worden uitgevoerd vanuit je tests.


Antwoord 26

Vandaag heb ik een Java-bibliotheek gepusht om te helpen bij het testen van privémethoden en velden. Het is ontworpen met Android in gedachten, maar het kan echt voor elk Java-project worden gebruikt.

Als je code hebt met privémethoden of velden of constructors, kun je BoundBox. Het doet precies wat je zoekt.
Hieronder ziet u een voorbeeld van een test die toegang heeft tot twee privévelden van een Android-activiteit om deze te testen:

@UiThreadTest
public void testCompute() {
    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();
    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBoxmaakt het gemakkelijk om privé/beschermde velden, methoden en constructors te testen. Je hebt zelfs toegang tot dingen die verborgen zijn door overerving. Inderdaad, BoundBox breekt inkapseling. Het geeft je door middel van reflectie toegang tot dat alles, MAARalles wordt gecontroleerd tijdens het compileren.

Het is ideaal voor het testen van verouderde code. Gebruik het zorgvuldig. 😉

https://github.com/stephanenicolas/boundbox


Antwoord 27

PowerMockito is hiervoor gemaakt.
Maven-afhankelijkheid gebruiken

   <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-core</artifactId>
        <version>2.0.7</version>
        <scope>test</scope>
    </dependency>

Dan kun je doen

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);

Antwoord 28

Ten eerste gooi ik deze vraag weg: waarom hebben uw privéleden geïsoleerde tests nodig? Zijn ze zo complex en bieden ze zulke gecompliceerde gedragingen dat ze apart van het openbare oppervlak moeten worden getest? Het is unit-testing, geen ‘line-of-code’-test. Maak je niet druk om de kleine dingen.

Als ze zo groot zijn, groot genoeg dat deze privéleden elk een ‘eenheid’ zijn met een grote complexiteit, overweeg dan om dergelijke privéleden uit deze klasse te refactoren.

Als refactoring ongepast of onhaalbaar is, kunt u dan het strategiepatroon gebruiken om de toegang tot deze privé-lidfuncties / lidklassen te vervangen wanneer u een eenheidstest uitvoert? Bij unit-test zou de strategie extra validatie bieden, maar in release-builds zou het een eenvoudige passthrough zijn.


Antwoord 29

Ik had onlangs dit probleem en schreef een kleine tool, genaamd Picklock, die de problemen van expliciet met behulp van de Java Reflection API, twee voorbeelden:

Oproepmethoden, bijv. private void method(String s)– door Java-reflectie

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Oproepmethoden, bijv. private void method(String s)– door Picklock

interface Accessible {
  void method(String s);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Velden instellen, bijv. private BigInteger amount;– door Java-reflectie

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Velden instellen, bijv. private BigInteger amount;– door Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

Other episodes