L'elettricità statica è universalmente "male" per i test unitari e, in caso affermativo, perché Resharper lo consiglia? [chiuso]


85

Ho scoperto che ci sono solo 3 modi per dipendenze unit test (mock / stub) statici in C # .NET:

Dato che due di questi non sono gratuiti e uno non ha raggiunto la versione 1.0, prendere in giro cose statiche non è troppo facile.

Questo ha metodi statici e tale "male" (nel senso del test unitario)? E se è così, perché il resharper vuole che io faccia qualcosa che possa essere statico, statico? (Supponendo che il resharper non sia anche "malvagio".)

Chiarimento: sto parlando dello scenario in cui si desidera testare un'unità un metodo e quel metodo chiama un metodo statico in un'unità / classe diversa . Con la maggior parte delle definizioni di unit test, se si lascia solo il metodo in prova chiamare il metodo statico in altra unità / classe allora siete non unit test, sei l'integrazione di test. (Utile, ma non un test unitario.)


3
TheLQ: Puoi. Credo che stia parlando di non essere in grado di testare metodi statici perché per la maggior parte tocca le variabili statiche. Cambiando così lo stato dopo e tra i test.

26
Personalmente penso che tu stia prendendo troppo in là la definizione di "unità". "Unità" dovrebbe essere "la più piccola unità che ha senso testare in modo isolato". Potrebbe essere un metodo, potrebbe essere più di questo. Se il metodo statico non ha stato e ben testato, avere una seconda chiamata di prova dell'unità non è un problema (IMO).
mlk,

10
"Personalmente penso che tu stia prendendo la definizione di" unità "troppo lontano." No, è solo che sta andando con un uso standard e stai inventando la tua definizione.

7
"perché il resharper vuole che io faccia qualcosa che possa essere statico, statico?" Resharper non vuole che tu faccia nulla. Ti sta semplicemente informando che la modifica è possibile e forse desiderabile da un POV di analisi del codice. Resharper non sostituisce il tuo giudizio!
Adam Naylor,

4
@ acidzombie24. I metodi regolari possono anche modificare lo stato statico, in modo da essere "cattivi" come i metodi statici. Il fatto che possano anche modificare lo stato con un ciclo di vita più breve li rende ancora più pericolosi. (Non sono favorevole ai metodi statici, ma il punto sulla modifica dello stato è un duro colpo anche ai metodi regolari, anche di più)
mike30

Risposte:


105

Guardando le altre risposte qui, penso che potrebbe esserci un po 'di confusione tra metodi statici che mantengono lo stato statico o causano effetti collaterali (che mi sembra una pessima idea) e metodi statici che semplicemente restituiscono un valore.

I metodi statici che non mantengono alcuno stato e non causano effetti collaterali dovrebbero essere facilmente testabili dall'unità. In effetti, considero tali metodi una forma di programmazione funzionale "da uomo povero"; si passa al metodo un oggetto o un valore e restituisce un oggetto o un valore. Niente di più. Non vedo come tali metodi influirebbero negativamente sui test unitari.


44
Il metodo statico è unit testabile, ma per quanto riguarda i metodi che chiamano il metodo statico? Se i chiamanti sono in un'altra classe, hanno una dipendenza che deve essere disaccoppiata per fare un test unitario.
Vaccano,

22
@Vaccano: Ma se scrivi metodi statici che non mantengono lo stato e non hanno effetti collaterali, sono comunque più o meno funzionalmente equivalenti a uno stub.
Robert Harvey,

20
Quelli semplici forse. Ma quelli più complessi possono iniziare a generare eccezioni e avere output che non sono previsti (e dovrebbero essere rilevati nel test unitario per il metodo statico, non nel test unitario per il chiamante del metodo statico), o almeno questo è ciò che I sono stato portato a credere nella letteratura che ho letto.
Vaccano,

21
@Vaccano: un metodo statico che ha output o eccezioni casuali ha effetti collaterali.
Tikhon Jelvis,

10
@TikhonJelvis Robert stava parlando delle uscite; e le eccezioni "casuali" non dovrebbero essere un effetto collaterale, sono essenzialmente una forma di output. Il punto è, ogni volta che testate un metodo che chiama il metodo statico, state avvolgendo quel metodo e tutte le permutazioni del suo potenziale output e non potete testare il vostro metodo in modo isolato.
Nicole,

