2013-09-22 2 views
7

die folgende KlasseWie erstellt man eine Schnittstelle, die einige interne Methoden zum Testen in C# enthält? Betrachten

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

Wie Sie es sehen hat eine public Verfahren und eine internal Methode. Jetzt möchte ich eine Schnittstelle erstellen, die es mir erlaubt, diese Klasse in Tests (sowohl in dieser Assembly als auch in anderen) zu verspotten. Ich schreibe meinen Code wie folgt um:

public interface IEntity { 
    void Foo(); 
} 

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

Allerdings schafft dies ein anderes Problem. Wenn die Klasse in einem anderen Verfahren in der gleichen Baugruppe verwendet, kann ich nicht Bar mehr nennen:

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

den Code wie folgt Umschreiben:

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

einen Fehler in dem Unit-Test auslösen, die SomeMethod auslöst . Wie überschreibe ich meinen Code, so dass ich immer noch internal Methoden in derselben Assembly verwenden kann und trotzdem öffentliche Member anderen Assemblys zugänglich machen?

Update: Klasse OtherClass ist ein Utility-Klasse, die auf Interna der Entity Klasse arbeiten muss, die direkt an die Nutzer ausgesetzt sind, nicht. Diese Klasse selbst ist jedoch Benutzern zugänglich, so dass sie indirekt Zugriff auf Interna der Entity haben. Dies ist erwünscht, da SomeMethod notwendige Überprüfungen durchführt, um sicherzustellen, dass Benutzer den internen Zustand eines Entity Objekts nicht vermasseln.

+0

Warum machen Sie die Methoden nicht einfach virtuell, anstatt eine Schnittstelle zu erstellen? – adrianm

+0

@adrianm: Weil ich auch eine klare Schnittstelle für die Benutzer wünsche, nicht mit privaten/internen Mitgliedern und Implementierung verwechselt werden. Und auch, weil ich in Zukunft gerne eine andere Implementierung bereitstellen würde, ohne alle abhängigen Projekte neu aufzubauen. In Anbetracht all der Schwierigkeiten, die Schnittstellen im Umgang mit internen Mitgliedern verursachen, kann ich diese Option trotz der oben genannten Vorteile wählen. –

Antwort

4

Herausgegeben:

Erweitern Sie Ihre IEntity Schnittstelle mit einem internen ITestEntity Schnittstelle

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

Um dies zu testen Sie es so verspotten zum Testen:

public interface IEntity 
{ 
    //Implementation 

} 

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


class Entity: ITestEntity 
{ 
    // 
} 
+0

Das ist eine großartige Idee, die mir auch in den Sinn kam, aber es funktioniert nicht. Um Ihrem Beispiel zu meiner Frage zu entsprechen, wird 'IEntity'' Foo' und 'ITestEntity'' '' 'publizieren, was es erforderlich machen würde, die' ITestEntity' an die öffentliche 'SomeMethod' zu übergeben. Dies führt zu inkonsistenten Zugriffsfehlern. –

+1

Ich sehe. Aber wenn Sie "IEntity" weitergeben, können Sie in "ITestEntity" umwandeln (Sie sollten "ITestEntity" vortäuschen) und seine Methoden verwenden. Ich gebe zu, es wird dort weniger elegant. –

+0

Das hört sich richtig an ... Benutzer werden 'Mock ' nicht wirklich in 'SomeMethod' übergeben, da sie keinen Komponententest dafür schreiben. Stattdessen werden sie nur "Entity" passieren. Ich denke, das könnte das Problem lösen. Lass uns testen, ob das funktioniert und wenn ja, werde ich deine Antwort als akzeptiert markieren. –

2

Ich nehme an, Sie möchten die interne Methode nur in Ihren Unit-Tests richtig aufrufen? Andernfalls würde das Aussetzen der Methode gegenüber anderen Assemblies das Ersetzen von intern durch public erfordern.

Für Unit-Tests im Allgemeinen ist es immer ein gutes Muster, wenn Sie nicht private oder interne Methoden testen. Ein Unit-Test sollte eigentlich nur die öffentliche Schnittstelle testen und jede Business-Klasse sollte öffentliche Methoden bereitstellen, die intern alles tun ... Um also Ihre internen Methoden zu testen, müssten Sie die öffentliche Repräsentation testen.

Aber wie auch immer. Wenn Sie einen privaten oder internen Accessor testen möchten, können Visual Studio Test-Projekte in der Regel Accessor-Wrapper für Sie generieren. Sie müssten den Accessor ctor anstelle des normalen ctor Ihrer Klasse aufrufen. Wenn Sie dies tun, können Sie auf alle privaten oder internen Methoden/Eigenschaften dieser Instanz zugreifen.

Weitere Informationen zu diesen generierten Typen hier: http://msdn.microsoft.com/en-us/library/bb385974(v=vs.100).aspx

: edit: nach Absprache, ich habe ein Beispiel für Sie, wie moq verwenden zusammen mit Unity und UnityAutoMoq (3 verschiedene nugets). Können sagen, Ihre Klasse wie folgt aussieht:

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

Nein. Ich möchte die interne Methode auch in anderen Methoden in derselben Baugruppe aufrufen, die wiederum öffentlich sind. Und ich versuche nicht, die interne Methode zu testen - ich möchte nur darüber nachdenken, dass eine andere Klasse sie tatsächlich aufruft. –

+0

haben Sie versucht, das Attribut InternalsVisibleTo zu verwenden? http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx – MichaC

+0

Ich sehe nicht, wie 'InternalsVisibleTo' hilfreich ist. Ich möchte den Zugriff auf interne Elemente auf die Assembly beschränken, anstatt Zugriff auf andere Assemblys zu gewähren. Dies ist standardmäßig so, aber jetzt muss ich die Klasse hinter einer Schnittstelle zum Testen verstecken, die keinen internen Zugriffsbezeichner haben kann. –

13

Wie überschreibe ich meinen Code, so dass ich noch interne Methoden in derselben Assembly verwenden kann und trotzdem öffentliche Member anderen Assemblys zugänglich machen?

Machen Sie die Schnittstelle intern und implementieren Sie sie dann explizit.

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() { ... } 
} 

So, jetzt public class Thing hat nur eine öffentliche Methode Foo. Code außerhalb der Baugruppe kann Bar nicht direkt oder über eine Konvertierung an die Schnittstelle aufrufen, da Bar und ITest beide intern sind.

Ich nehme zur Kenntnis, dass eine Basisklasse muss als Berechnungs Klasse mindestens so sichtbar sein. Nicht so Schnittstellen. Nur wenige Menschen wissen, dass dies legal:

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

Eine Klasse eine Schnittstelle implementieren kann, die nur kann es sehen! Das ist etwas seltsam, aber es gibt Situationen, in denen es Sinn macht.

+1

Eine interessante Idee, aber jetzt können Benutzer 'Thing' Klasse nicht außerhalb der Baugruppe verspotten. Sie können Klassen unter Verwendung von 'Thing' schreiben und werden sie wahrscheinlich auch in Einheiten testen wollen. –

+6

@SergiyByelozyorov: Sie können es nicht beide Möglichkeiten haben. Entweder ist "Bar" außerhalb der Baugruppe zugänglich oder nicht. –

+0

Es sollte nicht außerhalb zugänglich sein. Die Benutzer müssen nur "Foo" vortäuschen, da sie nicht einmal von "Bar" wissen. –

Verwandte Themen