如何测试具有.base()调用的函数

本文关键字:base 调用 函数 测试 何测试 | 更新日期: 2023-09-27 18:06:55

我想对一个函数做一个单元测试,该函数在其实现中调用基类(使用.base())

我不能使用mock,因为这是我们处理的继承,所以我没有在构造函数中获得对象。

示例代码:

protected override BuyerDeal Map(BuyerDealDTO buyerDealDTO, BuyerDeal buyerDealEntity)
{            
    buyerDealEntity.prop1 = buyerDealDTO.prop2;
    base.Map(buyerDealDTO, buyerDealEntity);
    return buyerDealEntity;
}

我想测试这个函数,但我不想这样:

base.Map(buyerDealDTO, buyerDealEntity);

发生,因为我单独测试基数。

然而,我确实想测试(验证)调用,并且仅仅是对基的调用。

顺便说一下,基类是抽象

如何测试具有.base()调用的函数

问题是,如果从基类继承的类很少,这将导致对基类进行多次测试。

在不了解映射器的情况下,可能有几种方法可以做到这一点:

将你的代码和map代码拆分为单独的方法,例如:

protected override BuyerDeal Map(BuyerDealDTO buyerDealDTO, BuyerDeal buyerDealEntity)
{            
    ExtraLogic(...);
    base.Map(buyerDealDTO, buyerDealEntity);
    return buyerDealEntity;
}
protected void ExtraLogic(...)
{
    buyerDealEntity.prop1 = buyerDealDTO.prop2;
}

则可以测试ExtraLogic。这可能不适用于所有代码,因为调用基可能需要作为依赖项,这会更改流程,如注释所述。我一般不建议这样做。

或者不从基类继承。继承Interface,然后模拟抽象类。优先组合而不是继承(FCoI)。这允许您注入基类并只测试您的代码。如果没有定义接口(无论如何都很糟糕),或者框架显式地使用抽象类,可能无法工作。

我看到三种方法:

  1. 尝试忽略派生类测试中的基类。这里的问题是您的测试是不完整的,因为您从未测试派生类代码与基类的交互。

  2. 测试fixture的并行继承层次,即基类的抽象fixture,由派生类的所有fixture继承。这有助于消除重复并测试上面提到的交互,但是使fixture难以理解。

  3. 尽可能遵循复合优于继承。没有基类,没有麻烦。

我建议遵循第三个,特别是看起来好像买家交易之间的映射可能被认为是一个单独的责任。

底线:只需创建一个BuyerDealMapper,测试它,并在测试其他类时模拟它。

在进行单元测试时,您实际上是在测试所测试的代码为相应的输入提供正确的输出。

这个输入可以以依赖关系的形式出现,比如方法参数、从文件系统或网络源读取的数据。

输出的形式可以是方法返回的值、写入文件系统的数据或传递给另一个类的属性。

只要您的输入产生预期的输出,在合理范围内,在代码中发生的任何其他事情都无关紧要。

在你发布的代码中,你的单元测试不应该关心是否调用base.Map(buyerDealDTO, buyerDealEntity); -只是在代码测试给出预期的输出。

如果你的基类本身需要依赖,比如文件系统写入或网络读取等…,这些可以在您的测试启动例程中模拟。

如果您担心要多次测试基类,这实际上是一件非常好的事情!代码测试的次数越多,一段代码看到的条件和情况就越多,这段代码就越彻底,也就越防弹。您的单元测试应该有助于保证您的基类代码和处理您的继承类使用它。

例如,如果基类抛出异常,你的方法将如何处理?返回一个意外的值?因为这样或那样的原因没有回来?单元测试应该考虑这些因素。

当然,如果基类调用与方法及其工作方式无关,那么可能根本不应该在该方法中调用它。可能需要进行一些代码重构来解决这个问题。事实上,这是单元测试的一个巨大好处,因为它还可以帮助您解决诸如此类的架构问题。当然,假设您正在使用TDD—而不是测试预先编写的代码—这可能会有很大的帮助。

重构此代码的一种潜在方法是不在重写的方法中调用基类方法。让基类确保调用此方法本身,然后在重写的代码中调用所需的代码。例如:
public abstract class MyBaseClass
{
    public BuyerDeal Map(BuyerDealDTO buyerDealDTO, BuyerDeal buyerDealEntity)
    {
        // perform base class logic here
        var entity = this.MapInternal(buyerDealDTO, buyerDealEntity);
        return entity;
    }
    protected abstract BuyerDeal MapInternal(BuyerDealDTO buyerDealDTO, BuyerDeal buyerDealEntity);
}
public class MyClass : MyBaseClass
{
    protected override BuyerDeal MapInternal(BuyerDealDTO buyerDealDTO, BuyerDeal buyerDealEntity)
    {            
        buyerDealEntity.prop1 = buyerDealDTO.prop2;
        return buyerDealEntity;
    }
}

使用此方法,当您单元测试MyClass时,您不再多次测试MyBaseClass::Map(...)。当然,仍然需要单独测试MyBaseClass本身。