26

Sembra che tu stia confondendo dati statici e metodi statici . Resharper, se ricordo bene, raccomanda di rendere privatestatici i metodi all'interno di una classe se possono essere fatti in questo modo - credo che ciò produca un piccolo vantaggio in termini di prestazioni. Essa non consiglia fare "tutto ciò che può essere" statica!

Non c'è nulla di sbagliato nei metodi statici e sono facili da testare (purché non modifichino dati statici). Ad esempio, pensa a una libreria matematica, che è un buon candidato per una classe statica con metodi statici. Se hai un metodo (inventivo) come questo:

public static long Square(int x)
{
    return x * x;
}

allora questo è eminentemente testabile e non ha effetti collaterali. Basta controllare che quando si passa, diciamo, 20 si torna indietro 400. Nessun problema.


4
Cosa succede quando hai un'altra classe che chiama questo metodo statico? Sembra semplice in questo caso, ma è una dipendenza che non può essere isolata se non con uno dei tre strumenti sopra elencati. Se non lo isolate, allora non state "test unitari", siete "test di integrazione" (perché state testando come le vostre diverse unità "si integrano" insieme.
Vaccano

3
Non succede nulla. Perché dovrebbe? Il framework .NET è pieno di metodi statici. Stai dicendo che non puoi usare questi in nessuno dei metodi che desideri test unitario?
Dan Diplo,

3
Bene, se il tuo codice è a livello di produzione / qualità di .NET Framework, allora procedi. Ma il punto centrale del test unitario è il test dell'unità, in isolamento. Se stai chiamando anche metodi in altre unità (siano esse statiche o meno), ora stai testando la tua unità più le sue dipendenze. Non sto dicendo che non è un test utile, ma non un "test unitario" per la maggior parte delle definizioni. (Perché ora state testando l'unità sotto test e l'unità che ha il metodo statico).
Vaccano,

10
Presumibilmente tu (o altri) avete già testato il metodo statico e dimostrato che funziona (almeno come previsto) prima di scrivere troppo codice attorno ad esso. Se qualcosa si rompe nella parte che testerai dopo, è lì che dovresti guardare prima, non nelle cose che hai già testato.
cHao,

6
@Vaccano Quindi come fa Microsoft a testare .NET Framework, eh? Molte delle classi nel Framework fanno riferimento a metodi statici in altre classi (come System.Math) per non parlare dell'abbondanza di metodi statici di fabbrica ecc. Inoltre non si sarebbe mai in grado di utilizzare alcun metodo di estensione ecc. Il fatto è che semplici funzioni come questa sono fondamentale per le lingue moderne. Puoi testarli separatamente (dato che saranno generalmente deterministici) e poi usarli nelle tue classi senza preoccupazioni. Non è un problema!
Dan Diplo,

18

Se la vera domanda qui è "Come testare questo codice?":

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Quindi, semplicemente refactoring il codice e iniettare come al solito la chiamata alla classe statica in questo modo:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}

9
Fortunatamente abbiamo anche domande sulla leggibilità del codice e KISS anche su SO :)
gbjbaanb

un delegato non sarebbe più semplice e farebbe la stessa cosa?
jk.

@jk potrebbe essere, ma è difficile usare il contenitore IoC allora.
Sunny

15

Le statistiche non sono necessariamente malvagie, ma possono limitare le tue opzioni quando si tratta di test unitari con falsi / mock / stub.

Esistono due approcci generali alla derisione.

Il primo (tradizionale - implementato da RhinoMocks, Moq, NMock2; in questo campo si trovano anche mock e tronconi manuali) si basa su cuciture di prova e iniezione di dipendenza. Supponiamo che tu stia testando un po 'di codice statico e abbia dipendenze. Ciò che accade spesso nel codice progettato in questo modo è che la statica crea le proprie dipendenze, invertendo l' inversione delle dipendenze . Scoprirai presto che non puoi iniettare interfacce derise nel codice sotto test progettato in questo modo.

