Domanda

Sto costruendo una libreria di classi che avrà del pubblico & amp; metodi privati. Voglio essere in grado di testare unitamente i metodi privati ??(principalmente durante lo sviluppo, ma potrebbe anche essere utile per il futuro refactoring).

Qual è il modo corretto per farlo?

È stato utile?

Soluzione

Se si utilizza .net, è necessario utilizzare InternalsVisibleToAttribute .

Altri suggerimenti

Se vuoi testare un'unità di un metodo privato, qualcosa potrebbe non andare. I test unitari sono (generalmente parlando) intesi a testare l'interfaccia di una classe, ovvero i suoi metodi pubblici (e protetti). Ovviamente puoi " hack " una soluzione a questo (anche se solo rendendo pubblici i metodi), ma potresti anche considerare:

  1. Se vale davvero la pena provare il metodo che vorresti testare, potrebbe essere utile spostarlo nella sua classe.
  2. Aggiungi altri test ai metodi pubblici che chiamano il metodo privato, testando la funzionalità del metodo privato. (Come indicato dai commentatori, dovresti farlo solo se la funzionalità di questi metodi privati ??fa davvero parte dell'interfaccia pubblica. Se effettivamente eseguono funzioni che sono nascoste all'utente (cioè il test unitario), probabilmente è un male).

Potrebbe non essere utile testare metodi privati. Tuttavia, a volte mi piace anche chiamare metodi privati ??da metodi di prova. Il più delle volte al fine di prevenire la duplicazione del codice per la generazione di dati di test ...

Microsoft fornisce due meccanismi per questo:

accessor

  • Vai al codice sorgente della definizione della classe
  • Fai clic con il pulsante destro del mouse sul nome della classe
  • Scegli " Crea accesso privato "
  • Scegli il progetto in cui creare l'accessor = & Gt; Finirai con una nuova classe con il nome foo_accessor. Questa classe verrà generata dinamicamente durante la compilazione e premi tutti i membri disponibili al pubblico.

Tuttavia, a volte il meccanismo è un po 'intrattabile quando si tratta di modifiche all'interfaccia della classe originale. Quindi, la maggior parte delle volte evito di usarlo.

Classe PrivateObject L'altro modo è utilizzare Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

Non sono d'accordo con " dovresti essere interessato solo a testare l'interfaccia esterna " filosofia. È un po 'come dire che un'officina riparazioni auto dovrebbe avere solo test per vedere se le ruote girano. Sì, alla fine sono interessato al comportamento esterno, ma mi piace che i miei test interni, privati, siano un po 'più specifici e pertinenti. Sì, se refactoring, potrei dover cambiare alcuni dei test, ma a meno che non sia un refactor enorme, dovrò solo cambiarne alcuni e il fatto che gli altri test interni (invariati) funzionino ancora è un ottimo indicatore che il refactoring ha avuto successo.

Puoi provare a coprire tutti i casi interni usando solo l'interfaccia pubblica e teoricamente è possibile testare tutti i metodi interni (o almeno quelli che contano) interamente utilizzando l'interfaccia pubblica ma potresti dover finire in piedi sul tuo testa per raggiungere questo obiettivo e la connessione tra i casi di test eseguiti attraverso l'interfaccia pubblica e la parte interna della soluzione che sono progettati per testare può essere difficile o impossibile da discernere. Dopo aver sottolineato, i singoli test che garantiscono che il macchinario interno funzioni correttamente valgono le modifiche minori apportate al refactoring, almeno questa è stata la mia esperienza. Se devi apportare enormi cambiamenti ai tuoi test per ogni refactoring, allora forse questo non ha senso, ma in quel caso, forse dovresti ripensare completamente il tuo design. Un buon design dovrebbe essere abbastanza flessibile da consentire la maggior parte delle modifiche senza riprogettazioni di massa.

