Unit testing di metodi privati ​​in C #


291

Visual Studio consente il testing di unità di metodi privati ​​tramite una classe di accessori generata automaticamente. Ho scritto un test di un metodo privato che viene compilato correttamente, ma non riesce in fase di esecuzione. Una versione abbastanza minimale del codice e il test è:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

L'errore di runtime è:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Secondo intellisense - e quindi immagino che il compilatore - target sia di tipo TypeA_Accessor. Ma in fase di esecuzione è di tipo TypeA, quindi la lista aggiunge non riesce.

C'è un modo per fermare questo errore? O, forse più probabilmente, quale altro consiglio hanno gli altri (prevedo forse "non testare metodi privati" e "non avere unit test manipolano lo stato degli oggetti").


È necessario un accessor per la classe privata TypeB. Accessor TypeA_Accessor fornisce l'accesso a metodi privati ​​e protetti di TypeA. Tuttavia TypeB non è un metodo. È una classe.
Dima,

Accessor fornisce l'accesso a metodi, membri, proprietà ed eventi privati ​​/ protetti. Non fornisce accesso a classi private / protette all'interno della tua classe. E le classi private / protette (TypeB) sono intese per essere utilizzate solo da metodi proprietari della classe (TypeA). Quindi sostanzialmente stai provando ad aggiungere una classe privata (TypeB) dall'esterno di TypeA a "myList" che è privato. Dal momento che stai usando accessor, non c'è nessun problema per accedere a myList. Tuttavia, non è possibile utilizzare TypeB tramite Accessor. Una soluzione possibile sarebbe quella di spostare TypeB al di fuori di TypeA. Ma può rompere il tuo design.
Dima,

Senti che metodi di prova privati dovrebbe essere fatto dal seguente stackoverflow.com/questions/250692/...
nate_weldon

Risposte:


274

Sì, non testare metodi privati ​​.... L'idea di un unit test è testare l'unità con la sua "API" pubblica.

Se stai riscontrando la necessità di testare molti comportamenti privati, molto probabilmente hai una nuova "classe" nascosta all'interno della classe che stai provando a testare, estrarla e testarla tramite la sua interfaccia pubblica.

Un consiglio / strumento di pensiero ..... C'è l'idea che nessun metodo dovrebbe mai essere privato. Significa che tutti i metodi dovrebbero vivere su un'interfaccia pubblica di un oggetto .... se ritieni di aver bisogno di renderlo privato, molto probabilmente vive su un altro oggetto.

Questo consiglio non funziona abbastanza nella pratica, ma è per lo più un buon consiglio, e spesso spingerà le persone a scomporre i loro oggetti in oggetti più piccoli.


385
Non sono d'accordo. In OOD, i metodi e le proprietà private sono un modo intrinseco di non ripetere te stesso ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). L'idea alla base della programmazione e dell'incapsulamento della scatola nera è quella di nascondere i dettagli tecnici al sottoscrittore. Quindi è davvero necessario avere metodi e proprietà private non banali nel codice. E se non è banale, deve essere testato.
AxD

8
non è intrinseco, alcuni linguaggi OO non hanno metodi privati, le proprietà private possono contenere oggetti con interfacce pubbliche che possono essere testati.
Keith Nicholas,

7
Il punto di questo consiglio è che se il tuo oggetto fa una cosa e sei ASCIUTTO, allora spesso ci sono poche ragioni per avere metodi privati. Spesso i metodi privati ​​fanno qualcosa per cui l'oggetto non è realmente responsabile ma è abbastanza utile, se non banale, quindi è generalmente un altro oggetto in quanto è probabile che violi SRP
Keith Nicholas,

28
Sbagliato. È possibile che si desideri utilizzare metodi privati ​​per evitare la duplicazione del codice. O per la convalida. O per molti altri scopi che il mondo pubblico non dovrebbe conoscere.
Jorj

37
Quando sei stato scaricato su una base di codice OO così orribilmente progettata e ti è stato chiesto di "migliorarlo in modo incrementale", è stato una delusione scoprire che non potevo fare alcuni primi test per metodi privati. Sì, forse nei libri di testo questi metodi non sarebbero qui, ma nel mondo reale abbiamo utenti che hanno requisiti di prodotto. Non posso semplicemente fare un grosso refactoring "Guarda un codice pulito" senza ottenere alcuni test nel progetto da realizzare. Sembra un altro esempio di forzare i programmatori praticanti in strade che ingenuamente sembrano buone, ma non riescono a tenere conto della vera merda disordinata.
dune.rocks,

666

È possibile utilizzare la classe PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);

25
Questa è la risposta corretta, ora che Microsoft ha aggiunto PrivateObject.
Zoey,

4
Buona risposta, ma si noti che il metodo privato deve essere "protetto" anziché "privato".
HerbalMart

23
@HerbalMart: Forse ti fraintendo, ma se stai suggerendo che PrivateObject può accedere solo ai membri protetti e non a quelli privati, ti sbagli.
kmote

