2013-09-22 18 views
7

Si consideri il seguente classeCome creare un'interfaccia che mantenga alcuni metodi interni per i test in C#?

public class Entity { 
    public void Foo() { ... } 
    internal void Bar() { ... } 
} 

come si vede ha un metodo public e un metodo di internal. Ora, vorrei creare un'interfaccia che mi permetta di prendere in giro questa classe nei test (sia in questo assembly che in altri). Riscrivo il mio codice come segue:

public interface IEntity { 
    void Foo(); 
} 

internal class Entity : IEntity { 
    public void Foo() { ... } 
    public void Bar() { ... } 
} 

Tuttavia, questo crea un altro problema. Quando si utilizza la classe in un altro metodo nella stessa assemblea non posso chiamare Bar più:

public class OtherClass { 
    public void SomeMethod(IEntity entity) { 
    entity.Bar(); // error! 
    } 
} 

Riscrivere il codice in questo modo:

public class OtherClass { 
    public void SomeMethod(IEntity entity) { 
    (entity as Entity).Bar(); // error in test! 
    } 
} 

attiveranno un errore nel test di unità che fa scattare SomeMethod . Come posso riscrivere il mio codice in modo da poter utilizzare ancora i metodi internal nello stesso assembly e tuttavia esporre solo membri pubblici ad altri assembly?

Aggiornamento: Classe OtherClass è una classe di utilità che ha bisogno di operare su interni della classe Entity, che non sono esposti agli utenti direttamente. Tuttavia, questa classe è esposta agli utenti, quindi hanno indirettamente accesso agli interni di Entity. Ciò è necessario poiché SomeMethod eseguirà i controlli necessari per garantire che gli utenti non rovinino lo stato interno di un oggetto Entity.

+0

Perché non si limitano a rendere virtuali i metodi anziché creare un'interfaccia? – adrianm

+0

@adrianm: Perché voglio anche un'interfaccia chiara per gli utenti, non confusa con membri privati ​​/ interni e implementazione. E anche perché mi piacerebbe essere in grado di fornire una diversa implementazione in futuro senza ricostruire tutti i progetti dipendenti. Tuttavia, considerando tutte le difficoltà, che le interfacce creano quando si hanno a che fare con membri interni, potrei scegliere questa opzione nonostante i suddetti vantaggi. –

risposta

4

Modificato:

Estendere l'interfaccia IEntity con un'interfaccia interna ITestEntity per il test:

public interface IEntity 
{ 
    //Implementation 

} 

internal interface ITestEntity : IEntity 
{ 
    void TestMethod(); 
} 


class Entity: ITestEntity 
{ 
    // 
} 
+0

Questa è una grande idea, che mi è venuta in mente, ma non funziona. Per abbinare il tuo esempio alla mia domanda, 'IEntity' esporrà' Foo' e 'ITestEntity' esporrà' Bar', che richiederebbe il passaggio di 'ITestEntity' al pubblico' SomeMethod'. Ciò causa un errore di accessibilità incoerente. –

+1

Capisco. Ma se si passa a 'IEntity', è possibile eseguire il cast su' ITestEntity' (si dovrebbe prendere in giro 'ITestEntity') e utilizzare i suoi metodi. Ammetto che qui diventa meno elegante. –

+0

Questo sembra giusto ... gli utenti non passeranno effettivamente 'Mock ' in 'SomeMethod' perché non scriveranno il test dell'unità per questo. Invece passeranno solo "Entità". Penso che questo possa effettivamente risolvere il problema. Incontriamoci se questo funziona e, in caso affermativo, segnerò la tua risposta come accettata. –

2

Suppongo che vogliate chiamare il metodo interno solo nei vostri test unitari, giusto? Perché altrimenti, esporre il metodo ad altri assembly richiederebbe la sostituzione interna con il pubblico della causa.

Per il test dell'unità in generale, è sempre un buon modello se NON si testano metodi privati ​​o interni. Un test unitario dovrebbe in realtà testare solo l'interfaccia pubblica e qualsiasi classe di business dovrebbe fornire metodi pubblici che facciano tutto internamente ... Quindi per testare i metodi interni, dovresti testarne la rappresentazione pubblica.

Ma comunque. Se si desidera verificare l'accesso privato o interno, i progetti di Test di Visual Studio in genere possono generare wrapper di accesso per te. Dovresti chiamare il responsabile dell'accessor invece del normale operatore della tua classe. Quando lo fai, puoi accedere a qualsiasi metodo/proprietà privato o interno di quell'istanza.

Trova maggiori informazioni su questi tipi generati qui: http://msdn.microsoft.com/en-us/library/bb385974(v=vs.100).aspx

: edit: dopo la discussione, ho un esempio per voi di come usare MOQ insieme con Unity e UnityAutoMoq (3 differenti Nugets). permette di dire la tua classe è simile al seguente:

public class MyClassWithInternalMethod 
{ 
    internal object GetSomething() 
    { 
     return null; 
    } 
} 

Per verificare questa si può deridere in questo modo:

using (var moqUnityContainer = new UnityAutoMoqContainer()) 
{ 
    moqUnityContainer.GetMock<MyClassWithInternalMethod>().Setup(p => p.GetSomething()).Returns(null); 
} 
+0

No. Voglio chiamare il metodo interno anche in altri metodi nello stesso assembly, che sono a loro volta pubblici. E non sto provando a testare il metodo interno - voglio solo deriderlo per assicurarmi che qualche altra classe lo chiami davvero. –

+0

hai provato a utilizzare l'attributo InternalsVisibleTo? http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx – MichaC

+0

Non vedo come 'InternalsVisibleTo' è utile. Voglio limitare l'accesso ai componenti interni all'assieme, piuttosto che consentire l'accesso ad altri assiemi. Questo è così di default, ma ora ho bisogno di nascondere la classe dietro un'interfaccia per il test, che non può avere un identificatore di accesso interno. –

13

Come riscrivo il mio codice in modo da poter utilizzare ancora metodi interni nello stesso assembly e tuttavia esporre solo membri pubblici ad altri assembly?

Rendere l'interfaccia interna e quindi implementarla esplicitamente.

internal interface ITest 
{ 
    void Foo(); 
    void Bar(); 
} 

public class Thing : ITest 
{ 
    void ITest.Foo() { this.Foo(); } 
    void ITest.Bar() { this.Bar(); } 
    public Foo() { ... } 
    internal Bar() { ... } 
} 

Così ora public class Thing ha un solo metodo pubblico Foo. Il codice all'esterno dell'assembly non può chiamare Bar direttamente o tramite una conversione all'interfaccia perché Bar e ITest sono entrambi interni.

Ho notato che una classe di base deve essere almeno così visibile come la sua classe derivante. Non così interfacce. Pochi sanno che questo è legale:

class C : C.I 
{ 
    private interface I {} 
} 

Una classe può implementare un'interfaccia che solo può vedere! Questa è una cosa un po 'strana da fare, ma ci sono alcune situazioni in cui ha senso.

+1

Un'idea interessante, ma ora gli utenti non possono prendere in giro la classe 'Thing' al di fuori dell'assembly. Potrebbero scrivere delle classi usando 'Thing' e probabilmente vorranno testare unitamente anche loro. –

+6

@SergiyByelozyorov: non puoi averlo in entrambi i modi. O 'Bar' è accessibile al di fuori dell'assieme o non lo è. –

+0

Non dovrebbe essere accessibile all'esterno. Gli utenti dovranno solo prendere in giro 'Foo' perché non sanno nemmeno dell'esistenza di' Bar'. –

Problemi correlati