簡體   English   中英

如何在單元測試中使用Moq調用同一類中的另一個方法

[英]How to use Moq in unit test that calls another method in same class

嗨,我是Moq框架的新手,對如何使用它有一些疑問。 我將舉一個例子並希望得到答案。

我有兩個類,一個接口和一個實現:

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;
    }
}

我想為這些方法編寫兩個測試。 問題是當我為GetVehicleByRegistrationNumber編寫測試時,我不知道如何模擬對GetTyreSpecification的方法調用。

測試方法如下所示:

[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";
    }
}

在測試方法GetVehicleByRegistrationNumber_test如何模擬對getTyreSpecification的調用?

你不應該試圖在你試圖測試的類上模擬一個方法。 模擬框架用於替換對您的類使用虛假調用所接受的依賴項的實際調用,以便您可以專注於測試類的行為,而不會被它具有的外部依賴性分散注意力。

您的Selecter類沒有外部依賴項,因此您無需模擬任何內容。 如果你不需要並且測試實際的代碼本身,我總是提倡不要嘲笑。 顯然,為了保持測試的原子性,如果有的話,你需要模擬對外部依賴的調用。

嘲笑被測試階級的重點使你對實際問題視而不見。

來自被測課程的評論......

  • '數據庫將為我們提供車輛規格'
  • '外部制造系統獲得輪胎規格'

你實際上暴露了應該注入到類中的兩個依賴項。

為了解釋這個答案,我們可以說這些依賴關系看起來像這樣。

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

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

這意味着需要對Selecter進行重構以期望這些依賴關系。

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;
    }
}

從那里開始,只需要模擬顯式測試被測方法行為所需的依賴關系。

selecter.GetTyreSpecification無需訪問數據庫,因此沒有理由為測試模擬和注入它。

[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需要能夠從其他方法獲取輪胎規格,因此該測試需要selecter.GetVehicleByRegistrationNumber兩個依賴項,以便將其運用到完成。

[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);
}    

現在,如果例如Selecter類將GetVehicleByRegistrationNumber作為virtual方法,

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

有一種方法可以使用moq來存根測試中的主題並模擬該方法進行測試。 這並不總是最好的設計,被認為是代碼氣味。 但是,在某些情況下,您最終會遇到此特定情況。

[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);
} 

在上面的示例中,當selecter.Object.GetVehicleByRegistrationNumber(registrationNumber) ,將調用由mock包裝的基本Selecter ,然后調用模擬的GetTyreSpecification ,該模擬被測試的模擬主題上的設置覆蓋。

在使用依賴於抽象成員的已實現成員測試抽象類時,您傾向於看到這一點。

通常我們使用模擬外部依賴項/在我們的類中使用的其他對象/接口調用,我們將編寫單元測試。 因此,當您為一個函數編寫測試時,該函數在內部調用同一個類中的另一個函數時,您不必模擬該函數調用。 但是在內部函數中,如果要調用外部接口,則必須模擬外部接口實例並編寫具有預期結果的單元測試

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM