我正在汇总有关单元测试的好处的演示文稿,我想一个简单的示例,说明意外后果:更改一个类别中破坏另一类功能的代码。

有人可以建议一个简单,容易解释的例子吗?

我的计划是围绕此功能编写单元测试,以证明我们知道我们立即通过测试破坏了一些东西。

有帮助吗?

解决方案

一个稍微简单,因此更清晰的例子是:

public string GetServerAddress()
{
    return "172.0.0.1";
}

public void DoSomethingWithServer()
{
    Console.WriteLine("Server address is: " +  GetServerAddress());
}

如果 GetServerAddress 是返回数组的更改:

public string[] GetServerAddress()
{
    return new string[] { "127.0.0.1", "localhost" };
}

dosomethingwithserver的输出将有所不同,但是所有这些都会汇总,从而使一个更细致的错误。

第一个(非阵列)版本将打印 Server address is: 127.0.0.1 第二个将打印 Server address is: System.String[], ,这是我在生产代码中也看到的东西。不用说它不再存在!

其他提示

这是一个例子:

class DataProvider {
    public static IEnumerable<Something> GetData() {
        return new Something[] { ... };
    }
}

class Consumer {
    void DoSomething() {
        Something[] data = (Something[])DataProvider.GetData();
    }
}

改变 GetData() 返回 List<Something>, , 和 Consumer 会破裂。

这可能看到了某种人为的人为,但是我在实际代码中看到了类似的问题。

说您有一种方法:

abstract class ProviderBase<T>
{
  public IEnumerable<T> Results
  {
    get
    {
      List<T> list = new List<T>();
      using(IDataReader rdr = GetReader())
        while(rdr.Read())
          list.Add(Build(rdr));
      return list;
    }
  }
  protected abstract IDataReader GetReader();
  protected T Build(IDataReader rdr);
}

使用各种实现。其中之一用于:

public bool CheckNames(NameProvider source)
{
  IEnumerable<string> names = source.Results;
  switch(names.Count())
  {
      case 0:
        return true;//obviously none invalid.
      case 1:
        //having one name to check is a common case and for some reason
        //allows us some optimal approach compared to checking many.
        return FastCheck(names.Single());
      default:
        return NormalCheck(names)
  }
}

现在,这都不是特别奇怪。我们没有假设特定的Inimentaiton。的确,这将适用于阵列和许多常用的集合(无法在System.Collections.collections.collections.collections.collections.collections.generic中不符合我的头顶)。我们只使用了普通方法和正常的扩展方法。为单项收藏提供优化的案例,甚至并不罕见。例如,我们可以将列表更改为一个数组,或者可能是标签集(自动删除重复项),或者链接清单或其他一些内容,并且它将继续工作。

尽管如此,尽管我们不依赖于特定的实现,但我们仍取决于特定功能,特别是可重新定位的功能(Count() 将调用iCollection.count。或通过枚举来枚举,然后将进行名称检查。

尽管有人看到了结果财产,并认为“嗯,这有点浪费”。他们替换为:

public IEnumerable<T> Results
{
  get
  {
    using(IDataReader rdr = GetReader())
      while(rdr.Read())
        yield return Build(rdr);
  }
}

这再次是完全合理的,在许多情况下,确实会导致大幅提高性能。如果 CheckNames 没有在相关编码器完成的即时“测试”中击中(也许不会在许多代码路径中击中),那么checknames会出错的事实(并且在更多情况下可能会返回错误结果比1个名称,如果打开安全风险,可能会更糟)。

但是,任何击中以超过零结果的检查名称的单位测试都会捕获它。


顺便说一句,可比较(如果更复杂)的变化是NPGSQL中向后兼容功能的原因。不像仅替换list.add()以回报率的收益率那样简单,但是对执行者工作方式的更改给出了从o(n)到o(1)的可比变更,以获得第一个结果。但是,在此之前,NPGSQLConnection允许用户在第一个仍未打开时从连接中获得另一个读者,并且在没有打开之后。 IDBConnection的文档说您不应该这样做,但这并不意味着没有运行代码。幸运的是,这样的运行代码是Nunit测试,添加了一个向后兼容的功能,以允许此类代码继续使用配置的更改。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top