Class 方法选择隐藏的基础 class 属性 而不是新的子 class 属性

Class method picks hidden base class property over new subclass property

我正在尝试为我的一个 classes 编写单元测试,它使用来自第三方库的基础 class,但我的第一次尝试很脆弱,因为测试取决于与内容管理员管理的文本集成。

首先,一个小的复制品。这将代表第三方基地 class 我必须与之合作:

namespace ThirdPartyXyz
{
    public class SomeFancyBaseClass
    {
        public SomeFancyBaseClass()
        {
            this.myMap = new Dictionary<string, string> { { "title", "Greatness" } };
        }

        public IReadOnlyDictionary<string, string> myMap { get; private set; }
    }
}

然后我按照这些行对代码进行单元测试:

namespace MyCorp
{
    public class MyThing : ThirdPartyXyz.SomeFancyBaseClass
    {
        public string GetHeadline()
        {
            return "[" + this.myMap["title"] + "]";
        }
    }
}

使用以下 NUnit 测试方法:

namespace MyCorp.UnitTestExperiments
{
    [TestFixture]
    public class MyThingTests
    {    
        [Test]
        public void GetHeadlineWillOutputBracketedResource()
        {
            var thing = new MyThing();
            var result = thing.GetHeadline();
            Assert.That(result, Is.EqualTo("[Greatness]"));
        }
    }
}

这是绿色的,但如果内容管理员将 "Greatness" 值更改为其他值,它将变为红色。所以我试图从基础 class 模拟/存根/伪造实际字典,但这并不简单,因为第三方库将 myMap 的字典 setter 声明为 private.

这是我试过的方法:

namespace MyCorp.UnitTestExperiments
{
    [TestFixture]
    public class MyThingTests
    {
        private class TestableMyThing : MyThing
        {
            public TestableMyThing(Dictionary<string, string> texts) { this.myMap = texts; }
            public new IReadOnlyDictionary<string, string> myMap { get; private set; }
        }

        [Test]
        public void GetHeadlineWillOutputBracketedResource()
        {
            var fakeTexts = new Dictionary<string, string> { { "title", "test text" } };
            var thing = new TestableMyThing(fakeTexts);
            var result = thing.GetHeadline();
            Assert.That(result, Is.EqualTo("[test text]"));
        }
    }
}

但是,这不起作用:测试仍然失败。 GetHeadline 方法使用来自 SomeFancyBaseClass 的隐藏 myMap 属性 而不是包含 "test text".

的伪字典

我目前的目标/问题有两个。首先,到目前为止,我很好奇我 (c/w) 如何获得我当前的工作方法。但其次,我担心让 MyThing 可测试的方法不是最好的方法,我想知道是否有办法完全避免这种情况。

三个选项:

  • 使用反射访问字典的seter

  • 第二个选项,让你的class更容易存根:

    public class MyThing : ThirdPartyXyz.SomeFancyBaseClass
    {
        public virtual new IReadOnlyDictionary<string, string> myMap;
        {
            get { return base.myMap; }
        }
    
        public string GetHeadline()
        {
            // this will use your 'virtual new myMap'!
            return "[" + this.myMap["title"] + "]";
        }
    }
    

    现在在你的单元测试中你可以:

    public class MyThingTestable : MyThing
    {
         public override IReadOnlyDictionary<string, string> myMap { get; set; }
    }
    

    现在你可以设置myMap,你的class将使用它(但注意SomeFancyBaseClass class不会使用它!它会用它myMap!不是很好!)

  • 第三种选择:使用 Microsoft Fakes 或类似产品。