Unit testing 单元测试中的最小质量

Unit testing 单元测试中的最小质量,unit-testing,moq,Unit Testing,Moq,让我们假设我有一个服务 public interface IAreaService { int CalculateArea(int x,int y); int CalculateAreaTimesX(int x, int y, int ammount); } public class AreaService : IAreaService { public int CalculateArea(int x,int y) { return x*y;

让我们假设我有一个服务

public interface IAreaService
{
   int CalculateArea(int x,int y);
   int CalculateAreaTimesX(int x, int y, int ammount);
}

public class AreaService : IAreaService
{
    public int CalculateArea(int x,int y)
    {
        return x*y;
    }

    public int CalculateAreaTimesX(int x, int y, int ammount)
    {
        return CalculateArea(x, y)*ammount;
    }
}
通过相关的单元测试

[TestMethod]
    public void AreaService_GetArea_Test()
    {
        AreaService service = new AreaService();
        int expected = 9;
        int actual  = service.CalculateArea(3, 3);
        Assert.AreEqual(expected,actual,"The Calculate area does not work as expected");
    }

    [TestMethod]
    public void AreaService_GetAreaMultiplyByX_TestTrueValue()
    {
        AreaService service = new AreaService();
        int expected = 27;
        int actual = service.CalculateAreaTimesX(3, 3, 3);
        Assert.AreEqual(expected,actual);
    }
好的,在运行单元测试之后。我确信我的方法是工作,生活应该是美好的

但是现在我想在另一个类中使用IAreaService,这就是我失去希望的地方。下面是另一个类的实现

public class PriceCalculatorService
{
    private readonly IAreaService _areaService;
    public PriceCalculatorService(IAreaService areaService)
    {
        _areaService = areaService;
    }


    public double GetPrice(int x, int y, int times, double price)
    {
       return  _areaService.CalculateAreaTimesX(x, y, times)*price;
    }
}
如果我运行下面的单元测试(我的想法在这里可能是错误的,这就是问题所在)

[TestMethod]
    public void PriceCalculatorService_GetPrice_Test()
    {
        var IAreaServiceMock = new Mock<IAreaService>();
        IAreaServiceMock.Setup(ism => ism.CalculateAreaTimesX(2, 2, 2)).Returns(8);
        PriceCalculatorService priceCalc = new PriceCalculatorService(IAreaServiceMock.Object);

        double expected = 20;
        double actual = priceCalc.GetPrice(2, 2, 2, 2.50);
        Assert.AreEqual(expected,actual);
    }
这意味着我的单元测试“AreaService\u GetArea\u test()”将失败,就像它应该失败一样,但由于“PriceCalculatorService\u GetPrice\u test()”测试中使用的模拟仍然会通过,因为当你模拟一个服务时,似乎没有使用实际的代码(显然) PriceCalculatorService\u GetPrice\u测试是无用的。但是我使用了一个存根,那么单元测试就会失败,因为它应该失败

那么什么时候嘲笑,什么时候不嘲笑

所以我的价格计算器服务价格测试是无用的

不,不是。它正在测试
GetPrice
的代码,只有
GetPrice
的代码对我来说是合理的

这种方法没有太多需要测试的地方,但你正在测试它。如果你没有三次使用相同的参数(例如,使用1、2、3会更好),那会更好,但你正在测试它

没有理由仅仅因为接口的一个实现发生了更改而导致该测试中断。您在
AreaService\u GetArea\u test()
中捕捉到了对
AreaService
的更改,这确实是您应该捕捉到的

所以我的价格计算器服务价格测试是无用的

不,不是。它正在测试
GetPrice
的代码,只有
GetPrice
的代码对我来说是合理的

这种方法没有太多需要测试的地方,但你正在测试它。如果你没有三次使用相同的参数(例如,使用1、2、3会更好),那会更好,但你正在测试它


没有理由仅仅因为接口的一个实现发生了更改就中断该测试。您在
AreaService\u GetArea\u test()中捕获到了对
AreaService
的更改
,这确实是你应该抓住它的地方。

PriceCalculatorService\u GetPrice\u Test
正在测试
GetPrice
。它正在测试
GetPrice
的返回值是否是
\u区域服务。CalculateAreaTimesX(x,y,times)
乘以
价格
。这就是测试的意义。因此它一点也不无用


GetPrice
应该取决于
CalculateAreaTimesX
的结果,而不是该方法的逻辑。因此,无论模拟返回什么,都可以进行测试。

PriceCalculatorService\u GetPrice\u test
正在测试
GetPrice
。测试
GetPrice
的返回值是否为
.CalculateAreaTimesX(x,y,times)
乘以
price
。这就是测试的意义。因此它一点都没有用处


GetPrice
应该取决于
CalculateAreaTimesX
的结果,而不是该方法的逻辑。因此,无论模拟返回什么,测试都可以。

但这就是测试单元的想法。您正在单独测试单独的单元。您的
AreaService\u GetArea\u测试将失败,这才是最重要的-您会立即看到哪个单元工作不正常。如果它导致了10个级联失败,而您仍然需要调试整个代码以确定“谁的错”?它不会破坏单元测试的目的吗?:)是的,灯是缓慢但肯定亮起的。这样做是一个非常大的心态转变,在你之前刚刚对问题进行编码之后。但我相信这是值得的。谢谢。但这就是测试单元的想法。您正在单独测试单独的单元。您的
AreaService\u GetArea\u测试将失败,这就是问题所在-您将立即看到哪个单元工作不正常。如果它导致了10次级联失败,而您仍然需要调试整个代码以确定“谁的错”,那会更好吗?它会不会挫败单元测试的目的呢?:)诚然,这盏灯正在缓慢但肯定地亮着。这样做是一个非常大的心态转变,在你之前刚刚对问题进行编码之后。但我相信这是值得的。谢谢。我担心的是,当另一个开发人员更改方法时,他会看到测试失败,但因为在他看来,这是一条路,他会更改单元测试以适应他的更改。这意味着所有其他的测试都是错误的,因为他们在模仿这个测试。@Captain0:这取决于更改的目的。如果这是一个违背接口契约的变更,那么实现就被破坏了。如果是接口内的更改,这不是问题。我关心的是,当另一个开发人员更改方法时,他会看到测试失败,但因为在他看来,这是一种方式,他会更改单元测试以适应他的更改。这意味着所有其他的测试都是错误的,因为他们在模仿这个测试。@Captain0:这取决于更改的目的。如果这是一个违背接口契约的变更,那么实现就被破坏了。如果这是接口内的更改,那么这不是问题。
public int CalculateArea(int x,int y)
{
    return x*y+2;
}