2013-05-14 4 views
17

Witam Jestem nowy w Moq framework i mam kilka pytań na temat tego, jak go używać. Dam przykład i mam nadzieję na odpowiedzi.Jak używać Moq w teście jednostkowym, który wywołuje inną metodę w tej samej klasie?

mam dwie klasy, interfejs i i realizacja:

public class Vehicle{ 
    public string RegistrationNumber {get; set;} 
    public long VehicleIdentifier { get; set; } 
    public Tyre TyreSpecification { get; set; } 
} 

public class Tyre { 
    public long NumberOfTyres {get; set;} 
    public long TyreSize { get; set;} 
} 

public interface ISelecter { 
    Vehicle GetVehicleByRegistrationNumber(string registrationNumber); 
    Tyre GetTyreSpecification(long vehicleIdentifier); 
} 

public class Selecter : ISelecter 
{ 
    public Vehicle GetVehicleByRegistrationNumber(string registrationNumber) 
    { 
     var vehicle = 'Database will give us the vehicle specification'; 

     //Then we do things with the vehicle object 

     //Get the tyre specification 
     vehicle.TyreSpecification = GetTyreSpecification(vehicle.VehicleIdentifier); 

     return vehicle; 

    } 

    public Tyre GetTyreSpecification(long vehicleIdentifier) 
    { 
     var tyre = 'external manufacture system gets the tyre specification'; 

     //Then do thing with the tyre before returning the object 


     return tyre; 
    } 
} 

Chcę napisać dwa testy dla tych metod. Problem polega na tym, że piszę test dla GetVehicleByRegistrationNumber Nie wiem, jak wyśmiewać wywołanie metody do GetTyreSpecification.

Metody badań wyglądać następująco:

[TestClass] 
public class SelecterTest 
{ 
    [TestMethod] 
    public void GetTyreSpecification_test() 
    { 
     //Arrange 
     var tyre = new Tyre { NumberOfTyres = 4, TyreSize = 18 }; 

     var mockSelecter = new Mock<ISelecter>(); 
     mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre); 

     //Act 
     var tyreSpec = mockSelecter.Object.GetTyreSpecification(123456); 

     //Assert 
     Assert.IsTrue(tyreSpec.NumberOfTyres == 4 && tyreSpec.TyreSize == 18); 
    } 

    [TestMethod] 
    public void GetVehicleByRegistrationNumber_test() 
    { 
     //Arrange 
     var vehicle= new Vehicle { VehicleIdentifier = 123456, RegistrationNumber = ABC123, TyreSpecification = new Tyre { Tyresize = 18, NumberOfTyres = 4 }}; 

     var mockSelecter = new Mock<ISelecter>(); 
     mockSelecter.SetUp(s=>s.GetVehicleByRegistrationNumber(It.IsAny<string> ())).Returns(vehicle); 

     //Act 
     var vehicle = mockSelecter.Object.GetVehicleByregistrationNumber(123456); 

     //Assert 
     Assert.IsTrue(vehicle.Registrationnumber == "ABC123"; 
    } 
} 

W metodzie testowej GetVehicleByRegistrationNumber_test jak mogę mock wywołanie getTyreSpecification?

+0

@VSO o jakiejkolwiek opinii na temat udzielonej odpowiedzi? – Nkosi

+0

@Nkosi Hey Nkosi, nie ma szansy, aby to jeszcze sprawdzić, będę na to patrzeć jak najszybciej. Ty za pomoc jak zwykle! – VSO

+0

@Nkosi Twoje testy są dobre! Nie próbuj testować więcej niż to, co robi twój test - nie powinieneś testować niczego więcej niż otrzymywanie pojazdu według numeru reg. Wszelkie testy specyficzne dla pojazdu powinny być przechowywane we własnych metodach testowych, takich jak napisane powyżej. Nie testuj więcej niż jednej rzeczy naraz. –

Odpowiedz

14

Nie powinieneś próbować kpić z metody, którą próbujesz sprawdzić. Szokujące frameworki są używane w celu zastąpienia faktycznych połączeń wykonywanych z zależnościami, które twoja klasa przyjmuje z fałszywymi połączeniami, abyś mógł skupić się na testowaniu zachowania twojej klasy bez rozpraszania się przez zewnętrzne zależności, które ma.

Nie ma zewnętrznych zależności podjętych przez twoją klasę Selecter, więc nie musisz niczego drwić. Zawsze zalecałabym nie kpiny, jeśli nie trzeba i testować samego kodu. Oczywiście, aby zachować test atomowy, musiałbyś wyśmiewać wywołania zewnętrznych zależności, jeśli takie były.

+0