17
@JeffPearce Per i metodi statici è possibile utilizzare "PrivateType pt = new PrivateType (typeof (MyClass));", quindi chiamare InvokeStatic sull'oggetto pt come si chiamerebbe Invoke su un oggetto privato.
Steve Hibbert,

12
Nel caso qualcuno si stesse chiedendo di MSTest.TestFramework v1.2.1 - le classi PrivateObject e PrivateType non sono disponibili per i progetti destinati a .NET Core 2.0 - C'è un problema github per questo: github.com/Microsoft/testfx/issues/366
shiitake

98

"Non esiste nulla chiamato standard o best practice, probabilmente sono solo opinioni popolari".

Lo stesso vale anche per questa discussione.

inserisci qui la descrizione dell'immagine

Tutto dipende da ciò che pensi sia un'unità, se pensi che UNIT sia una classe, allora colpirai solo il metodo pubblico. Se ritieni che UNIT sia una linea di codice che colpisce metodi privati ​​non ti farà sentire in colpa.

Se si desidera invocare metodi privati, è possibile utilizzare la classe "PrivateObject" e chiamare il metodo invoke. Puoi guardare questo video di YouTube ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) che mostra come utilizzare "PrivateObject" e discute anche se il test di metodi privati ​​è logico o meno.


55

Un altro pensiero qui è quello di estendere i test a classi / metodi "interni", dando un senso più white-box di questo test. È possibile utilizzare InternalsVisibleToAttribute sull'assieme per esporli a moduli di test unità separati.

In combinazione con classe sigillata è possibile avvicinarsi a tale incapsulamento in modo che il metodo di prova sia visibile solo dall'assemblaggio unittest dei metodi. Considera che il metodo protetto in classe sigillata è di fatto privato.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

E unit test:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}

Non sono sicuro di capire. InternalsVisibleToAttribute rende accessibili metodi e attributi contrassegnati come "interni", ma i miei campi e metodi sono "privati". Stai suggerendo di cambiare le cose da privato a interno? Penso di fraintendere.
Junichiro,

2
Sì, è quello che sto suggerendo. È un po '"confuso", ma almeno non sono "pubblici".
Jeff

27
Questa è una risposta meravigliosa solo perché non dice "non testare metodi privati" ma sì, è abbastanza "confuso". Vorrei che ci fosse una soluzione. IMO è brutto dire "i metodi privati ​​non dovrebbero essere testati" perché per come la vedo io : è equivalente a "i metodi privati ​​non dovrebbero essere corretti".
MasterMastic,

5
yen ken, ho anche confuso da coloro che sostengono che i metodi privati ​​non dovrebbero essere testati nel test unitario. L'API pubblica è l'output, ma a volte un'implementazione errata fornisce anche l'output giusto. Oppure l'implementazione ha prodotto alcuni effetti collaterali negativi, ad esempio trattenendo risorse che non sono necessarie, facendo riferimento a oggetti che ne impediscono la raccolta da parte di gc ... ecc. A meno che non forniscano altri test in grado di coprire i metodi privati ​​piuttosto che unit test, altrimenti considererei che non possono mantenere un codice testato al 100%.
Mr.Pony il

Sono d'accordo con MasterMastic. Questa dovrebbe essere la risposta accettata.
XDS

27

Un modo per testare metodi privati ​​è attraverso la riflessione. Questo vale anche per NUnit e XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);

call methods statico e non statico ?
Kiquenet,

2
L'aspetto negativo dei metodi basati sulla riflessione è che tendono a rompersi quando si rinominano i metodi usando R #. Potrebbe non essere un grosso problema su piccoli progetti, ma su enormi basi di codice diventa un po 'fastidioso avere test unitari che si rompono in questo modo e quindi devono andare in giro e risolverli rapidamente. In questo senso, i miei soldi vanno alla risposta di Jeff.
XDS

2
@XDS Il nome nameof () non valido non funziona per ottenere il nome di un metodo privato dall'esterno della sua classe.
Gabriel Morin,

12

Ermh ... È venuto qui con esattamente lo stesso problema: prova un metodo privato semplice , ma fondamentale . Dopo aver letto questa discussione, sembra che sia "Voglio praticare questo semplice foro in questo semplice pezzo di metallo e voglio assicurarmi che la qualità soddisfi le specifiche", e poi arriva "Okay, non è così facile. Prima di tutto, non esiste uno strumento adeguato per farlo, ma potresti costruire un osservatorio con onde gravitazionali nel tuo giardino Leggi il mio articolo su http://foobar.brigther-than-einstein.org/ Prima di tutto, tu devi frequentare alcuni corsi avanzati di fisica quantistica, quindi hai bisogno di tonnellate di nitrogenium ultra-cool e poi, ovviamente, il mio libro disponibile su Amazon "...

In altre parole...

No, prima le prime cose.

Ogni metodo, sia esso privato, interno, protetto, pubblico deve essere testabile. Ci deve essere un modo per implementare tali test senza tali indugi come è stato presentato qui.

Perché? Esattamente a causa delle menzioni architettoniche fatte finora da alcuni collaboratori. Forse una semplice reiterazione dei principi del software potrebbe chiarire alcuni fraintendimenti.

