【问题标题】:How to test method in unit testing如何在单元测试中测试方法
【发布时间】:2020-03-30 11:23:21
【问题描述】:

我有如下service。假设我想测试Create() 方法。我在unit testing 中读到,我应该通过comparing, counting 进行测试,依此类推。然后我怎么能测试我的Create() 方法。将返回类型从 void Create 更改为 bool Create 只是为了能够检查方法输出以用于测试目的是否很难看,或者这不是理想的想法?你能提出一些建议吗?

public class CreateCarService : ICreateCarService
{
    private readonly ICarQuery _carQuery;
    private readonly ICarRepository _carRepository;

    public CreateCarService(ICarQuery carQuery, ICarRepository carRepository)
    {
        _carQuery = carQuery;
        _carRepository = carRepository;
    }

    public void Create(Car car)
    {
        if (car == null) throw new CusException(Error, "Car object cannot be null");

        if (_carQuery.IsLoginExist(car.Login))
            throw new CusException(Error, "Message1");

        if (_carQuery.IsEmailExist(car.Email))
            throw new CusException(Error, "Message1");

        _carRepository.Add(car);
    }
}

【问题讨论】:

  • 您可以使用模拟 carRepository 并检查它是否会运行 Add 方法
  • @PavelAnikhouski 是的,我知道如何模拟查询和存储库,但我的问题更像是如何测试方法是否正常工作。从我在单元测试中读到的方法可以返回一些东西,但在我的情况下是无效的。我应该不创建 void 方法,而是创建 bool 返回类型来检查方法吗?
  • 退后一步,问问自己你想在这里测试什么。你有这样的说法:ccs.Create(someCar);这个方法运行成功是什么意思?你对它有什么期望?如果您能够手动调用此方法,那么之后您将如何验证它是否完成了应有的操作?获得这些答案后,您就可以将这些期望形式化,以及如何将它们验证为代码。

标签: c# unit-testing nunit


【解决方案1】:

您可以通过设置IsLoginExistIsEmailExist 方法的Moq 行为并使用Verify 方法来验证任何有效的Car 实例Add 方法只调用一次

[TestFixture]
public class Test
{
    [Test]
    public void CreateCarServiceTest()
    {
        var carQueryMock = new Mock<ICarQuery>();
        var carRepositoryMock = new Mock<ICarRepository>();
        var createCarService = new CreateCarService(carQueryMock.Object, carRepositoryMock.Object);

        carQueryMock.Setup(c => c.IsLoginExist(It.IsAny<string>())).Returns(false);
        carQueryMock.Setup(c => c.IsEmailExist(It.IsAny<string>())).Returns(false);

        createCarService.Create(new Car());
        carRepositoryMock.Verify(c => c.Add(It.IsAny<Car>()), Times.Once);
    }
}

Create 方法抛出异常时,检查否定情况也很有意义

[Test]
public void CreateCarNegativeTest()
{
    var carQueryMock = new Mock<ICarQuery>();
    var carRepositoryMock = new Mock<ICarRepository>();
    var createCarService = new CreateCarService(carQueryMock.Object, carRepositoryMock.Object);

    Assert.Throws<CusException>(() => createCarService.Create(null));

    carQueryMock.Setup(c => c.IsLoginExist(It.IsAny<string>())).Returns(true);
    Assert.Throws<CusException>(() => createCarService.Create(new Car()));

    carQueryMock.Setup(c => c.IsLoginExist(It.IsAny<string>())).Returns(false);
    carQueryMock.Setup(c => c.IsEmailExist(It.IsAny<string>())).Returns(true);
    Assert.Throws<CusException>(() => createCarService.Create(new Car()));
}

您可以将此方法拆分为不同的测试,以便每个测试有一个Assert,或者将参数传递给它。

【讨论】:

  • 这是关键点,我已经读到在单元测试中我不应该检查例如是否达到了某些代码行(验证),否则我需要检查所有方法中的许多地方。从方法输出并检查比较计数或其他东西不是更好吗?这意味着例如,如果我得到预期的计数也达到了 Add ?看看这篇我和@Alireza stackoverflow.com/questions/60916220/…进行对话的帖子
  • @Arie IMO,这看起来不太好。您可以单独检查每个案例并与其他案例隔离,这是单元测试的基本目标。来自不同情况的混合输出可能会导致检查输出时出现一些陷阱
