Jak mockować klasę bez interfejsu?

82

Pracuję na .NET 4.0 używając C # w Windows 7.

Chcę przetestować komunikację między niektórymi metodami za pomocą makiety. Jedynym problemem jest to, że chcę to zrobić bez implementacji interfejsu. Czy to jest możliwe?

Po prostu przeczytałem wiele tematów i kilka samouczków na temat pozorowanych obiektów, ale wszystkie z nich były używane do mockowania interfejsów, a nie klas. Próbowałem użyć frameworków Rhino i Moq.

Vinicius Seganfredo
źródło
1
Naprawdę gryzie, że te narzędzia są tworzone wyłącznie z perspektywy używania „interfejsów”.
AR
4
Są tworzone przy założeniu, że używasz interfejsu DI opartego na interfejsie. W dzisiejszych czasach jest to dość standardowy wzór.
Maess,
Niestety, ten wzorzec jest w konflikcie z "wzorcem" Immutable Type :(
Matthew Watson,
3
Istnieje wiele metod, które nie zostały tutaj wymienione w tej odpowiedzi
BlueRaja - Danny Pflughoeft

Odpowiedzi:

67

Po prostu oznacz dowolną metodę, którą chcesz udawać, jako virtual(a nie prywatną). Wtedy będziesz mógł stworzyć fałszywkę, która może zastąpić metodę.

Jeśli używasz new Mock<Type>i nie masz konstruktora bez parametrów, możesz przekazać parametry jako argumenty powyższego wywołania, ponieważ przyjmuje ono typparam Objects

Justin Pihony
źródło
1
Zastanawiam się, czy w ogóle konieczne jest tworzenie interfejsu dla każdej klasy, z której chcę kpić. Czy nie moglibyśmy po prostu użyć konkretnych klas do kpiny, jeśli nie mają interfejsu?
orad
14
@orad W rzeczywistości staram się najpierw tworzyć klasę, tworząc interfejs tylko wtedy, gdy potrzebuję rozbić typową funkcjonalność.
Justin Pihony
Jak następnie przekazać makietę do obiektu, który oczekuje określonego typu? Jeśli utworzę makietę „nowy Mock <MyType>” i spróbuję przekazać go do obiektu oczekującego MyType, otrzymuję błąd „Nie można przekonwertować Mock <MyType> na MyType”.
Neutrino
2
Rozpracowałem to, jeśli zdefiniujesz swój mock jako 'var myMock = new Mock <MyType> ()', musisz przekazać go do klasy, która używa makiety jako myMock.Object.
Neutrino
4
Główny problem pojawia się, gdy nie ma konstruktora bez parametrów, a sparametryzowany konstruktor w zamkniętej klasie Concrete nie jest publiczny. (Tutaj przez „zamknięte” mam na myśli opakowanie zewnętrzne). Wtedy nie można zaimplementować interfejsu, nie można uczynić tej metody wirtualną, a także nie można użyć sparametryzowanego konstruktora.
Abhilash Pokhriyal
22

Większość mockujących frameworków (w tym Moq i RhinoMocks) generuje klasy proxy jako substytut dla twojej mockowanej klasy i zastępuje metody wirtualne zachowaniem, które zdefiniujesz. Z tego powodu można tylko pozorować interfejsy lub metody wirtualne w klasach konkretnych lub abstrakcyjnych. Ponadto, jeśli mockujesz konkretną klasę, prawie zawsze musisz podać konstruktor bez parametrów, aby modelowa struktura wiedziała, jak utworzyć instancję klasy.

Skąd niechęć do tworzenia interfejsów w swoim kodzie?

matowe
źródło
107
Ponieważ zapycha bazę kodu tonami interfejsów, które, gdyby nie pewne techniczne ograniczenia frameworków testowych, byłyby całkowicie niepotrzebne?
BlueRaja - Danny Pflughoeft
9
Słyszałeś wyrażenie „zasięg przekracza chwyt”. To wyjaśnia, dlaczego programiści zawsze narzekają. Język C # nie został zaprojektowany z myślą o testowaniu jednostkowym. Dlatego naprawdę trudno jest wstrzyknąć pozorowane zależności bez ogromnego bałaganu bezcelowych interfejsów lub metod wirtualnych. Być może wkrótce ktoś wymyśli język, który będzie łatwy do przetestowania jednostkowego. Do tego czasu będziemy mieli jeszcze coś do zarzucenia.
John Henckel
13
Cieszę się, że nie jestem jedynym, który postrzega interfejsy i metody wirtualne jako bałagan, jeśli ich jedynym celem jest obsługa testów.
aaaaaa
13
„jedynym celem jest obsługa testów”, ale czy tworzenie testowalnego kodu nie jest dla Ciebie ważne?
MakkyNZ
13
„Skąd niechęć do tworzenia interfejsów w swoim kodzie?” Ponieważ nie napisałem tego obiektu i nie mam dostępu do kodu. Muszę utworzyć makietę zamkniętego obiektu, którego nie jestem właścicielem, a który nie implementuje interfejsu.
Sean Worle
16

Dzięki MoQ możesz kpić z konkretnych zajęć:

var mocked = new Mock<MyConcreteClass>();

ale to pozwala na przesłonięcie virtualkodu (metody i właściwości).

Roy Dictus
źródło
1
Próbowałem tego, ale kiedy uruchamiam projekt testowy, mój program zgłasza wyjątek: „Nie można utworzyć instancji serwera proxy klasy” „Nie można znaleźć konstruktora bez parametrów”.
Vinicius Seganfredo,
2
Po prostu przekaż parametry konstruktora do konstruktora Mock <>. Np.new Mock<MyConcreteClass>(param1, anotherParam, thirdParam, evenMoreParams);
Bozhidar Stoyneff
1
Dlaczego po prostu nie stworzyć interfejsu dla klasy?
Robert Perry,
11

Myślę, że lepiej jest stworzyć interfejs dla tej klasy. I utwórz test jednostkowy za pomocą interfejsu.

Jeśli nie masz dostępu do tej klasy, możesz utworzyć adapter dla tej klasy.

Na przykład:

public class RealClass
{
    int DoSomething(string input)
    {
        // real implementation here
    }
}

public interface IRealClassAdapter
{
    int DoSomething(string input);
}

public class RealClassAdapter : IRealClassAdapter
{
    readonly RealClass _realClass;

    public RealClassAdapter() => _realClass = new RealClass();

    int DoSomething(string input) => _realClass.DoSomething(input);
}

W ten sposób możesz łatwo utworzyć makietę dla swojej klasy za pomocą IRealClassAdapter.

Mam nadzieję, że to działa.

Anang Satria
źródło
4
To straszne z punktu widzenia konserwacji. Jeśli chcesz dodać nową metodę do RealClass, musisz dodać ją również do IReadClassAdapter, a także do RealClassAdapter. POTRÓJNY wysiłek! Lepszym rozwiązaniem jest dodanie słowa kluczowego „virtual” do każdej metody publicznej w RealClass.
John Henckel
12
@JohnHenckel Zakładam, że nie mamy dostępu do RealClass. Dlatego dodanie metody „wirtualnej” nie wchodzi w grę moim zdaniem. Jeśli masz dostęp do prawdziwej klasy, lepiej zaimplementować interfejs bezpośrednio do nowej klasy, myślę, że to najlepsza praktyka.
Anang Satria
Dzięki. Wydaje się, że jest to jedyne słuszne rozwiązanie do kpienia z klas bez dostępu. Nawet jeśli zawiera dużo „głupiego” kodu, potrzebuję go, aby uruchomić kod w „nieodpowiednim środowisku”
Michael Spannbauer
7

Standardowe struktury do mockowania tworzą klasy proxy. To jest powód, dla którego są technicznie ograniczone do interfejsów i metod wirtualnych.

Jeśli chcesz również naśladować „normalne” metody, potrzebujesz narzędzia, które współpracuje z instrumentacją zamiast generowania proxy. Np. MS Moles i Typemock mogą to zrobić. Ale ten pierwszy ma okropne „API”, a drugi jest komercyjny.

Thomas Weller
źródło
czy możemy kpić z normalnej klasy za pomocą tej metody publicznej?
SivaRajini,
7

Jeśli nie możesz zmienić testowanej klasy, jedyną opcją, którą mogę zasugerować, jest użycie MS Fakes https://msdn.microsoft.com/en-us/library/hh549175.aspx . Jednak MS Fakes działa tylko w kilku wersjach programu Visual Studio.

Rustem Zinnatullin
źródło
2

Jeśli jest gorzej, możesz utworzyć interfejs i parę adapterów. Zmieniłbyś wszystkie zastosowania ConcreteClass, aby zamiast tego używał interfejsu i zawsze przekazywał adapter zamiast konkretnej klasy w kodzie produkcyjnym.

Adapter implementuje interfejs, więc makieta może również implementować interfejs.

To bardziej tworzenie szkieletów niż tylko tworzenie wirtualnej metody lub po prostu dodawanie interfejsu, ale jeśli nie masz dostępu do źródła dla konkretnej klasy, może to wydostać cię z wiązania.

Tim Ottinger
źródło
1

Spotkałem się z czymś takim w jednym ze starych i starszych projektów, w którym pracowałem, który nie zawiera żadnych interfejsów ani najlepszych praktyk, a także jest zbyt trudne, aby wymusić na nich ponowne tworzenie rzeczy lub refaktoryzację kodu ze względu na dojrzałość biznesową projektu, więc w moim projekcie UnitTest tworzyłem Wrapper na klasach, które chcę mockować, i interfejs implementacji opakowania, który zawiera wszystkie moje potrzebne metody, które chcę skonfigurować i pracować z nimi. Teraz mogę wyszydzać opakowanie zamiast prawdziwej klasy.

Na przykład:

Usługa, którą chcesz przetestować, która nie zawiera metod wirtualnych ani interfejsu implementacji

public class ServiceA{

public void A(){}

public String B(){}

}

Wrapper do moq

public class ServiceAWrapper : IServiceAWrapper{

public void A(){}

public String B(){}

}

Interfejs Wrapper

public interface IServiceAWrapper{

void A();

String B();

}

W teście jednostkowym możesz teraz mockować opakowanie:

    public void A_Run_ChangeStateOfX()
    {
    var moq = new Mock<IServiceAWrapper>();
    moq.Setup(...);
    }

Może to nie jest najlepsza praktyka, ale jeśli reguły Twojego projektu narzucają Ci to w ten sposób, zrób to. Ponadto umieść wszystkie opakowania w projekcie testu jednostkowego lub projekcie pomocnika określonym tylko dla testów jednostkowych, aby nie przeciążać projektu niepotrzebnymi opakowaniami lub adapterami.

Aktualizacja: Ta odpowiedź od ponad roku, ale w tym roku spotkałem się z wieloma podobnymi scenariuszami z różnymi rozwiązaniami. Na przykład tak łatwo jest używać Microsoft Fake Framework do tworzenia mocków, fake i stubów, a nawet testowania prywatnych i chronionych metod bez żadnych interfejsów. Możesz przeczytać: https://docs.microsoft.com/en-us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes?view=vs-2017

Marzouk
źródło