Il secondo (tutto finto - implementato da TypeMock, JustMock e Moles) si basa sull'API di profilazione di .NET . Può intercettare una qualsiasi delle tue istruzioni CIL e sostituire un pezzo del tuo codice con un falso. Ciò consente a TypeMock e ad altri prodotti in questo campo di deridere qualsiasi cosa: statica, classi sigillate, metodi privati ​​- cose non progettate per essere testabili.

C'è un dibattito in corso tra due scuole di pensiero. Uno dice, seguire i principi SOLID e progettare per la testabilità (che spesso include andare piano con la statica). L'altro dice, compra TypeMock e non preoccuparti.


14

Dai un'occhiata a questo: "I metodi statici sono morte per testabilità" . Breve riassunto dell'argomento:

Per unit test è necessario prendere una piccola parte del codice, ricollegare le sue dipendenze e testarlo in modo isolato. Questo è difficile con i metodi statici, non solo nel caso in cui accedano allo stato globale ma anche se chiamano semplicemente altri metodi statici.


32
Non mantengo lo stato globale né causo effetti collaterali nei miei metodi statici, quindi questo argomento mi sembra irrilevante. L'articolo che hai collegato fa un argomento di pendenza scivoloso che non ha alcuna base se i metodi statici sono limitati a un semplice codice procedurale, agendo in modo "generico" (come le funzioni matematiche).
Robert Harvey,

4
@Robert Harvey - come testate un metodo che utilizza un metodo statico di un'altra classe (cioè ha una dipendenza da un metodo statico). Se lo lasci semplicemente chiamare, non stai "test unitari", sei "test di integrazione"
Vaccano

10
Non limitarti a leggere l'articolo del blog, leggi i molti commenti che non sono d'accordo con esso. Un blog è solo un'opinione, non un dato di fatto.
Dan Diplo,

8
@Vaccano: un metodo statico senza effetti collaterali o stato esterno è funzionalmente equivalente a un valore. Sapendo ciò, non è diverso dal test unitario di un metodo che crea un numero intero. Questa è una delle intuizioni chiave che ci offre la programmazione funzionale.
Steven Evers,

3
Escludendo il funzionamento dei sistemi embedded o un'app i cui bug hanno il potenziale per creare incidenti internazionali, IMO, quando la "testabilità" guida l'architettura, è stata distorta prima di adottare le metodologie test-last-last-corner. Inoltre sono stanco della versione XP di tutto ciò che domina ogni conversazione. XP non è un'autorità, è un'industria. Ecco la definizione originale e ragionevole di unit test: python.net/crew/tbryan/UnitTestTalk/slide2.html
Erik Reppen

5

La semplice verità raramente riconosciuta è che se una classe contiene una dipendenza visibile dal compilatore su un'altra classe, non può essere testata isolatamente da quella classe. Puoi falsificare qualcosa che assomiglia a un test e verrà visualizzato in un rapporto come se fosse un test.

Ma non avrà la chiave che definisce le proprietà di un test; fallendo quando le cose sono sbagliate, passando quando sono giuste.

Questo vale per qualsiasi chiamata statica, chiamata del costruttore e qualsiasi riferimento a metodi o campi non ereditati da una classe o interfaccia di base . Se il nome della classe appare nel codice, è una dipendenza visibile dal compilatore e non puoi validamente testare senza di essa. Qualsiasi pezzo più piccolo semplicemente non è un'unità testabile valida . Qualsiasi tentativo di trattarlo come se avesse risultati non più significativi della scrittura di una piccola utility per emettere l'XML utilizzato dal framework di test per dire "test superato".

Detto questo, ci sono tre opzioni:

  1. definire test unitari per testare l'unità composta da una classe e dalle sue dipendenze codificate. Funziona, a condizione di evitare dipendenze circolari.

  2. non creare mai dipendenze in fase di compilazione tra le classi di cui sei responsabile per il test. Funziona, a condizione che non ti dispiaccia lo stile di codice risultante.

  3. non test unitario, test di integrazione invece. Che funziona, a condizione che non sia in conflitto con qualcos'altro per cui devi usare il termine test di integrazione.