【解决方案2】:

您想测试被测成员的“预期行为”。由于被测成员不返回任何可验证的输出并且依赖于外部抽象,因此您应该能够监视被测成员与该外部抽象的交互并验证预期的行为

一个这样的例子

public void CreateCarService_Create_Should_Add_Car() {    
    //Arrange
    Car car = new Car {
        Login = "Login",
        Email = "Email"
    };

    ICarQuery carQuery = Mock.Of<ICarQuery>();
    ICarRepository carRepository = Mock.Of<ICarRepository>();

    ICreateCarService subject = new CreateCarService(carQuery, carRepository);

    //Act
    subject.Create(car);

    //Assert
    Mock.Get(carRepository).Verify(_ => _.Add(car), Times.Once);
}

上面的示例安全地导航到被测成员的末尾,但假设您想测试为 null 参数情况引发的异常。

public void CreateCarService_Create_Should_Throw_CusException_For_Null_Car() {    
    //Arrange        
    ICreateCarService subject = new CreateCarService(null, null);

    //Act
    Action act = ()=> subject.Create(null);

    //Assert
    var ex = Assert.Throws<CusException>(act);        
}

您想为通过被测成员的所有可能路径创建测试。因此,花点时间回顾一下被测主题并找出可能的测试用例。安排主题以满足这些情况并练习这些情况以验证预期的行为。

参考 Moq Quickstart 以更好地了解如何使用 Moq 模拟框架。

【讨论】:

  • 我同意检查 Throw 但验证是否到达了某些代码行似乎没用,否则对于某些方法,我需要检查每一行是否到达。不是更好地以某种方式检查方法,例如使用一些输出计数或任何如果计数正确也意味着添加也达到的方法。这正是我与@Alireza 交谈的重点:stackoverflow.com/questions/60916220/…
  • @Arie 我没有检查是否到达了某些行。我正在检查代码在给定不同输入的情况下是否按预期运行。如果被测对象没有提供输出来验证行为,那么您必须检查被测对象与什么交互以验证预期行为。
  • @Arie 上面提供的答案解决了您帖子中给出的具体示例。根据任何给定的场景,可能有不同的方法。没有一种硬性和快速的方法来做测试。这个话题太宽泛了,我不打算讨论什么被认为是无用或有用的,因为这是可以接受的。
【解决方案3】:

您无需将其更改为 bool,只需进行测试即可。一个简单的方法是:

        [TestFixture]
        public class Test
        {
          CreateCarService createCarService;
          ICarRepository carRepositoryMock; 
        [Setup]
        public void InitializeTest()
        {
            var carQueryMock = new Mock<ICarQuery>();
            carRepositoryMock = new Mock<ICarRepository>();
            createCarService = new CreateCarService(carQueryMock.Object, carRepositoryMock.Object);
        }

        [Test]
        public void CreateCarShouldThrowIfNull() 
        { 
          //arrange
          Car car =null;

         //act and assert
         Assert.Throw<CustomException>(()=>
         {
            createCarService.CreateCar(car);
         });
        }
        [Test]
        public void CreateCarShouldThrowForInvalidLogin() 
        { 
          //arrange
          var car = new Car()
         {
            Login=null,
            Email="Email"
         };

         //act and assert
         Assert.Throw<CustomException>(()=>
         {
            createCarService.CreateCar(car);
         });
        }

等等。

您可以将Assert.Throw 用于无效的汽车对象,或将Assert.DoesNotThrow 用于有效的汽车对象。最后,您可以通过以下方式测试汽车是否已添加到存储库:

  [Test]
    public void CreateCarShouldAddCarToRepo() 
    { 
      //arrange
      var car = new Car()
     {
        Login="Login",
        Email="Email"
     };

     //act
        createCarService.CreateCar(car);

      var carRetrieved =carRepositoryMock.GetCar(car.id);//depending on your implementation
      //assert
      Assert.AreSame(car,carRetrieved);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 2014-11-08
    • 1970-01-01
    相关资源
    最近更新 更多