【问题标题】:Is it ok to unit-test an abstract class by instantiating it as a mock object?可以通过将抽象类实例化为模拟对象来对抽象类进行单元测试吗?
【发布时间】:2011-04-12 08:20:53
【问题描述】:

我注意到您可以通过将抽象类实例化为模拟对象来对其进行单元测试。因此,您可以模拟抽象属性和方法,同时能够测试已实现的属性和方法。

但是,我习惯于区分要测试的类和注入的依赖项要模拟/存根。所以,在我新启蒙的早期阶段,我想知道这种测试方式是否有任何陷阱?对此有什么想法吗?

问候, 莫腾

【问题讨论】:

    标签: c# unit-testing inheritance mocking abstract-class


    【解决方案1】:

    一个具有任何价值的抽象类都应该有可以被实例化的具体子类。所以对它们进行单元测试,并通过它们隐式地测试基类。

    如果它没有具体的子类,我想不出它应该存在的任何理由(即作为抽象类)。

    一般来说,我更喜欢使用模拟来设置要测试的类的环境,而不是实例化类本身。这种区别 - 对我来说 - 使测试用例更加清晰,并确保我始终测试类的真正功能。

    当然,我可以想到主要问题是能够以某种方式编写单元测试以启用重构(如Working Effectively with Legacy Code 中所述)的情况(使用遗留代码)。作为这种情况下的临时解决方案,(几乎)任何事情都会发生。但是,一旦单元测试开始工作,就应该尽快对类进行适当的重构,使其干净且可测试(以及它的单元测试,以使其可维护)。

    【讨论】:

    • 它可能是框架的一部分,由库的用户继承——但库不提供具体的实现。
    • @Stefan,如果没有真正被子类化,那么如何使用这样的类?顺便说一句,恕我直言,一个设计良好的库应该公开接口,而不是抽象类。除非这些抽象类打算被重用于子类化(例如,集合框架中的Abstract*)。
    • @Péter:不,那不是真的。例如,在我们的应用程序中,我们有一个平台,它提供了许多由模块继承和扩展的基类。平台是自己测试的,当然跟模块没有任何关系。
    • @Peter,区分测试输入和测试输入也是我发布这个问题的原因。另一方面:如果你测试抽象类,你可以在测试继承它的对象时忽略它。这不是更干吗?
    • @Stefan,好的,我明白你的意思。即使在这种情况下,我更愿意为平台测试一个“基本”默认子类(甚至在测试领域中实现),而不是模拟它。这反过来也验证了您的类设计运行良好,即您的抽象类确实可以以连贯一致的方式进行子类化。
    【解决方案2】:

    不要用继承来组合两个类,看看能不能用组合。

    所以不要这样:

    abstract class PaySalary {
        payAmount(decimal money) {
            if (transferAmount(money)) {
                isPaid = true;
            }
        }
    
        abstract void transferAmount(decimal money);
    }
    

    这样做:

    class PaySalary {
        public MoneyTransferrer;
    
        payAmount(decimal money) {
            if (MoneyTransferrer.transferAmount(money)) {
                isPaid = true;
            }
        }
    }
    

    这样,您不必在单元测试中继承类,但您可以简单地模拟 MoneyTransferrer。您也可以在不依赖基类的情况下测试 MoneyTransferrer。

    【讨论】:

    • 谢谢,但我的问题是如何测试抽象类——而不是如何避免从抽象类继承。我对在必要的情况下测试抽象类的最佳实践感兴趣:-)
    【解决方案3】:

    如果我正在测试的部分没有具体的实现,我通常会编写一个测试实现。在单元测试中,我做的事情与我的课程的用户类似。如果他继承了我的类,我在测试中继承。

    另一方面,我不会说使用模拟框架来创建实现是不好的。在我的情况下,我通常不工作,通常是因为我需要为那个类和类似的东西编写 ORM 映射文件。

    【讨论】:

      【解决方案4】:

      是的,您将测试一个抽象类,就像使用注入测试一个接口一样。

      如果您为ILog 编写一个测试并使用MockLog 将其注入进行测试,那么您将对LogBase(在此示例中是一个抽象类)执行相同的操作并使用MockLog 注入它只是相同的。

      abstract class 不是interface,但它们在开销上的使用几乎相同。

      【讨论】:

        【解决方案5】:

        根据定义,abstract class 是抽象的。不应该有任何东西可以测试它是否真的是抽象的。

        【讨论】:

        • @Jon:听起来你在谈论一个接口,一个抽象类具有功能(如果它包含逻辑可能需要测试),但不能直接实例化,只能派生自。
        • 如果抽象类中没有具体的东西,那不就是一个接口吗?
        • @Jackson,但是如果你的抽象类没有具体的子类,那么它存在的意义何在?
        • @Peter:我想他有一个抽象类,在两个或多个具体类之间共享功能。他可以使用具体类测试该功能,或者直接测试抽象类。如果没有具体类,那么抽象类是多余的。
        • 我一直认为抽象类是一个接口,但至少在 C# 术语中您似乎可以包含具体/共享功能。我的立场是正确的!
        猜你喜欢
        • 2011-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-21
        • 1970-01-01
        • 1970-01-01
        • 2017-02-05
        相关资源
        最近更新 更多