3
Non c'è nulla che dica che un unit test è un test di una singola classe. È un test di una singola unità. Le proprietà che definiscono i test unitari sono che sono veloci, ripetibili e indipendenti. Il riferimento Math.Piin un metodo non lo rende un test di integrazione secondo una definizione ragionevole.
Sara

cioè l'opzione 1. Sono d'accordo che sia l'approccio migliore, ma può essere utile sapere che altre persone usano i termini in modo diverso (ragionevolmente o no).
soru,

4

Non ci sono due modi per farlo. I suggerimenti di ReSharper e le varie utili funzioni di C # non verrebbero utilizzate così spesso se si scrivessero test di unità atomiche isolate per tutto il codice.

Ad esempio, se si dispone di un metodo statico e è necessario eliminarlo, non è possibile se non si utilizza un framework di isolamento basato sul profilo. Una soluzione alternativa compatibile con le chiamate consiste nel modificare la parte superiore del metodo per utilizzare la notazione lambda. Per esempio:

PRIMA:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

DOPO:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

I due sono compatibili con le chiamate. I chiamanti non devono cambiare. Il corpo della funzione rimane lo stesso.

Quindi nel tuo codice Unit Test, puoi stub questa chiamata in questo modo (supponendo che sia in una classe chiamata Database):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

Fai attenzione a sostituirlo con il valore originale dopo aver finito. Puoi farlo tramite un tentativo / finalmente o, nel tuo clean-test dell'unità, quello che viene chiamato dopo ogni test, scrivere un codice come questo:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

che richiamerà l'inizializzatore statico della tua classe.

I Lambda Func non sono così ricchi di supporto come i normali metodi statici, quindi questo approccio ha i seguenti effetti collaterali indesiderati:

  1. Se il metodo statico era un metodo di estensione, è necessario prima modificarlo in un metodo non di estensione. Resharper può farlo automaticamente per te.
  2. Se uno dei tipi di dati dei metodi statici è un assembly di interoperabilità incorporato, ad esempio Office, è necessario racchiudere il metodo, avvolgere il tipo o modificarlo per digitare "oggetto".
  3. Non è più possibile utilizzare lo strumento di refactoring per la firma delle modifiche di Resharper.

Ma supponiamo che tu eviti del tutto la statica e la converti in un metodo di istanza. Non è ancora derisorio a meno che il metodo non sia virtuale o implementato come parte di un'interfaccia.

Quindi, in realtà, chiunque suggerisca il rimedio per eliminare i metodi statici è di renderli metodi di istanza, sarebbero anche contro metodi di istanza che non sono virtuali o che fanno parte di un'interfaccia.

Quindi perché C # ha metodi statici? Perché consente metodi di istanza non virtuali?

Se si utilizza una di queste "Funzionalità", semplicemente non è possibile creare metodi isolati.

Quindi quando li usi?

Usali per qualsiasi codice che non ti aspetti che qualcuno voglia mai stub. Alcuni esempi: il metodo Format () della classe String il metodo WriteLine () della classe Console il metodo Cosh () della classe Math

E un'altra cosa ... La maggior parte delle persone non si preoccuperà di questo, ma se è possibile eseguire una chiamata indiretta, questo è un altro motivo per evitare i metodi di istanza. Ci sono casi in cui si tratta di un successo prestazionale. Ecco perché esistono in primo luogo metodi non virtuali.


3
  1. Credo che sia in parte dovuto al fatto che i metodi statici sono "più veloci" da chiamare rispetto ai metodi di istanza. (Tra virgolette perché puzza di micro ottimizzazione) vedi http://dotnetperls.com/static-method
  2. Ti sta dicendo che non ha bisogno di stato, quindi potrebbe essere chiamato da qualsiasi luogo, rimuovendo l'instatazione ambientale se questa è l'unica cosa di cui qualcuno ha bisogno.
  3. Se voglio deriderlo, allora penso che sia generalmente la pratica che viene dichiarata su un'interfaccia.
  4. Se è dichiarato su un'interfaccia, R # non ti suggerirà di renderlo statico.
  5. Se è dichiarato virtuale, R # non ti suggerirà nemmeno di renderlo statico.
  6. Mantenere staticamente (i campi) staticamente è qualcosa che dovrebbe essere sempre considerato attentamente . Stato statico e fili si mescolano come litio e acqua.