Rozumiem. Ale GetTyresSpecification ma rzeczy, których nie chcę wykonywać i kiedy testuję GetVehicleByRegistrationNumber Jest to uproszczona wersja problemu w prawdziwej aplikacji. 7 wywołań do zewnętrznego systemu jest dokonywanych w getTyreSpecification. Czy nie można sfałszować tej metody i pobierać tylko te dane, które chcę dla TyreSpecification? – user2227138

+1

Masz 2 alternatywy. Możesz kpić z zewnętrznych wywołań, które zdarzają się w metodzie 'getTyreSpecification' lub możesz wyciągnąć tę metodę do swojej własnej klasy, owinąć w interfejs i wstrzyknąć interfejs do swojej klasy' Selecter'. To pozwoliłoby ci na drwiny. – levelnis

+0

Ok, dziękuję za twoje odpowiedzi. Kpię z połączeń zewnętrznych. Oznacza to, że mam zduplikowany kod dla mojego testu metody GetTyresSpecification. Jeden fragment kodu w metodzie testowej metody GetTyresSpecification i jeden fragment kodu w metodzie GetVehicleByregistrationNumber, które wykonują dokładnie to samo. Poprawny? – user2227138

0
var mockSelecter = new Mock<ISelecter>{ CallBase = true }; 
mockSelecter.SetUp(s=>s.GetTyreSpecification(It.IsAny<long>())).Returns(tyre); 
+3

Rozwiąż swoją odpowiedź, aby dołączyć objaśnienie kodu. –

+0

Podczas korzystania z CallBase należy używać rzeczywistej klasy, a nie interfejsu. Wywołanie klasy bazowej interfejsu nie ma sensu. Powyższa metoda zadziała, jeśli używana jest prawdziwa klasa ORAZ metoda jest wirtualna. – TehTechGuy

3

Nacisk na kpiny z badanej klasy oślepił cię na rzeczywisty problem.

Z uwag w klasie badanego ...

  • 'Database dadzą nam specyfikację pojazdu'
  • 'system produkcja zewnętrzna pobiera specyfikację opon'

faktycznie ujawnić dwie zależności, które powinny zostać wstrzyknięte do klasy.

W celu wyjaśnienia tej odpowiedzi powiedzmy, że te zależności wyglądały tak.

public interface IDatabase { 
    Vehicle GetVehicleByRegistrationNumber(string registrationNumber); 
} 

public interface IExternalManufactureSystem { 
    Tyre GetTyreSpecification(long vehicleIdentifier); 
} 

Oznaczałoby to, że Selecter musiałyby być refactored się spodziewać tych zależności.

public class Selecter : ISelecter { 
    private IDatabase database; 
    private IExternalManufactureSystem externalManufactureSystem; 

    public Selecter(IDatabase database, IExternalManufactureSystem externalManufactureSystem) { 
     this.database = database; 
     this.externalManufactureSystem = externalManufactureSystem; 
    } 

    public Vehicle GetVehicleByRegistrationNumber(string registrationNumber) { 
     //'Database will give us the vehicle specification' 
     var vehicle = database.GetVehicleByRegistrationNumber(registrationNumber); 

     //Then we do things with the vehicle object 

     //Get the tyre specification 
     vehicle.TyreSpecification = GetTyreSpecification(vehicle.VehicleIdentifier); 

     return vehicle; 
    } 

    public Tyre GetTyreSpecification(long vehicleIdentifier) { 
     //'external manufacture system gets the tyre specification' 
     var tyre = externalManufactureSystem.GetTyreSpecification(vehicleIdentifier); 

     //Then do thing with the tyre before returning the object 

     return tyre; 
    } 
} 

Stamtąd należałoby kpić tylko z zależności wyraźnie potrzebnych do przetestowania zachowania testowanej metody.

selecter.GetTyreSpecification nie ma potrzeby dostępu do bazy danych, więc nie ma powodu, aby kpić i wstrzykiwać ją do testu.

[TestMethod] 
public void GetTyreSpecification_test() { 
    //Arrange 
    var vehicleIdentifier = 123456; 
    var expected = new Tyre { NumberOfTyres = 4, TyreSize = 18 }; 

    var mockSystem = new Mock<IExternalManufactureSystem>(); 
    mockSystem.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(expected); 

    var selecter = new Selecter(null, mockSystem.Object); 

    //Act 
    var actual = selecter.GetTyreSpecification(vehicleIdentifier); 

    //Assert 
    Assert.AreEqual(expected, actual); 
} 

selecter.GetVehicleByRegistrationNumber jednak musi być w stanie uzyskać specyfikację opon z drugiej metodzie więc ten test musiałby oba zależności wyśmiewany, aby mogła ona być sprawowana do ukończenia.