In questo caso, i soliti sospetti sono: OCP, SRP e, come sempre, KIS.

Ma aspetta un minuto. L'idea di rendere tutto pubblicamente disponibile è più o meno politica e una sorta di atteggiamento. Ma. Quando si tratta di codice, anche nella comunità Open Source, questo non è un dogma. Invece, "nascondere" qualcosa è una buona pratica per rendere più facile familiarizzare con una determinata API. Nasconderesti, ad esempio, i calcoli fondamentali del tuo nuovo termometro digitale sul mercato - non per nascondere la matematica dietro la vera curva misurata ai lettori di codici curiosi, ma per impedire che il tuo codice diventi dipendente da alcuni, forse improvvisamente utenti importanti che non hanno potuto resistere all'utilizzo del codice precedentemente privato, interno e protetto per implementare le proprie idee.

Di cosa sto parlando?

privato double TranslateMeasurementIntoLinear (double actualMeasurement);

È facile proclamare l'Età dell'Acquario o quello che viene chiamato al giorno d'oggi, ma se il mio pezzo di sensore passa da 1.0 a 2.0, l'implementazione di Translate ... potrebbe cambiare da una semplice equazione lineare che è facilmente comprensibile e "ri utilizzabile "per tutti, per un calcolo piuttosto sofisticato che utilizza l'analisi o altro, e quindi romperei il codice degli altri. Perché? Perché non capivano i principi fondamentali della codifica software, nemmeno KIS.

Per farla breve: abbiamo bisogno di un modo semplice per testare metodi privati ​​- senza indugi.

Primo: buon anno a tutti!

Secondo: prova le tue lezioni di architetto.

Terzo: il modificatore "pubblico" è la religione, non una soluzione.


5

Un'altra opzione che non è stata menzionata è semplicemente la creazione della classe unit test come figlio dell'oggetto che si sta testando. Esempio NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Ciò consentirebbe una facile verifica dei metodi privati ​​e protetti (ma non ereditati privati) e ti permetterebbe di mantenere tutti i tuoi test separati dal codice reale in modo da non distribuire assiemi di test in produzione. Il passaggio da metodi privati ​​a metodi protetti sarebbe accettabile in molti oggetti ereditati ed è una modifica piuttosto semplice da apportare.

PERÒ...

Sebbene si tratti di un approccio interessante per risolvere il problema di come testare metodi nascosti, non sono sicuro di sostenere che questa sia la soluzione corretta al problema in tutti i casi. Sembra un po 'strano testare internamente un oggetto, e sospetto che potrebbero esserci alcuni scenari in cui questo approccio ti farà esplodere. (Oggetti immutabili, ad esempio, potrebbero rendere alcuni test davvero difficili).

Mentre menziono questo approccio, suggerirei che si tratta più di un suggerimento di brainstorming che di una soluzione legittima. Prendilo con un granello di sale.

EDIT: Trovo davvero divertente che le persone votino questa risposta, dal momento che descrivo esplicitamente questa come una cattiva idea. Significa che le persone sono d'accordo con me? Sono così confuso.....


È una soluzione creativa ma un po 'confusa.
shinzou,

3

Dal libro Lavorare efficacemente con il codice legacy :

"Se abbiamo bisogno di testare un metodo privato, dovremmo renderlo pubblico. Se renderlo pubblico ci preoccupa, nella maggior parte dei casi, significa che la nostra classe sta facendo troppo e che dovremmo risolverlo."

Il modo per risolverlo, secondo l'autore, è creare una nuova classe e aggiungere il metodo come public.

L'autore spiega ulteriormente:

"Un buon design è verificabile e un design non testabile è negativo".

Quindi, entro questi limiti, l'unica vera opzione è quella di creare il metodo public, sia nella classe corrente che in una nuova.


2

TL; DR: estrae il metodo privato in un'altra classe, test su quella classe; leggi di più sul principio SRP (Single Responsibility Principle)

Sembra che tu debba estrarre il privatemetodo in un'altra classe; in questo dovrebbe essere public. Invece di provare a provare il privatemetodo, dovresti testare il publicmetodo di questa altra classe.

Abbiamo il seguente scenario:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Dobbiamo testare la logica di _someLogic; ma sembra che Class Aassumano più ruolo del necessario (viola il principio SRP); refactoring in due classi

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

In questo modo someLogicpotrebbe essere testato su A2; in A1 basta creare un falso A2 quindi iniettare nel costruttore per verificare che A2 sia chiamato alla funzione denominata someLogic.


0

In VS 2005/2008 è possibile utilizzare l'accessorio privato per testare un membro privato, ma in questo modo era scomparso nella versione successiva di VS


1
Buona risposta nel 2008 forse all'inizio del 2010. Ora si prega di fare riferimento alle alternative di PrivateObject e Reflection (vedere diverse risposte sopra). VS2010 aveva bug di accesso, MS lo ha deprecato in VS2012. A meno che tu non sia costretto a rimanere in VS2010 o più vecchi (> 18 anni di strumenti di costruzione), risparmia tempo evitando accessi privati. :-).
Zephan Schroeder
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.