使用模拟库来测试抽象类中的构造函数行为,而无需直接创建派生类

时间:2018-01-08 18:38:05

标签: c# unit-testing inheritance nunit moq

我的要求是从我的抽象基类派生的每个类型必须包含至少一个具有指定类型的字段。这种类型必须实现我的界面。 (为了简化示例,我假设继承链中只能有一个子类。)我可以通过直接在测试类中创建假类来测试此要求(参见下面的示例)。

  public interface ISampleInterface
  {
  }

  public class SampleClass : ISampleInterface
  {
  }

  public abstract class BaseClass
  {
    public BaseClass()
    {
      // try to get fields of ISampleInterface type
      var fields = GetType().GetFields();
      var sampleInterfaceFields = fields.Where(f => typeof(ISampleInterface).IsAssignableFrom(f.FieldType));
      if (!sampleInterfaceFields.Any())
        throw new MissingFieldException($"Derived class must contain at least one type which implements {nameof(ISampleInterface)}!");
    }
  }

  [TestFixture]
  public class BaseClassTests
  {
    public class FakeDerivedClass : BaseClass
    {
      //public SampleClass sampleClass; //this field is required to properly create the object
    }

    [Test]
    public void ShouldConstructorThownExceptionWhenDerivedClassDoesNotContainRequiredFields()
    {
      var exception = Assert.Throws<MissingFieldException>(() => new FakeDerivedClass());
      StringAssert.Contains($"Derived class must contain at least one type which implements {nameof(ISampleInterface)}!", exception.Message);
    }
  }

我正在考虑使用一些模拟库来测试此行为的更好方法。我更喜欢Moq,但我也在考虑NSubstitute。提前感谢您提供有关如何改进此测试代码的任何建议。

2 个答案:

答案 0 :(得分:1)

在基类上创建abstract property以确保派生类至少具有在基类上定义的类

public abstract class BaseClass {

    protected abstract ISampleInterface SampleProperty { get; }

}

这样,所有派生类至少会实现所需的最小行为

public class SampleClass : ISampleInterface {
    //...
}

public class FakeDerivedClass : BaseClass {
   ISampleInterface sampleField = new SampleClass();

   protected override ISampleInterface SampleProperty {
       get {
           return sampleField;
       }
   }
} 

因此,无需手动检查所需属性,因为如果未实现,编译器将提供错误详细信息。无需重新发明您开箱即用的现有行为。

答案 1 :(得分:0)

TL; DR - 您需要一个类通过引用它并尝试使用它来获得内部字段。这样,如果它丢失了你的代码将无法编译。您可以编写代码来检查是否存在未使用的内部字段,如果缺少则抛出异常,但这没有任何意义。

如果类(基类或其他类)需要访问具有给定类型的字段,例如:

public class MyClass
{

    void DoSomethingThatRequiresTheFieldINeed()
    {
        var value = _theField.Value;
    }

    // or

    SomeType PropertyThatExposesTheField => _theField;
}

然后编写一行引用该字段的代码将导致需要该字段。换句话说,如果在上面的类中,我没有添加一个名为_theField的字段,我的代码将无法编译。如果一个类需要有成员而不需要成员,则应该是编译器错误,而不是运行时错误。

(从技术上讲,字段可以在基类中。但是在这种情况下我会将它作为属性。继承的类不应该弄乱它继承的类的内部任何其他类如果你在家里,我会暴露一个房产,这样你就可以进入我的冰箱了,但你不能把它取下来或用不同的冰箱更换。我的内衣抽屉是一个私人场地,并且如果我想让你访问它,我会提供一种方法。)

如果上述解决方案不起作用,则意味着无论您是否需要,您都需要在某个类中使用该字段。这不好。在课堂上拥有一个领域的唯一原因是因为你需要它。如果您可以从类中删除内部成员并且它仍然编译,那么您删除的内容几乎肯定不需要在那里,要求或其他。

如果一个类需要一个外部可访问的成员,那么它应该实现一个接口。否则,无法引用该类并知道它具有给定的成员。我们知道它有成员,因为它实现了接口。

  

我的要求是从我的抽象基类派生的每个类型必须包含至少一个具有指定类型的字段。这种类型必须实现我的界面。

如果您需要一个实现特定接口的类型的对象,则将其声明为该接口的类型。这正是接口的用途。或者,如果它不是接口 - 您只是希望它是实现接口的特定类型,请将其声明为该类型。

但我们如何使用它描述了对象的任何要求。如果我们希望对象具有ISomeInterface的属性或方法,那么我们将尝试使用它们。如果类型没有直接或通过继承实现该接口,那么我们的代码将无法编译。