【问题标题】:Unit Test protected method of an abstract class抽象类的单元测试保护方法
【发布时间】:2013-12-10 20:19:20
【问题描述】:

我正在尝试编写一个单元测试来测试抽象类中的受保护方法。我尝试编写一个继承自抽象类的测试类,但是当我实例化测试类时,基本抽象类尝试连接到 Oracle 数据库并失败,这不允许我测试我感兴趣的受保护方法in. 抽象类不能修改。

如何直接对这个抽象类中的受保护方法进行单元测试?

这是我尝试使用反射的 sn-p。

Type type = typeof(AbstractClass);
BindingFlags eFlags = BindingFlags.Instance | BindingFlags.NonPublic;
MethodInfo myMethod = type.GetMethod("ProtectedMethod", eFlags);
object[] arguments = new object[] { _myDs };
myMethod.Invoke(type, arguments);
_myDs = (DataSet)arguments[0];

【问题讨论】:

  • 在可以创建子类的情况下为什么还要使用反射?如果您的基本方法连接到数据库,那么您必须模拟它。您可以将数据库连接到不同的类吗?
  • 我已经提到,当我使用抽象类的派生类时,当派生类实例化时,抽象类会尝试访问Oracle数据库并失败,从而导致整个单元测试失败.那么......我怎样才能获得抽象类内部的受保护方法?不,我无法将数据库连接提取到不同的类。它遵循 CSLA 标准。
  • @Shar1er80:如果您遇到这样的问题,那么该方法是否真的属于该类?如果您想在没有连接的情况下对其进行测试,则意味着它有单独的责任。考虑将其移至其他班级。
  • 基类在实例化时访问数据库是没有意义的,如果你不能改变它访问数据库的方式,那么这个类就太紧耦合了。

标签: c# .net reflection nunit abstract-class


【解决方案1】:

这是可行,但请注意您的代码可能存在几个严重问题:

  • 构造函数不应执行任何业务逻辑。
  • 您的抽象类不应该知道数据库类型(依赖倒置原则)。
  • 你的抽象类有更多的职责吗?如果是,请将它们移到单独的类/项目中(单一职责原则)。
  • 您不应该对非公共 API 进行单元测试。

这是一个食谱:

  • 首先,创建抽象类的假实例。
  • 然后,调用FormatterServices.GetUninitializedObject 获取类的实例而不调用构造函数。
  • 之后,您可以使用反射来调用ProtectedMethod

这样,您就可以测试ProtectedMethod

  • 不调用构造函数。
  • 不修改抽象类。

这是一个示例代码:

class AbstractClassFake : AbstractClass { }

[Test]
public void Test()
{
    // Arrange:
    var abstractClassFake = (AbstractClassFake)FormatterServices
        .GetUninitializedObject(typeof(AbstractClassFake));

    MethodInfo method = abstractClassFake.GetType()
        .GetMethod("ProtectedMethod",
            BindingFlags.Instance | BindingFlags.NonPublic);

    object[] arguments = new object[] { myDs };

    // Act:
    object val = method.Invoke(abstractClassFake, new[] { myDs });

    // Assert:
    // TODO: Your assertion here
}

【讨论】:

  • 嘿,伙计,我正在查看您的代码,但我无法理解反射是如何工作的,以及 myDs 到底是什么。我在尝试进行反射时也遇到了错误。 abstractClassFake.GetType() is throwing Error : An object reference is required for the non-static field, method, or property : object.GetType() 你能解释一下这里发生了什么吗,因为你的代码似乎真的很好,而且在我看来,它是干净的,而且我迄今为止看到的关于这个主题的所有其他人都可以阅读它。提前致谢!
  • 你好。 myDs 变量未在此处初始化。作者没有初始化它,所以我假设它是一个字段或一个参数。此外,我建议不惜一切代价避免此类测试。这段代码证明了解决上述问题是可能的,但它也表明应该重新设计可测试的代码。如果您有任何其他问题,请告诉我。
【解决方案2】:

妥协。

假设你有:

public abstract class CanNeverChange
{
    public CanNeverChange()
    {
        //ACK!  connect to a DB!!  Oh No!!
    }

    protected abstract void ThisVaries();

    //other stuff
}

public class WantToTestThis : CanNeverChange
{
    protected override void ThisVaries()
    {
         //do something you want to test
    }
}

改成这样:

public class WantToTestThis : CanNeverChange
{
    protected override void ThisVaries()
    {
         new TestableClass().DoSomethingYouWantToTest();
    }
}

public class TestableClass
{
    public void DoSomethingTestable()
    {
        //do something you want to test here instead, where you can test it
    }
}

现在您可以在TestableClass 中测试您想要测试的行为。对于WantToTestThis 类,妥协于可怕的遗留代码的压力,不要测试它。用最少的未经测试的代码插入可测试的东西是一种历史悠久的策略。我第一次听说它是从 Michael Feather 的书Working Effectively with Legacy Code

【讨论】:

  • 结果是允许编辑基本抽象类,我最终确实将所需的属性和方法移到了内部静态类中。这对方法的调用者造成了微小的变化,并允许对我需要的方法进行单元测试
【解决方案3】:

我认为最好的解决方案是按照 cmets 的建议将班级分开。

然而,如果你想在不改变类的情况下测试它,你可以将构造函数的内容移动到一个虚方法中:

public abstract class TheOracleOne
{
    protected virtual void Init()
    {
        // connection things
    }
}

并且在你的派生类中重写什么都不做:

public class TestClass : TheOracleOne
{
    protected override void Init()
    {
    }
}

这应该跳过抽象类的构造函数中发生的初始化,允许您在派生类上正常访问该方法。

【讨论】:

  • 不能修改基础抽象类。
  • 你真的应该只测试你拥有并且可以修改的代码。如果是这样的话,我想我会改变我的建议:“不要测试它”。
  • 结果是允许编辑基本抽象类,我最终确实将所需的属性和方法移到了内部静态类中。这对方法的调用者造成了微小的变化,并允许对我需要的方法进行单元测试
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多