如何对不可变的类构造函数进行单元测试?

时间:2020-03-06 14:41:49  来源:igfitidea点击:

我有一个带有某些私有字段的不可变类,这些私有字段是在构造函数执行期间设置的。我想对该构造函数进行单元测试,但是在这种情况下我不确定"最佳实践"。

简单的例子

此类在Assembly1中定义:

public class Class2Test
{
    private readonly string _StringProperty;

    public Class2Test()
    {
        _StringProperty = ConfigurationManager.AppSettings["stringProperty"];
    }
}

此类在Assembly2中定义:

[TestClass]
public class TestClass
{
    [TestMethod]
    public void Class2Test_Default_Constructor()
    {
        Class2Test x = new Class2Test();
        //what do I assert to validate that the field was set properly?
    }
}

编辑1:我已经用潜在的解决方案回答了这个问题,但是我不确定这是否是"正确的方法"。因此,如果我们认为自己有更好的主意,请发布。

这个示例确实不值得测试,但是假设构造函数具有一些更复杂的逻辑。是否最好的方法是避免测试构造函数,并假设该类上所有方法的测试都能正常工作,那么就假设它可以正常工作吗?

编辑2:看起来我使示例有点简单。我已经更新了一个更合理的情况。

解决方案

我已经在Assembly1(代码)上正确启用了[InternalsVisibleTo],以便与Assembly2(测试)建立信任关系。

public class Class2Test
{
    private readonly string _StringProperty;
    internal string StringProperty { get { return _StringProperty; } }

    public Class2Test(string stringProperty)
    {
        _StringProperty = stringProperty;
    }
}

这使我可以断言:

Assert.AreEqual(x.StringProperty, "something");

对此我唯一不喜欢的是,当我们仅查看Class2Test时,不清楚内部属性的用途是什么(不加评论)。

其他想法将不胜感激。

没什么,除非我们正在使用该字段。我们不希望通过测试过度规范。换句话说,无需测试赋值运算符是否有效。

如果要在方法或者其他方法中使用该字段,请调用该方法并对其进行断言。

编辑:

assume the constructor has some more complex logic

我们不应在构造函数中执行任何逻辑。

编辑2:

public Class2Test()
{
     _StringProperty = ConfigurationManager.AppSettings["stringProperty"];
}

不要那样做! =)简单单元测试现在已经成为集成测试,因为它取决于一个以上类的成功操作。编写一个处理配置值的类。 WebConfigSettingsReader可以是名称,它应该封装ConfigurationManager.AppSettings调用。将该SettingsReader类的实例传递到Class2Test的构造函数中。然后,在单元测试中,我们可以模拟WebConfigSettingsReader,并存根对我们可能对其进行的任何调用的响应。

在编辑中,我们现在对ConfigurationManager的依赖度很难测试。

一种建议是提取到该接口的接口,然后使Class2Test ctor以IConfigManager实例作为参数。现在我们可以使用伪造/模拟对象来设置其状态,以便可以测试依赖于Configuration的任何方法以查看它们是否使用了正确的值...

public interface IConfigManager
    {
        string FooSetting { get; set; }
    }

    public class Class2Test
    {
        private IConfigManager _config;
        public Class2Test(IConfigManager configManager)
        {
            _config = configManager;   
        }

        public void methodToTest()
        {
            //do something important with ConfigManager.FooSetting
            var important = _config.FooSetting;
            return important;
        }
    }

    [TestClass]
    public class When_doing_something_important
    {
        [TestMethod]
        public void Should_use_configuration_values()
        {
            IConfigManager fake = new FakeConfigurationManager();
            //setup state
            fake.FooSetting = "foo";
            var sut = new Class2Test(fake);
            Assert.AreEqual("foo", sut.methodToTest());
        }
    }