4

假设我们有一个类似的方法

public static IThing getTheThing() {
    return internalThingGetter();
}

我们希望出于调试或单元测试的目的,引入一种轻量级策略来手动覆盖它。

private static IThing _thingManualOverride;

public static IThing getTheThing() {
    if (/*some condition*/)
        return _thingManualOverride;
    else
        return internalThingGetter();
}

检查_thingManualOverride != null或引入新的布尔值并检查是否更好,例如,_shouldOverride

或者,这里有更可靠的模式吗?

编辑:要满足的一些目标:

  • 假设保持界面很重要。很多代码都使用这种静态方法,改变它是一个不错的目标,但代价高昂。
  • 这两项检查都是“正确的”,因为如果覆盖是null我们可以安全地假设默认课程是正确的。
4

4 回答 4

2

如果会去_thingManualOverride != null。您可以检查另一个字段,比如说一个名为“_IsThingManualOverrideSet”的布尔值,但由于程序员的错误,它们可能会给出冲突的值:

_thingManualOverride = null;
_IsThingManualOverrideSet = true; // <-- oops!

所以检查值本身是最好的方法。

于 2013-05-01T15:00:31.120 回答
1

这不是答案,只是建议。阅读 Roy Osherove 所写的The Art Of Unit Testing 的第三章会让你受益(更好的是,出去购买,阅读整本书——太棒了!)。特别是第 3.4.5 节(下面是包含该节的示例第 3 章的链接):

单元测试的艺术 - 示例第 3 章

更新:根据您对使用静态工厂类的示例的请求:

static class ThingFactory
{
  private static IThing _thingManualOverride = null;

  public static IThing getTheThing() 
  {
    if (_thingManualOverride != null)
      return _thingManualOverride;

    return new internalThingGetter();
  }

  public static void SetThing(IThing thing)
  {
    _thingManualOverride = thing;
  }
}

我要补充一点,我不喜欢使用这种模式。如果你有机会重构,考虑它可能是明智的。我不喜欢使用它的主要原因是因为您必须记住在每次测试后重置状态(使用 null 参数调用 SetThing 方法),这样您就不会通过手动覆盖影响其他测试。

于 2013-05-01T15:01:33.220 回答
0

为了单元测试的目的,最好摆脱工厂中的静态方法,并制作虚拟方法。这样您就可以使用模拟替换它的返回值。

class Factory{    
   public virtual IThing getTheThing() {
      return internalThingGetter();
   }
}

在测试中,使用 Moq 框架:

var m = new Mock<Factory>();
m.Setup(f=>f.getTheThing())
 .Returns(thingManualOverride);
于 2013-05-01T14:54:36.153 回答
0

您可以创建一个帮助类来为您执行此操作,可能是这样的:

public class OverridableValue<T>
{
    private readonly Func<T> OriginalValueGetter;
    private Func<T> ValueGetter;

    public OverridableValue(Func<T> valueGetter)
    {
        this.OriginalValueGetter = valueGetter;
        this.ValueGetter = OriginalValueGetter;
    }

    public T Value
    {
        get 
        {
            return ValueGetter();
        }
    }

    public void SetValueOverride(T value)
    {
        if (value == null)
            ValueGetter = OriginalValueGetter;
        else
            ValueGetter = () => value;
    }
}

用法可能如下所示:

private static readonly OverridableValue<IThing> _thingManualOverride = new OverridableValue<IThing>(internalThingGetter);

public static IThing getTheThing() {
    return _thingManualOverride.Value;
}

覆盖它的地方很简单:

_thingManualOverride.SetValueOverride(new Thing2());

您可以轻松地将null签入换成SetValueOverride其他东西,或者简单地定义一个新方法,RemoveOverride()当一个null值被认为是有效覆盖时会有所帮助:

public void SetValueOverride(T value)
{
    ValueGetter = () => value;
}

public void RemoveOverride()
{
    ValueGetter = OriginalValueGetter;
}

另一个好处是,这个覆盖应该(我认为)通常是线程安全的(你不会遇到一些IsOverriddenFlagtrue但没有覆盖值的情况)。

编辑:您可以Valuesetter 中覆盖值,但我认为这在实践中看起来很奇怪:

public T Value
{
    get 
    {
        return ValueGetter();
    }
    set
    {
        if (value == null)
            ValueGetter = OriginalValueGetter;
        else
            ValueGetter = () => value;
    }
}

myThing = _thingManualOverride.Value; //Thing1
_thingManualOverride.Value = new Thing2();
myThing = _thingManualOverride.Value; //Thing2
_thingManualOverride.Value = null;
myThing = _thingManualOverride.Value; //Thing1 ... not null!?
于 2013-05-01T15:26:23.350 回答