Come testate i metodi privati ​​con NUnit?


104

Mi chiedo come utilizzare correttamente NUnit. Innanzitutto, ho creato un progetto di test separato che utilizza il mio progetto principale come riferimento. Ma in tal caso, non sono in grado di testare metodi privati. La mia ipotesi era che dovessi includere il mio codice di prova nel mio codice principale ?! - Non sembra essere il modo corretto per farlo. (Non mi piace l'idea di spedire il codice con dei test.)

Come testate i metodi privati ​​con NUnit?

Risposte:


74

Generalmente, il test unitario si rivolge all'interfaccia pubblica di una classe, sulla base della teoria che l'implementazione è irrilevante, a condizione che i risultati siano corretti dal punto di vista del cliente.

Quindi, NUnit non fornisce alcun meccanismo per testare i membri non pubblici.


1
+1 Mi sono appena imbattuto in questo problema e nel mio caso c'è un algoritmo di "mappatura" che avviene tra il significato privato e quello pubblico se dovessi Unit Test il pubblico, in realtà sarebbe un test di integrazione. In questo scenario penso che stia cercando di scrivere i punti di test su un problema di progettazione nel codice. in tal caso dovrei probabilmente creare una classe diversa che esegue quel metodo "privato".
andy

140
Non sono completamente d'accordo con questo argomento. I test unitari non riguardano la classe, ma il codice . Se avessi una classe con un metodo pubblico e dieci privati ​​utilizzati per creare il risultato di quello pubblico, non ho modo di garantire che ogni metodo privato funzioni nel modo previsto. Potrei provare a creare casi di test sul metodo pubblico che colpiscano il metodo privato giusto nel modo giusto. Ma poi non ho idea se il metodo privato sia stato effettivamente chiamato, a meno che non ritenga che il metodo pubblico non cambi mai. I metodi privati ​​sono intesi come privati ​​per gli utenti di produzione del codice, non per l'autore.
Quango

3
@Quango, in una configurazione di test standard, l '"autore" è un utente di produzione del codice. Tuttavia, se non senti l'odore di un problema di progettazione, hai opzioni per testare metodi non pubblici senza alterare la classe. System.Reflectionti consente di accedere e invocare metodi non pubblici utilizzando flag di associazione, in modo da poter hackerare NUnit o configurare il tuo framework. Oppure (più facile, penso), potresti impostare un flag in fase di compilazione (#if TESTING) per modificare i modificatori di accesso, permettendoti di utilizzare i framework esistenti.
arpione

23
Un metodo privato è un dettaglio di implementazione. Lo scopo principale degli unit test è consentire il refactoring dell'implementazione senza modificare il comportamento . Se i metodi privati ​​diventano così complicati che si vorrebbe coprirli con i test individualmente, allora si può usare questo lemma: "Un metodo privato può sempre essere un metodo pubblico (o interno) di una classe separata". Cioè, se un pezzo di logica è sufficientemente avanzato per essere sottoposto a test, è probabilmente candidato per essere la sua classe, con i suoi test.
Anders Forsgren

6
@AndersForsgren Ma il punto del test unitario , al contrario dell'integrazione o del test funzionale , è proprio quello di testare tali dettagli. E la copertura del codice è considerata un'importante metrica di test unitario, quindi in teoria ogni parte della logica è "sufficientemente avanzata per essere soggetta a test".
Kyle Strand

57

Sebbene sia d'accordo sul fatto che il focus del test unitario dovrebbe essere l'interfaccia pubblica, si ottiene un'impressione molto più granulare del codice se si testano anche metodi privati. Il framework di test MS lo consente attraverso l'uso di PrivateObject e PrivateType, NUnit no. Quello che faccio invece è:

private MethodInfo GetMethod(string methodName)
{
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("methodName cannot be null or whitespace");

    var method = this.objectUnderTest.GetType()
        .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} method not found", methodName));

    return method;
}

In questo modo non devi compromettere l'incapsulamento a favore della testabilità. Tieni presente che dovrai modificare i tuoi BindingFlags se vuoi testare metodi statici privati. L'esempio sopra è solo per i metodi di esempio.


7
Il test di questi piccoli metodi di supporto acquisisce la parte unitaria nel test unitario. Non tutti sono adatti neanche per le classi di utilità.
Johan Larsson,

12
Questo è esattamente ciò di cui avevo bisogno. Grazie! Tutto ciò di cui avevo bisogno era controllare che un campo privato fosse impostato correttamente e NON avevo bisogno di passare 3 ore per capire come determinarlo tramite l'interfaccia pubblica. Questo è codice esistente che non può essere modificato per capriccio. È divertente come si possa rispondere a una semplice domanda con un dogma idealistico ...
Droj

5
Questa dovrebbe essere la risposta selezionata, poiché è l'unica che risponde effettivamente alla domanda.
bavaza

6
Concordato. La risposta scelta ha i suoi meriti, ma è la risposta a "dovrei testare metodi privati", non la domanda dell'OP.
chesterbr

2
funziona. Grazie! Questo è ciò che molte persone stanno cercando. Questa dovrebbe essere la risposta selezionata,
Able Johnson

47

Un modello comune per la scrittura di unit test consiste nel testare solo metodi pubblici.

Se trovi che hai molti metodi privati ​​che vuoi testare, normalmente questo è un segno che dovresti refactoring del tuo codice.

Sarebbe sbagliato rendere pubblici questi metodi nella classe in cui vivono attualmente. Ciò infrangerebbe il contratto che vuoi che quella classe abbia.

Potrebbe essere corretto spostarli in una classe di aiuto e renderli pubblici lì. Questa classe potrebbe non essere esposta dalla tua API.

In questo modo il codice di prova non viene mai mischiato con il codice pubblico.

Un problema simile sta testando le classi private, ad es. classi che non esporti dal tuo assembly. In questo caso è possibile rendere esplicitamente il codice assembly di test un amico dell'assembly del codice di produzione utilizzando l'attributo InternalsVisibleTo.


1
+ 1 sì! Sono appena arrivato a questa conclusione mentre scrivevo i miei test, buon consiglio!
andy

1
Spostare metodi privati ​​che si desidera testare ma non esporre agli utenti dell'API a una classe diversa che non è esposta e renderli pubblici è esattamente quello che stavo cercando.
Thomas N

grazie @morechilli. Non aveva mai visto InternalsVisibleTo prima. Ha reso i test molto più semplici.
Andrew MacNaughton

Ciao. Recentemente ha ricevuto alcuni voti negativi senza commenti. Fornisci un feedback per spiegare i tuoi pensieri o preoccupazioni.
morechilli

6
Quindi, se hai un metodo privato che viene chiamato in più punti all'interno di una classe per fare qualcosa di molto specifico necessario solo a quella classe e non deve essere esposto come parte dell'API pubblica ... stai dicendo di metterlo in un helper classe solo per testare? Potresti anche renderlo pubblico per la classe.
Daniel Macias

21

È possibile testare metodi privati ​​dichiarando l'assembly di test come assembly amico dell'assembly di destinazione che si sta testando. Vedere il collegamento sottostante per i dettagli:

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx

Questo può essere utile in quanto per lo più separa il codice di test dal codice di produzione. Non ho mai usato questo metodo da solo perché non ne ho mai sentito la necessità. Suppongo che potresti usarlo per provare e testare casi di test estremi che semplicemente non puoi replicare nel tuo ambiente di test per vedere come il tuo codice lo gestisce.

Come è stato detto, però, non dovresti davvero aver bisogno di testare metodi privati. È più che probabile che tu voglia rifattorizzare il tuo codice in blocchi di costruzione più piccoli. Un suggerimento che potrebbe esserti d'aiuto quando ti rivolgi al refactoring è provare a pensare al dominio a cui si riferisce il tuo sistema e pensare agli oggetti "reali" che abitano questo dominio. I tuoi oggetti / classi nel tuo sistema dovrebbero riferirsi direttamente a un oggetto reale che ti permetterà di isolare il comportamento esatto che l'oggetto dovrebbe contenere e anche limitare le responsabilità degli oggetti. Ciò significa che stai effettuando il refactoring in modo logico piuttosto che solo per rendere possibile testare un metodo particolare; sarai in grado di testare il comportamento degli oggetti.

Se senti ancora la necessità di testare internamente, potresti anche prendere in considerazione la possibilità di prendere in giro i tuoi test poiché è probabile che tu voglia concentrarti su un pezzo di codice. Il mocking è dove si iniettano le dipendenze di un oggetto in esso, ma gli oggetti iniettati non sono gli oggetti "reali" o di produzione. Sono oggetti fittizi con comportamento hardcoded per semplificare l'isolamento degli errori comportamentali. Rhino.Mocks è un popolare framework gratuito di derisione che essenzialmente scriverà gli oggetti per te. TypeMock.NET (un prodotto commerciale con un'edizione comunitaria disponibile) è un framework più potente che può deridere gli oggetti CLR. Molto utile per deridere le classi SqlConnection / SqlCommand e Datatable, ad esempio, durante il test di un'app di database.

