工厂实现接口的公共成员与私有成员

时间:2019-01-04 05:08:01

标签: c# unit-testing interface factory class-design

摘要

仅出于编写更好的单元测试的目的而将具体的类数据公开为仅获取公共属性是否有害?


背景

通常,我宁愿保留与private readonly一样多的数据。这确保使用者不绑定到所保存的数据,而绑定到类型提供的行为。我最近注意到的一个(潜在)问题是为实现接口的工厂类型编写好的单元测试。请考虑以下说明性示例:

public interface IComponent
{
    void DoStuff();
}
public interface IComponentDependency
{
    void HelpInDoingStuff(int data);
}
public interface IFactory
{
    IComponent CreateComponent(IComponentDependency dependency)
}
public class SpecialComponent : IComponent
{
    private readonly int someOtherDependency;
    private readonly IComponentDependency dependency;
    public SpecialComponent(IComponentDependency dependency, int someOtherDependency)
    {
        this.dependency = dependency;
        this.someOtherDependency = someOtherDependency;
    }
    public void DoStuff()
    {
        dependency.HelpInDoingStuff(someOtherDependency);
    }
}
public class SpecialComponentFactory : IFactory
{
    private readonly int someOtherDependency;
    public SpecialComponentFactory(int someOtherDependency)
    {
        this.someOtherDependency = someOtherDependency;
    }

    public  IComponent CreateComponent(IComponentDependency dependency)
    {
        return new SpecialComponent(dependency, this.someOtherDependency);
    }
}

SpecialComponentFactory编写测试时,唯一可以公开测试的是SpecialComponentFactory返回SpecialComponent的实例。如果我还想确认SpecialComponent确实是由someOtherDependency构造的,或者实际上,他是由指定的dependency构造的,该怎么办。为此,我们可以使它们成为protected和子类SpecialComponent的子类,然后通过测试伪造将其公开公开:

public class SpecialComponent
{
    protected readonly int someOtherDependency;
    protected readonly IComponentDependency dependency;
    public SpecialComponent(IComponentDependency dependency, int someOtherDependency)
    {
        this.dependency = dependency;
        this.someOtherDependency = someOtherDependency;
    }
    public void DoStuff()
    {
        dependency.HelpInDoingStuff(someOtherDependency);
    }
}
public class FakeSpecialComponent : SpecialComponent
{
    public FakeSpecialComponent(IComponentDependency dependency, int someOtherDependency)
        : base(dependency, someOtherDependency)
    {
        this.dependency = dependency;
        this.someOtherDependency = someOtherDependency;
    }
}

但这对我来说似乎并不理想。另一种选择是将依赖项公开为public。尽管这可以使测试断言所使用的依赖项是正确的,但是这也与我长期以来一直认为将数据保持为私有并仅公开行为的信念背道而驰。

public class SpecialComponent
{
    public int SomeOtherDependency;
    public IComponentDependency Sependency;
    public SpecialComponent(IComponentDependency dependency, int someOtherDependency)
    {
        Sependency = dependency;
        SomeOtherDependency = someOtherDependency;
    }
    public void DoStuff()
    {
        Dependency.HelpInDoingStuff(SomeOtherDependency);
    }
}

总结思想

如果整个解决方案实际上从未直接引用SpecialComponent,而是始终引用IComponent,则在将依赖项公开为public的第二种选择中是否有任何危害(或气味)仅获取属性。但是现在,我与另一个长期存在的信念背道而驰,那就是仅出于单元测试的目的而修改成员的可访问性。


问题

private readonly字段实际变为我的具体类型的public只读属性是否有任何危害?我正在尝试将其合理化,因为SpecialComponent的使用者仅引用IComponent,因此他们将无法直接访问数据,但是测试可以简单地转换为SpecialComponent并执行所需的断言语句。

我是否想过所有这些? :)

编辑:我已经更新了上面的代码示例,其中包括IComponent的方法声明和实现以“填写”示例。如下面的注释中所述,我可以对从IComponent返回的SpecialComponentFactory.CreateComponent实例进行某种方式的单元测试,这将证明使用了必需的依赖项,从而提供了覆盖范围,但是对我来说听起来像最不可接受的选择,因为对SpecialComponentFactory的单元测试将不必要地测试SpecialComponent的实现,从而为它提供了在其自身实现之外失败的原因,并将SpecialComponentFactory测试耦合到SpecialComponent

1 个答案:

答案 0 :(得分:1)

这实际上是进行单元测试的症状之一。
我会说工厂是使用IComponent的类的实现细节。
工厂将通过针对该类的测试进行测试。

我不在乎这些测试的分类方式(单元,集成等)-我只在乎我的测试运行缓慢或设置复杂。
因此,我将仅嘲笑会使我的测试变慢或变得复杂的事情。逻辑将在应用程序边界内进行测试,从而使测试保持快速。

例如,在Web应用程序中,它可以是Controller-> Database(内存中或模拟的)。

将单位视为“行为单位”。

写作单元测试仅是因为应该对每个课程进行测试-可以认为这是浪费时间。因为内部设计的任何变化都将迫使我重写单元测试或删除过时的或编写新的单元测试,所以仅适用于课堂,我认为这对他而言是可用的。