首页 文章

如何在单元测试中使用Moq调用同一类中的另一个方法

提问于
浏览
20

嗨,我是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 的调用?

4 回答

  • 15

    你不应该试图在你试图测试的类上模拟一个方法 . 模拟框架用于替换对您的类使用虚假调用所接受的依赖项的实际调用,以便您可以专注于测试类的行为,而不会被它具有的外部依赖性分散注意力 .

    Selecter 类没有外部依赖项,因此您不必测试实际代码本身 . 显然,为了保持测试的原子性,如果有的话,你需要模拟对外部依赖的调用 .

  • 1

    嘲笑被测试阶级的重点使你对实际问题视而不见 .

    来自被测课程的评论......

    '数据库将为我们提供车辆规格''外部制造系统获得轮胎规格'

    你实际上暴露了应该注入到类中的两个依赖项 .

    为了解释这个答案,我们可以说这些依赖关系看起来像这样 .

    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 需要能够从其他方法获得轮胎规格,因此该测试需要模拟两个依赖关系以使其完成 .

    [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 .

    在使用依赖于抽象成员的已实现成员测试抽象类时,您倾向于看到这一点 .

  • 7

    通常我们使用模拟外部依赖/在我们的类中使用的其他对象/接口调用,我们将编写单元测试 . 因此,当您为一个函数编写测试时,该函数内部调用同一个类中的另一个函数,您不必模拟该函数调用 . 但是在内部函数中,如果要调用外部接口,则必须模拟外部接口实例并编写具有预期结果的单元测试

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

相关问题