Speriamo che questa risposta ti fornisca qualche informazione in più per informarti sugli Unit Testing in generale e aiutarti a ottenere risultati migliori dagli Unit Testing.


12
È possibile testare metodi interni , non privati ​​(con NUnit).
TrueWill

6

Sono favorevole alla possibilità di testare metodi privati. Quando xUnit è stato avviato, era destinato a testare la funzionalità dopo la scrittura del codice. A tal fine è sufficiente testare l'interfaccia.

Il test unitario si è evoluto in uno sviluppo basato sui test. Avere la capacità di testare tutti i metodi è utile per quell'applicazione.


5

Questa domanda è negli anni avanzati, ma ho pensato di condividere il mio modo di farlo.

Fondamentalmente, ho tutte le mie classi di unit test nell'assembly che stanno testando in uno spazio dei nomi 'UnitTest' sotto il 'default' per quell'assembly: ogni file di test è racchiuso in un:

#if DEBUG

...test code...

#endif

block, e tutto ciò significa che a) non viene distribuito in una versione eb) posso usare le dichiarazioni internal/ Friendlevel senza saltare il cerchio.

L'altra cosa che offre, più pertinente a questa domanda, è l'uso di partialclassi, che possono essere utilizzate per creare un proxy per testare metodi privati, quindi per esempio per testare qualcosa come un metodo privato che restituisce un valore intero:

