2009-09-02 11 views
5

Diciamo che ho un progetto che è una libreria di classi. Ho una classe in quella libreria e questa classe ha alcuni metodi che sono usati solo all'interno della classe. In questo modo:Test delle unità e ambito degli oggetti: come testare metodi privati ​​/ interni ecc.?

public class MyClass 
{ 
    public void MyPublicMethod 
    { 
    int k 

    // do something ... 

    int z = MyInternalMethod(k); 
    // do something else ... 

    } 

    internal int MyInternalMethod(int i) 
    { 
     // do something ... 

    } 
} 

Ora voglio scrivere test di unità per questi metodi. Vorrei creare un progetto di "unit test", fare riferimento al NUnit da esso e scrivere qualcosa del genere

[TestFixture] 
public class UnitTests 
{ 
    private MyClass myClass; 

    [SetUp] 
    public void SetupTest 
    { 
    myClass = new MyClass(); 
    } 

    [Test] 
    public void TestMyInternalMethod 
    { 
    int z = 100; 
    int k = myClass.MyInternalMethod(z); //CAN NOT DO THIS! 
    Assert.AreEqual(k, 100000); 
    } 

    [TearDown] 
    public void TearDown 
    { 
    myClass = null; 
    } 
} 

Naturalmente, non posso fare questo, a causa della portata MyInternalMethod. Quale sarebbe il modo corretto di gestire questa situazione?

risposta

2

È possibile rendere gli interni visibili a determinati assiemi utilizzando InternalsVisibleToAttribute.

+0

Questa è la risposta che ha funzionato meglio per la mia situazione e ha richiesto solo l'aggiunta di una linea, una vera gemma. Qualcuno lo ha downvoted senza indicarne la ragione ... – Evgeny

+0

Sono curioso anche per il downvote. Ci sono degli svantaggi nel testare internals in questo modo rispetto ad un altro? –

+2

il link è morto –

-2

Mi piace mettere i miei test unitari nella stessa classe di quello che stanno testando. Questo ha due vantaggi, il primo è che risolve il problema che si sta colpendo, il secondo è che non si perde mai o si dimentica di loro come spesso accade se si trovano in un gruppo separato.

Non tutti sono d'accordo con questo tipo di approccio (si veda questo SO question che ho chiesto in precedenza) ma finora non ho trovato o ha avuto difetti in esso sottolineato a me. Ho fatto test unitari in quel modo per andare avanti da 4 o 5 anni.

#if UNITTEST 
using NUnit.Framework; 
#endif 

public class MyBlackMagic 
{ 
    private int DoMagic() 
    { 
     return 1; 
    } 

    #if UNITTEST 

    [TestFixture] 
    public class MyBlackMagicUnitTest 
    { 
     [TestFixtureSetUp] 
     public void Init() 
     { 
      log4net.Config.BasicConfigurator.Configure(); 
     } 

     [Test] 
     public void DoMagicTest() 
     { 
      Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); 
      Assert.IsTrue(DoMagic() == 1, "You are not a real magician!"); 
     } 
    } 

    #endif 
} 
+2

Cattivo, cattivo, cattivo - non farlo per favore. – Corehpf

+1

Ma dovrai fare riferimento a nunit.framework da uno dei progetti che hai in quel momento. Non sono sicuro che ciò abbia conseguenze come tempi di compilazione più lunghi, eseguibili più grandi, ma comunque sembra un po 'maldestro. – Evgeny

+0

@Corehpf - non stiamo discutendo di religione qui. Se hai una discussione contro qualcosa, fornitela. – sipwiz

4

Indovina dipende dalla tua idea di cosa sia un'unità, giusto? Generalmente scrivo test unitari per l'interfaccia accessibile e ignoro le cose private. Ho lavorato con persone che renderanno le cose private protette (java) per l'accesso al test di unità. Non mi piace questo approccio perché sacrifica la pulizia del design della classe per l'accesso al test.

+1

concordato. Userò InternalsVisibleTo a volte, spesso in modo da poter rendere certe classi interne ma comunque testarle. Non lo difenderò come buona pratica, anche se, in generale, testiamo l'interfaccia pubblica. Rende più facile il refactoring (meno possibilità che i test si interrompano). – TrueWill

2

Ecco un buon articolo su questo argomento:

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

io personalmente solo evitare di scrivere i metodi privati ​​che non fanno qualcosa di veramente complesso. Ci sono altri modi per incapsulare comportamenti che non vuoi esporre, pur continuando a darti la possibilità di testare le cose che dovrebbero essere nascoste. Penso che ci sia un compromesso tra incapsulamento perfetto e testabilità. L'incapsulamento perfetto è difficile da ottenere, e di solito è più vantaggioso darti più informazioni sulle lezioni. Questo potrebbe essere discutibile.