Nei rari casi in cui volevo testare le funzioni private, in genere le ho modificate per proteggerle e ho scritto una sottoclasse con una funzione wrapper pubblica.

La classe:

...

protected void APrivateFunction()
{
    ...
}

...

Sottoclasse per i test:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...

Penso che dovrebbe essere posta una domanda più fondamentale: perché stai provando a testare il metodo privato in primo luogo. Questo è un odore di codice che stai provando a testare il metodo privato attraverso l'interfaccia pubblica di quella classe mentre quel metodo è privato per una ragione in quanto è un dettaglio di implementazione. Bisogna preoccuparsi solo del comportamento dell'interfaccia pubblica e non di come viene implementato sotto le coperte.

Se voglio testare il comportamento del metodo privato, usando i refactoring comuni, posso estrarre il suo codice in un'altra classe (forse con visibilità a livello di pacchetto, quindi assicurati che non faccia parte di un'API pubblica). Posso quindi testare il suo comportamento da solo.

Il prodotto del refactoring significa che il metodo privato è ora una classe separata che è diventata un collaboratore della classe originale. Il suo comportamento sarà stato ben compreso attraverso i test unitari.

Posso quindi deridere il suo comportamento quando provo a testare la classe originale in modo da poter poi concentrarmi sul test del comportamento dell'interfaccia pubblica di quella classe piuttosto che dover testare un'esplosione combinatoria dell'interfaccia pubblica e il comportamento di tutti i suoi metodi privati.

Lo vedo analogo alla guida di un'auto. Quando guido una macchina, non guido con il cofano rivolto verso l'alto, così posso vedere che il motore funziona. Mi affido all'interfaccia fornita dall'auto, vale a dire il contagiri e il tachimetro per sapere che il motore funziona. Mi affido al fatto che l'auto si muove effettivamente quando premo il pedale del gas. Se voglio testare il motore posso fare controlli su questo in modo isolato. : D

Naturalmente testare direttamente i metodi privati ??può essere l'ultima risorsa se si dispone di un'applicazione legacy, ma preferirei che il codice legacy venga sottoposto a refactoring per consentire test migliori. Michael Feathers ha scritto un ottimo libro su questo argomento. http://www.amazon.co.uk/Working-Effectively-Legacy- Robert-Martin / dp / 0131177052

I tipi privati, interni e membri privati ??sono così per qualche motivo, e spesso non vuoi scherzare direttamente con loro. E se lo fai, è probabile che ti romperai più tardi, perché non vi è alcuna garanzia che i ragazzi che hanno creato quegli assemblaggi manterranno le implementazioni private / interne in quanto tali.

Ma, a volte, quando faccio alcuni hack / esplorazioni di assiemi compilati o di terze parti, ho finito per voler inizializzare una classe privata o una classe con un costruttore privato o interno. O, a volte, quando ho a che fare con librerie legacy precompilate che non posso cambiare - finisco per scrivere alcuni test con un metodo privato.

Nasce così AccessPrivateWrapper - http: //amazedsaint.blogspot .com / 2010/05 / accessprivatewrapper-c-40-dynamic.html - è una classe di wrapper rapido che renderà il lavoro più semplice grazie alle funzionalità dinamiche e alla riflessione di C # 4.0.

Puoi creare tipi interni / privati ??come

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();

Bene, puoi testare unitamente il metodo privato in due modi

  1. puoi creare un'istanza della classe PrivateObject la sintassi è la seguente

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
    
  2. Puoi usare la riflessione.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });
    

Ho anche usato il metodo InternalsVisibleToAttribute. Vale anche la pena ricordare che, se ti senti a disagio nel rendere interni i tuoi metodi precedentemente privati ??per raggiungere questo obiettivo, forse non dovrebbero comunque essere oggetto di test unitari diretti.

Dopotutto, stai testando il comportamento della tua classe, piuttosto che implementazione specifica - puoi cambiare quest'ultima senza cambiare la prima e i tuoi test dovrebbero comunque passare.

