Is it because the production class or method are covered by other test class/method INDIRECTLY?
Fondamentalmente si. I test servono a garantire che il sistema abbia alcune proprietà. Di solito il sistema fornisce alcune funzionalità. (A volte scriviamo test per riprodurre un comportamento difettoso, tuttavia, ad esempio, quando si registra un bug report.) Quindi se si scrivono nuovi test meno fragili perché sono più disaccoppiati dal codice di produzione, e anche più espressivi perché indicare le funzionalità ottenute dal sistema e non il modo in cui il sistema le ottiene; i vecchi test diretti non forniscono alcun valore aggiuntivo e devono essere rimossi perché rappresentano un onere di manutenzione. L'indirazione ti consente di ottenere sia il disaccoppiamento che l'espressività.
Are there general design pattern or rules that can help writing code that decouples test from production code?
Lo schema verde di zio Bob sulla destra sarebbe ed esempio del modello di facciata perché nasconde servizi multipli e complicati dietro una singola interfaccia.
Per chiarire una cosa, gli sviluppatori junior non leggono il diagramma UML di zio Bob letteralmente. I servizi implementano la facciata in senso sprovveduto.
Vedi sotto l'esempio per vedere come i servizi applicativi implementano la facciata del test. Per vedere anche la differenza / relazione tra la facciata dell'applicazione e la facciata del test (come nel commento di @ jonrsharpe). Un suggerimento può essere: scrivi la tua facciata di prova in modo che si legga come le storie che dimostrerai, o gli scenari nel tuo piano di test, ecc.
class SomeTestClass
{
public void users_cant_register_without_providing_contact_info()
{
failsWithMissingContactInfoErrorMessage(
() -> user.sendsRegistrationFormWithMissingContactInfo());
admin.doesntSeeInRegisteredUsers(user);
}
private failsWithMissingContactInfoErrorMessage(Runnable r)
{
failsWithErrorMentioning(r, MissingContactInfo.class, "missing contact");
}
}
class UserFacade // Can be named after roles
{
CommandInterface handler;
ViewInterface view;
String enteredEmail;
String enteredPhoneNumber;
void sendsRegistrationFormWithMissingContactInfo()
{
fillsTheForm();
leavesEmailBlank();
leavesPhoneNumberBlank();
sendsRegistrationForm();
}
void leavesEmailBlank()
{
this.enteredEmail = "";
}
void leavesPhoneNumberBlank()
{
this.enteredPhoneNumber = "";
}
void entersAValidEmail()
{
this.enteredEmail = "[email protected]";
}
void entersAValidPhoneNumber()
{
this.enteredPhoneNumber = "+1-541-754-3010";
}
void entersAnInvalidPhoneNumber()
{
this.enteredPhoneNumber = "##123456**";
}
void sendsRegistrationForm()
{
handler.handle(new RegisterNewUser(enteredEmail, enteredPhoneNumber));
}
}
class AdminFacade
{
BackEnd backEnd;
void doesntSeeInRegisteredUsers(UserFacade user)
{
// ....
}
}