public partial class TheClassBeingTested
{
    private int TheMethodToBeTested() { return -1; }
}

nelle classi principali dell'assembly e nella classe di test:

#if DEBUG

using NUnit.Framework;

public partial class TheClassBeingTested
{
    internal int NUnit_TheMethodToBeTested()
    {
        return TheMethodToBeTested();
    }
}

[TestFixture]
public class ClassTests
{
    [Test]
    public void TestMethod()
    {
        var tc = new TheClassBeingTested();
        Assert.That(tc.NUnit_TheMethodToBeTested(), Is.EqualTo(-1));
    }
}

#endif

Ovviamente, devi assicurarti di non utilizzare questo metodo durante lo sviluppo, anche se una build di rilascio presto indicherà una chiamata involontaria ad esso se lo fai.


4

L'obiettivo principale dello unit test è testare i metodi pubblici di una classe. Quei metodi pubblici useranno quei metodi privati. I test unitari testeranno il comportamento di ciò che è disponibile pubblicamente.


3

Mi scuso se questo non risponde alla domanda, ma soluzioni come l'utilizzo di reflection, #if #endif o rendere visibili metodi privati ​​non risolvono il problema. Ci possono essere diversi motivi per non rendere visibili i metodi privati ​​... cosa succede se si tratta di codice di produzione e il team sta scrivendo in modo retrospettivo gli unit test, ad esempio.