Esistono 2 tipi di metodi privati. Metodi privati ??statici e metodi privati ??non statici (metodi di istanza). I seguenti 2 articoli spiegano come testare unitamente metodi privati ??con esempi.

  1. Unit Testing Metodi privati ??statici
  2. Unit Testing Metodi privati ??non statici

MS Test ha una bella funzionalità integrata che rende disponibili membri e metodi privati ??nel progetto creando un file chiamato VSCodeGenAccessors

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

Con classi che derivano da BaseAccessor

come

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }

Su CodeProject, c'è un articolo che discute brevemente pro e contro del test di metodi privati. Fornisce quindi un codice di riflessione per accedere ai metodi privati ??(simile al codice fornito da Marcus sopra). L'unico problema che ho riscontrato con l'esempio è che il codice non tiene conto dei metodi sovraccarichi.

Puoi trovare l'articolo qui:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

Dichiarali interni , quindi usa InternalsVisibleToAttribute per consentire al gruppo di test delle unità di vederli.

Tendo a non usare le direttive del compilatore perché ingombrano rapidamente le cose. Un modo per mitigarlo se ne hai davvero bisogno è metterli in una classe parziale e fare in modo che la tua build ignori quel file .cs quando crei la versione di produzione.

In primo luogo non dovresti testare i metodi privati ??del tuo codice. Dovresti testare l '"interfaccia pubblica" o l'API, le cose pubbliche delle tue classi. Le API sono tutti i metodi pubblici che esponi a chiamanti esterni.

Il motivo è che una volta che inizi a testare i metodi privati ??e gli interni della tua classe, stai accoppiando l'implementazione della tua classe (le cose private) ai tuoi test. Ciò significa che quando decidi di modificare i dettagli dell'implementazione dovrai anche cambiare i tuoi test.

Per questo motivo dovresti evitare di usare InternalsVisibleToAtrribute.

Ecco un grande discorso di Ian Cooper che tratta questo argomento: Ian Cooper: TDD, dove è andato tutto storto

A volte, può essere utile testare le dichiarazioni private. Fondamentalmente, un compilatore ha un solo metodo pubblico: Compile (string outputFileName, params string [] sourceSFileNames). Sono sicuro che capirai che sarebbe difficile testare un tale metodo senza testare ciascuno "nascosto". dichiarazioni!

Ecco perché abbiamo creato Visual T #: per rendere più semplici i test. È un linguaggio di programmazione .NET gratuito (compatibile con C # v2.0).

Abbiamo aggiunto l'operatore '.-'. Si comporta semplicemente come '.' operatore, tranne che puoi anche accedere a qualsiasi dichiarazione nascosta dai tuoi test senza modificare nulla nel tuo progetto testato.

Dai un'occhiata al nostro sito web: download it gratuitamente .

Sono sorpreso che nessuno l'abbia ancora detto, ma una soluzione che ho usato è creare un metodo statico all'interno della classe per testarlo. Questo ti dà accesso a tutto ciò che è pubblico e privato con cui testare.

Inoltre, in un linguaggio di scripting (con capacità OO, come Python, Ruby e PHP), puoi eseguire il test del file stesso quando eseguito. Bel modo rapido per assicurarsi che le modifiche non abbiano interrotto nulla. Questo ovviamente rende una soluzione scalabile per testare tutte le tue classi: eseguile tutte. (puoi anche farlo in altre lingue con un vuoto principale che esegue sempre anche i suoi test).

Voglio creare qui un chiaro esempio di codice che puoi usare su qualsiasi classe in cui vuoi testare il metodo privato.

Nella tua classe di test case includi semplicemente questi metodi e poi impiegali come indicato.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this- > _callMethod ('_ someFunctionName', array (param1, param2, param3));

Emetti semplicemente i parametri nell'ordine in cui compaiono nella funzione privata originale