R # non è l'unico strumento che fornirà questo suggerimento. Anche l'analisi del codice FxCop / MS farà lo stesso.

Direi generalmente che se il metodo è statico, in genere dovrebbe essere testabile. Ciò porta un po 'di considerazione sul design e probabilmente più discussioni di quelle che ho tra le dita in questo momento, quindi aspetto pazientemente i voti negativi e i commenti ...;)


Puoi dichiarare un metodo / oggetto statico su un'interfaccia? (Penso di no). Modo eterico, mi riferisco a quando il tuo metodo statico viene chiamato dal metodo sotto test. Se il chiamante si trova in un'unità diversa, il metodo statico deve essere isolato. Questo è molto difficile da fare senza uno dei tre strumenti sopra elencati.
Vaccano,

1
No, non puoi dichiarare statico su un'interfaccia. Non avrebbe senso.
MIA,

3

Vedo che dopo molto tempo nessuno ha ancora dichiarato un fatto davvero semplice. Se il resharper mi dice che posso rendere statico un metodo, significa una cosa enorme per me, posso sentire la sua voce dirmi: "hey, tu, questi pezzi di logica non sono RESPONSABILITÀ della classe corrente da gestire, quindi dovrebbe rimanere fuori in una classe di aiuto o qualcosa del genere ".


2
Non sono d'accordo. Il più delle volte quando Resharper dice che posso rendere statico qualcosa è un po 'di codice che era comune a due o più metodi nella classe e quindi l'ho estratto dalla sua stessa procedura. Spostarlo su un aiutante sarebbe una complessità insignificante.
Loren Pechtel,

2
Vedo il tuo punto solo se il dominio è molto semplice e non è adatto per future modifiche. Diversamente, ciò che chiamate "complessità insignificante" è per me un design valido e leggibile dall'uomo. Avere una classe di supporto con una ragione semplice e chiara per esistere è in qualche modo il "mantra" dei principi di SoC e responsabilità singola. Inoltre, considerando che questa nuova classe diventa una dipendenza per quella principale, deve esporre alcuni membri pubblici e, di conseguenza, diventa testabile in isolamento e facile da deridere quando funge da dipendenza.
g1ga,

1
Non pertinente alla domanda.
Igby Largeman,

2

Se il metodo statico viene chiamato dall'altro metodo , non è possibile impedire o sostituire tale chiamata. Ciò significa che questi due metodi formano un'unica unità; Test unitari di qualsiasi tipo li testano entrambi.

E se questo metodo statico parla a Internet, collega i database, mostra i popup della GUI o converte in altro modo il test dell'unità in un casino completo, non fa altro che aggirare il problema. Un metodo che chiama tale metodo statico non è testabile senza refactoring, anche se ha un sacco di codice puramente computazionale che trarrebbe grande vantaggio dal test unitario.


0

Credo che Resharper ti dia prova e applichi le linee guida di codifica con cui è stato impostato. Quando ho usato Resharper e mi ha detto che un metodo dovrebbe essere statico, è destinato ad essere su un metodo privato che non agisce su nessuna variabile di istanza.

Ora per quanto riguarda la testabilità questo scenario non dovrebbe essere un problema in quanto non dovresti testare metodi privati ​​comunque.

Per quanto riguarda la testabilità dei metodi statici che sono pubblici, i test unitari diventano difficili quando i metodi statici toccano lo stato statico. Personalmente lo terrei al minimo e userò i metodi statici il più possibile come pure funzioni in cui tutte le dipendenze vengono passate al metodo che può essere controllato tramite un dispositivo di prova. Tuttavia questa è una decisione di progettazione.


Mi riferisco a quando hai un metodo sotto test (non statico) che chiama un metodo statico in un'altra classe. Tale dipendenza può essere isolata solo con uno dei tre strumenti sopra elencati. Se non lo si isola, si esegue il test di integrazione, non il test dell'unità.
Vaccano,

L'accesso intrinseco alle variabili di istanza significa che non può essere statico. L'unico stato che un metodo statico può avere è le variabili di classe e l'unica ragione che dovrebbe accadere è se hai a che fare con un singleton e quindi non hai scelta.
Loren Pechtel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.