Per il progetto su cui sto lavorando solo MSTest (purtroppo) sembra avere un modo, utilizzando le funzioni di accesso, per testare metodi privati.


2

Non provi le funzioni private. Esistono modi per utilizzare la riflessione per accedere a metodi e proprietà privati. Ma non è proprio facile e sconsiglio vivamente questa pratica.

Semplicemente non dovresti testare nulla che non sia pubblico.

Se disponi di alcuni metodi e proprietà interni, dovresti considerare di cambiarli in pubblico o di inviare i tuoi test con l'app (qualcosa che non vedo davvero come un problema).

Se il tuo cliente è in grado di eseguire una Test-Suite e vede che il codice che hai consegnato sta effettivamente "funzionando", non lo vedo come un problema (a patto che tu non dia via il tuo IP attraverso questo). Le cose che includo in ogni versione sono rapporti di prova e rapporti di copertura del codice.


Alcuni clienti cercano di mantenere un budget limitato e di avviare discussioni sull'ambito dei test. È difficile spiegare a queste persone che scrivere un test non è perché sei un cattivo programmatore e non ti fidi delle tue capacità.
MrFox

1

In teoria dello Unit Testing dovrebbe essere testato solo il contratto. cioè solo membri pubblici della classe. Ma in pratica lo sviluppatore di solito vuole testare i membri interni su. - e non è male. Sì, va contro la teoria, ma in pratica a volte può essere utile.

Quindi, se vuoi davvero testare i membri interni, puoi utilizzare uno di questi approcci:

  1. Rendi pubblico il tuo membro. In molti libri gli autori suggeriscono questo approccio come semplice
  2. Puoi renderti membri interni e aggiungere InternalVisibleTo da assemblare
  3. È possibile proteggere i membri della classe ed ereditare la classe di test dalla classe in fase di test.

Esempio di codice (pseudo codice):

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}

Sembra che lo scarafaggio sia nascosto nel codice di esempio;) Il metodo all'interno del dispositivo di NUnit deve essere pubblico, altrimenti si ottiene Message: Method is not public.
Gucu112

@ Gucu112 Grazie. L'ho riparato. In generale non importa poiché l'obiettivo è mostrare l'approccio dal punto di vista del design.
burzhuy

1

Puoi rendere i tuoi metodi protetti internamente e quindi utilizzarli assembly: InternalsVisibleTo("NAMESPACE") per il tuo spazio dei nomi di test.

Quindi, NO! Non puoi accedere ai metodi privati, ma questa è una soluzione.


0

Renderei visibile il pacchetto dei metodi privati. In questo modo lo manterrai ragionevolmente privato pur essendo in grado di testare quei metodi. Non sono d'accordo con le persone che dicono che le interfacce pubbliche sono le uniche che dovrebbero essere testate. C'è spesso codice veramente critico nei metodi privati ​​che non può essere adeguatamente testato passando solo attraverso le interfacce esterne.

Quindi si riduce davvero a se ti interessa di più il codice corretto o l'occultamento delle informazioni. Direi che la visibilità del pacchetto è un buon compromesso poiché per accedere a questi metodi qualcuno dovrebbe inserire la propria classe nel pacchetto. Questo dovrebbe davvero farli riflettere due volte sul fatto che sia davvero una cosa intelligente da fare.

Sono un ragazzo Java, btw, quindi la visibilità del pacchetto potrebbe essere chiamata qualcosa di completamente diverso in C #. Basti dire che è quando due classi devono trovarsi nello stesso spazio dei nomi per accedere a quei metodi.

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.