Per chiunque voglia eseguire metodi privati ??senza tutti i problemi. Funziona con qualsiasi framework di unit test usando nient'altro che un buon vecchio Reflection

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Quindi nei tuoi test reali, puoi fare qualcosa del genere:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");

MbUnit ha ottenuto un bel wrapper per questo chiamato Reflector.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

Puoi anche impostare e ottenere valori dalle proprietà

dogReflector.GetProperty("Age");

Per quanto riguarda il "test privato" Sono d'accordo che .. nel mondo perfetto. non ha senso fare test di unità private. Ma nel mondo reale potresti voler voler scrivere test privati ??invece di refactoring del codice.

Ecco un buon articolo sui test unitari di metodi privati. Ma non sono sicuro di cosa sia meglio, per farti un'applicazione progettata appositamente per i test (è come creare test solo per i test) o usare la riflessione per i test. Abbastanza sicuro che molti di noi sceglieranno la seconda strada.

Uso PrivateObject classe. Ma come accennato in precedenza meglio evitare di testare metodi privati.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
CC -Dprivate=public

" CC " è il compilatore della riga di comando sul sistema che uso. -Dfoo = bar fa l'equivalente di #define foo bar . Quindi, questa opzione di compilazione cambia efficacemente tutte le cose private in pubbliche.

Ecco un esempio, prima la firma del metodo:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Ecco il test:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}

Un modo per farlo è quello di avere il tuo metodo protetto e scrivere un dispositivo di prova che erediti la tua classe per essere testato. In questo modo, non stai né trasformando il tuo metodo public , ma attivi il test.

1) Se hai un codice legacy, l'unico modo per testare metodi privati ??è tramite la riflessione.

2) Se si tratta di un nuovo codice, hai le seguenti opzioni:

  • Usa la riflessione (per complicare)
  • Scrivi unit test nella stessa classe (rende brutto il codice di produzione con codice di test anche in esso)
  • Rifattorizza e rendi pubblico il metodo in una sorta di classe util
  • Utilizza l'annotazione @VisibleForTesting e rimuovi privato

Preferisco il metodo di annotazione, più semplice e meno complicato. L'unico problema è che abbiamo aumentato la visibilità che penso non sia una grande preoccupazione. Dovremmo sempre codificare per interfacciare, quindi se abbiamo un'interfaccia MyService e un'implementazione MyServiceImpl, allora possiamo avere le classi di test corrispondenti che sono MyServiceTest (metodi dell'interfaccia di prova) e MyServiceImplTest (metodi privati ??di prova). Tutti i client dovrebbero comunque utilizzare l'interfaccia in modo tale che, sebbene la visibilità del metodo privato sia stata aumentata, non dovrebbe importare davvero.

Potresti anche dichiararlo come pubblico o interno (con InternalsVisibleToAttribute) durante la creazione in modalità debug:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Blocca il codice, ma sarà privato in una build di rilascio.

Secondo me dovresti solo testare l'API pubblica della tua classe.

Rendere pubblico un metodo, al fine di testarlo, rompe l'incapsulamento esponendo i dettagli dell'implementazione.

Una buona API pubblica risolve un obiettivo immediato del codice client e lo risolve completamente.

È possibile generare il metodo di test per il metodo privato da Visual Studio 2008. Quando si crea un test di unità per un metodo privato, una cartella Riferimenti test viene aggiunta al progetto di test e un accessor viene aggiunto a quella cartella. L'accessorio viene anche indicato nella logica del metodo di test dell'unità. Questo accessorio consente all'unità di test di chiamare metodi privati ??nel codice che si sta testando. Per i dettagli, dai un'occhiata a

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

Si noti inoltre che InternalsVisibleToAtrribute richiede che l'assembly sia strong name , che crea il proprio set di problemi se si sta lavorando a una soluzione che non aveva avuto quel requisito prima. Uso l'accessor per testare metodi privati. Vedi questa domanda che per un esempio di questo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top