[TestMethod] 
public void GetVehicleByRegistrationNumber_test() { 
    //Arrange 
    var vehicleIdentifier = 123456; 
    var registrationNumber = "ABC123"; 
    var tyre = new Tyre { TyreSize = 18, NumberOfTyres = 4 }; 
    var expected = new Vehicle { 
     VehicleIdentifier = vehicleIdentifier, 
     RegistrationNumber = registrationNumber, 
     TyreSpecification = tyre 
    }; 

    var mockSystem = new Mock<IExternalManufactureSystem>(); 
    mockSystem.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(tyre); 

    var mockDatabase = new Mock<IDatabase>(); 
    mockDatabase.Setup(s => s.GetVehicleByRegistrationNumber(registrationNumber)).Returns(expected); 

    var selecter = new Selecter(mockDatabase.Object, mockSystem.Object); 

    //Act 
    var actual = selecter.GetVehicleByRegistrationNumber(registrationNumber); 

    //Assert 
    Assert.IsTrue(actual.RegistrationNumber == registrationNumber); 
}  

Teraz, że na uboczu, jeśli na przykład klasa Selecter miał GetVehicleByRegistrationNumber jako metody virtual,

public virtual Tyre GetTyreSpecification(long vehicleIdentifier) { 
    //...code removed for brevity. 
} 

Jest sposób można użyć MOQ stub przedmiotu badanego i wyśmiewać tę metodę do testowania. Nie zawsze jest to najlepszy projekt i jest uważany za zapach kodu. Są jednak sytuacje, w których zakończy się ten szczególny scenariusz.

[TestMethod] 
public void GetVehicleByRegistrationNumber_test2() { 
    //Arrange 
    var vehicleIdentifier = 123456; 
    var registrationNumber = "ABC123"; 
    var tyre = new Tyre { TyreSize = 18, NumberOfTyres = 4 }; 
    var expected = new Vehicle { 
     VehicleIdentifier = vehicleIdentifier, 
     RegistrationNumber = registrationNumber, 
     TyreSpecification = tyre 
    };   

    var mockDatabase = new Mock<IDatabase>(); 
    mockDatabase.Setup(s => s.GetVehicleByRegistrationNumber(registrationNumber)).Returns(expected); 

    var selecter = new Mock<Selecter>(mockDatabase.Object, null) { 
     CallBase = true //So that base methods that are not setup can be called. 
    } 

    selecter.Setup(s => s.GetTyreSpecification(vehicleIdentifier)).Returns(tyre); 

    //Act 
    var actual = selecter.Object.GetVehicleByRegistrationNumber(registrationNumber); 

    //Assert 
    Assert.IsTrue(actual.RegistrationNumber == registrationNumber); 
} 

W powyższym przykładzie, gdy selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) nazywa baza Selecter owinięte makiety zostanie wywołana, co z kolei następnie wywołać szydzili GetTyreSpecification które zostały zastąpione przez konfigurację w szydzili przedmiotu badanego.

Widać to podczas testowania klas abstrakcyjnych z zaimplementowanymi elementami członkowskimi, które mają zależności od abstrakcyjnych elementów.

+0

Przyznam ci nagrodę, zanim zapomnę i wygasa, i to jest dobra odpowiedź, ale myślę, że lepiej byłoby odpowiedzieć ogólnie. – VSO

+0

Wyjaśnij, co masz na myśli, mówiąc bardziej ogólnie, abym mógł zająć się tym bezpośrednio. – Nkosi

+0

Mam na myśli ogólny opis podejścia, a nie szczegółowe dane dotyczące tego pytania. Ogólna odpowiedź, którą można zastosować do dowolnego przypadku użycia. W tej chwili podałeś konkretny przypadek użycia i muszę ekstrapolować ogólne podejście z niego, a następnie zastosować je do mojego przypadku użycia. Lepiej byłoby tylko wyjaśnić ogólne podejście. Napraw to, jeśli chcesz.Wciąż pomagałeś i zasługujesz na nagrodę. – VSO

0

Witam generalnie używamy makiet dla zewnętrznych zależności/innych połączeń obiekt/interfejs używanych w naszej klasie, dla których będziemy pisać testy jednostkowe. Kiedy więc piszesz test na jedną z funkcji, która wewnętrznie wykonuje połączenie z inną funkcją w ramach tej samej klasy, nie musisz kpić z tej funkcji. Jednak w funkcji wewnętrznej, jeśli nawiązujesz połączenie z zewnętrznym interfejsem, będziesz musiał wyśmiać zewnętrzną instancję interfejsu i napisać swój test jednostkowy z oczekiwanym wynikiem: