2013-09-22 12 views
7

Rozważmy następujące klasyJak utworzyć interfejs, który zachowuje niektóre metody wewnętrzne do testowania w języku C#?

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

Jak widać ma public sposób i metodę internal. Teraz chciałbym stworzyć interfejs, który pozwoli mi naśmiewać się z tej klasy w testach (zarówno w tym zespole, jak iw innych). Przepisuję mój kod w następujący sposób:

public interface IEntity { 
    void Foo(); 
} 

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

Jednak tworzy to kolejny problem. Przy użyciu klasy w inny sposób, w tym samym zespole nie mogę nazwać Bar już:

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

Przepisanie kodu tak:

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

spowoduje błąd w badanej jednostki, która wyzwala SomeMethod . Jak przerobić mój kod, aby nadal korzystać z metod internal w tym samym asemblowaniu, a jedynie wystawiać publicznych członków na inne zespoły?

Aktualizacja: Klasa OtherClass jest klasą narzędzie, które musi działać na wewnętrzne klasy Entity, które nie są narażone na użytkowników bezpośrednio. Jednak sama ta klasa jest narażona na działanie użytkowników, więc pośrednio mają dostęp do wewnętrznych elementów urządzenia Entity. Jest to pożądane, ponieważ SomeMethod przeprowadzi niezbędne kontrole w celu upewnienia się, że użytkownicy nie zepsują stanu wewnętrznego obiektu Entity.

+0

Dlaczego po prostu nie uczynisz metod wirtualnych zamiast tworzyć interfejs? – adrianm

+0

@adrianm: Ponieważ chcę również przejrzysty interfejs dla użytkowników, nie pomieszany z członkami prywatnymi/wewnętrznymi i implementacją. A także dlatego, że chciałbym móc zapewnić inną implementację w przyszłości bez odbudowy wszystkich zależnych projektów. Biorąc jednak pod uwagę wszystkie trudności, jakie stwarzają interfejsy w kontaktach z członkami wewnętrznymi, mogę wybrać tę opcję pomimo powyższych zalet. –

Odpowiedz

4

Zmieniano:

Rozszerzenie interfejsu IEntity z wewnętrznym interfejsem ITestEntity do testowania:

public interface IEntity 
{ 
    //Implementation 

} 

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


class Entity: ITestEntity 
{ 
    // 
} 
+0

To jest świetny pomysł, który również przyszedł mi do głowy, ale to nie działa. Aby dopasować twój przykład do mojego pytania, 'IEntity' eksponuje' Foo', a 'ITestEntity' eksponuje' Bar', co wymagałoby przekazania wewnętrznej 'ITestEntity' do publicznego' SomeMethod'. Powoduje to niespójny błąd dostępności. –

+1

Rozumiem. Ale jeśli przekażesz 'IEntity', możesz rzucić na' ITestEntity' (powinieneś kpić 'ITestEntity') i użyć jego metod. Przyznaję, że tam jest mniej elegancko. –

+0

To brzmi dobrze ... użytkownicy nie będą przekazywać 'Mock ' do 'SomeMethod', ponieważ nie będą pisać testów jednostkowych dla niego. Zamiast tego będą przekazywać tylko 'Entity'. Myślę, że to może faktycznie rozwiązać problem. Spełnijmy test, jeśli to działa, a jeśli tak, zaznaczę twoją odpowiedź jako zaakceptowaną. –

2

Zakładam, że chcesz wywołać metodę wewnętrzną tylko w swoich testach jednostkowych, prawda? W przeciwnym razie eksponowanie metody na inne złożenia wymagałoby zastąpienia wewnętrznego z publiczną przyczyną.

W przypadku testów jednostkowych ogólnie rzecz biorąc, zawsze jest dobry wzór, jeśli NIE testujesz metod prywatnych lub wewnętrznych. Test jednostkowy powinien po prostu przetestować interfejs publiczny, a każda klasa biznesowa powinna zapewnić publiczne metody, które robią wszystko wewnętrznie ... Aby przetestować swoje wewnętrzne metody, musiałbyś przetestować publiczną reprezentację tego.

Ale tak. Jeśli chcesz przetestować prywatny lub wewnętrzny akcesor, projekty Visual Studio Test zwykle mogą wygenerować dla Ciebie wrappery. Będziesz musiał zadzwonić do ctor dostępowego zamiast normalnego ctor klasy. Gdy to zrobisz, uzyskasz dostęp do prywatnych lub wewnętrznych metod/właściwości tej instancji.

Znajdź więcej informacji na temat tych generowanych typów tutaj: http://msdn.microsoft.com/en-us/library/bb385974(v=vs.100).aspx

: Edit: po dyskusji, mam dla was przykładem, w jaki sposób korzystać z MOQ razem z Unity i UnityAutoMoq (3 różni Nugets). Powiedzmy klasa wygląda następująco:

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

Aby to sprawdzić można drwić go tak:

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

Nie. Chcę nazwać metodę wewnętrzną innymi metodami w tym samym zespole, które są z kolei publiczne. I nie próbuję testować metody wewnętrznej - po prostu chcę ją wyśmiewać, aby upewnić się, że inna klasa faktycznie to nazywa. –

+0

czy próbowałeś użyć atrybutu InternalsVisibleTo? http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx – MichaC

+0

Nie widzę, w jaki sposób pomocne jest "InternalsVisibleTo". Chcę ograniczyć dostęp do elementów wewnętrznych do zespołu, zamiast udostępniać inne zespoły. Jest to domyślnie, ale teraz muszę ukryć klasę za interfejsem do testowania, która nie może mieć specyfikatora dostępu wewnętrznego. –

13

Jak przerobić mój kod, aby nadal używać wewnętrznych metod w tym samym zespole, a jednocześnie tylko wystawiać publicznych członków na inne zespoły?

Wykonaj interfejs wewnętrzny, a następnie jawnie go zaimplementuj.

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

Więc teraz publicznej klasy Thing ma tylko jedną metodę publiczną Foo. Kod spoza zestawu nie może wywoływać ani bezpośrednio, ani poprzez konwersję do interfejsu, ponieważ zarówno Bar, jak i ITest są wewnętrznymi.

Zauważam, że klasa podstawowa podstawowa klasa musi być co najmniej tak widoczna, jak klasa pochodna. Nie takie interfejsy. Niewiele osób wie, że to jest legalne:

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

klasa może implementować interfejs, który tylko może widzieć! To trochę dziwna rzecz, ale są sytuacje, w których ma to sens.

+1

Ciekawy pomysł, ale teraz użytkownicy nie mogą wykpić klasy 'Thing' poza złożeniem. Mogą pisać klasy za pomocą 'Thing' i prawdopodobnie również będą chcieli je przetestować. –

+6

@SergiyByelozyorov: Nie możesz mieć tego w obie strony. Albo 'Bar' jest dostępny poza złożeniem, albo nim nie jest. –

+0

Nie powinien być dostępny na zewnątrz. Użytkownicy będą musieli jedynie udawać "Foo", ponieważ nie wiedzą nawet o istnieniu 'Bar'. –

Powiązane problemy