Ti suggerirei un po 'di refactoring del codice. Quando devi iniziare a pensare a usare reflection o altri tipi di cose, solo per testare il tuo codice, qualcosa non va nel tuo codice.
Hai menzionato diversi tipi di problemi. Cominciamo con campi privati. In caso di campi privati avrei aggiunto un nuovo costruttore e iniettato campi in quello. Invece di questo:
public class ClassToTest {
private final String first = "first";
private final List<String> second = new ArrayList<>();
...
}
Avrei usato questo:
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;
}
...
}
Questo non sarà un problema anche con un codice legacy. Il vecchio codice utilizzerà un costruttore vuoto e, se me lo chiedi, il codice refactored apparirà più pulito e sarai in grado di iniettare i valori necessari nel test senza riflessione.
Ora sui metodi privati. Nella mia esperienza personale quando devi stub un metodo privato per il test, quel metodo non ha nulla a che fare in quella classe. Un modello comune, in quel caso, sarebbe avvolgerlo all'interno di un'interfaccia, come Callable
e quindi si passa a quell'interfaccia anche nel costruttore (con quel trucco costruttore multiplo):
public ClassToTest() {
this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
this.privateMethodLogic = privateMethodLogic;
}
Per lo più tutto ciò che ho scritto sembra essere un modello di iniezione di dipendenza. Nella mia esperienza personale è molto utile durante i test e penso che questo tipo di codice sia più pulito e sarà più facile da mantenere. Direi lo stesso delle classi nidificate. Se una classe nidificata contiene una logica pesante, sarebbe meglio se l'avessi spostata come classe privata del pacchetto e l'avessi iniettata in una classe che ne aveva bisogno.
Ci sono anche molti altri modelli di progettazione che ho usato durante il refactoring e la manutenzione del codice legacy, ma tutto dipende dai casi del codice da testare. L'uso della riflessione per lo più non è un problema, ma quando si dispone di un'applicazione aziendale che è pesantemente testata e i test vengono eseguiti prima di ogni distribuzione, tutto diventa molto lento (è solo fastidioso e non mi piace quel tipo di cose).
C'è anche l'iniezione del setter, ma non consiglierei di usarlo. Meglio restare con un costruttore e inizializzare tutto quando è veramente necessario, lasciando la possibilità di iniettare le dipendenze necessarie.