题
我正在汇总有关单元测试的好处的演示文稿,我想一个简单的示例,说明意外后果:更改一个类别中破坏另一类功能的代码。
有人可以建议一个简单,容易解释的例子吗?
我的计划是围绕此功能编写单元测试,以证明我们知道我们立即通过测试破坏了一些东西。
解决方案
一个稍微简单,因此更清晰的例子是:
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测试,添加了一个向后兼容的功能,以允许此类代码继续使用配置的更改。