0

Visual Studio può generare accessors privati ​​per te. Controlla Unit Tests for Private, Internal, and Friend Methods su MSDN. Credo che VS2005 abbia semplicemente generato e aggiunto lezioni di accessor private al progetto di test dell'unità. Quindi hai dovuto rigenerarli quando le cose sono cambiate. Tuttavia, VS2008 genera un assembly di accesso privato e lo rende disponibile per il progetto di test dell'unità. Stai usando NUnit ma penso che dovrebbe essere ok. Controlla. In questo modo puoi mantenere il tuo codice reale libero da qualsiasi codice di test e/o hack.

0

In passato ho creato fixture di test nello stesso spazio dei nomi e assembly della classe in prova per testare i metodi interni. Non sto dicendo se i metodi interni dovrebbero essere testati o meno. Pragmaticamente, potresti testarli poi refactoring più tardi.

Ho anche creato delle classi parziali per testare i metodi privati ​​e ho usato una direttiva del compilatore sull'intera parte (che era nel suo stesso file). Di nuovo, non dicendo che questo è il migliore, ma a volte è necessario andare avanti.

Al momento della compilazione potremmo eseguire i test unitari Debug o modalità di rilascio, e potremmo striscia il codice di prova sia da costruire se desiderato, quindi non c'era danno nel mettere il codice di prova con il codice in esame; semmai, è simile per argomento a code-and-data-together = object o object-and-doc-comments = documented-object. In altre parole: codice-e-dati-e-test-e-doc-commenti-insieme = unità coesiva.

Un tempo aggiuntivo durante la costruzione era trascurabile.

3

ho appena provato i metodi pubblici (e non mi interessa le metriche di copertura, mi interessa le funzionalità che funzionano).

nota che se i metodi pubblici non utilizzano i metodi interni, non è necessario che i metodi interni esistano!

1

Molte persone diranno che non è necessario testare i metodi interni, ma testarli tramite l'API pubblica. Indipendentemente da ciò, puoi usare la reflection se vuoi davvero accedere a questi membri privati.

0

Ho usato un paio di metodi per farlo. Ho reso i miei metodi privati ​​protetti in modo tale che io possa ereditare dalla classe nel mio test unitario e creare una classe "helper" per testare questi metodi. L'altro è riflessione. La riflessione è più semplice, ma non va contro il modo in cui le classi dovrebbero essere progettate per essere testate. Ecco una versione semplificata di ciò di cui sto parlando.

public static class ReflectionHelper 
{ 
    public static object RunStaticMethod<TInstance>(string methodName, params object[] methodParams) 
    { 
     var methodType = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; 
     return RunMethod<TInstance>(null, methodName, methodType, methodParams); 
    } 

    public static object RunInstanceMethod<TInstance>(this TInstance instance, string methodName, params object[] methodParams) 
    { 
     var methodType = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 
     return RunMethod<TInstance>(instance, methodName, methodType, methodParams); 
    } 

    private static object RunMethod<TInstance>(object instance, string methodName, BindingFlags methodType, params object[] methodParams) 
    { 
     var instanceType = typeof(TInstance); 
     var method = instanceType.GetMethod(methodName, methodType); 
     if (method == null) 
     { 
      throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", methodName, instanceType)); 
     } 

     var result = method.Invoke(instance, methodParams); 

     return result; 
    } 
} 

Data una classe come questa

public class User 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 

    internal string GetPrettyName() 
    { 
     return string.Concat(FirstName, " ", LastName); 
    } 

    static internal int GetSystemId(string userName) 
    { 
     // some magic here 
     return 13; 
    } 
} 

devi usare questi come in modo

var user = new User { FirstName = "Peter", LastName = "Gibbons" }; 

var name = user.RunInstanceMethod("GetPrettyName"); 
Assert.That(name, Is.EqualTo("Peter Gibbons")); 

var id = ReflectionHelper.RunStaticMethod<User>("GetSystemId", "tester"); 
Assert.That(id, Is.EqualTo(13)); 
1
Ci

di due casi: o i metodi privati ​​ottenere chiamato da qualche metodo pubblico, in nel qual caso puoi testarli attraverso quel metodo. Oppure, non vengono richiamati da un metodo pubblico, in cui non possono essere chiamati affatto, sono codice morto e devono essere eliminati, non testati.

Nota che se stai facendo TDD, i metodi privati ​​possono nascere solo estraendoli da metodi pubblici, nel qual caso sono già stati testati automaticamente.