質問
私は単体テストの利点についてプレゼンテーションをまとめています。つまり、意図しない結果の簡単な例が欲しいです。別のクラスで機能を破るあるクラスでコードを変更することです。
誰かがこの例を簡単で簡単に説明できることを提案できますか?
私の計画は、この機能に関するユニットテストを書いて、すぐにテストを実行することで何かを破ったことを知っていることを実証することです。
解決
少し単純で、したがって、おそらくより明確な例は次のとおりです。
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" };
}
DosomethingServerからの出力は多少異なりますが、それでもすべてコンパイルされ、さらに微妙なバグが作成されます。
最初の(非アレイ)バージョンが印刷されます Server address is: 127.0.0.1
2つ目が印刷されます 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);
}
さまざまな実装が使用されています。そのうちの1つは以下で使用されます。
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)
}
}
今、これは特に奇妙ではありません。私たちは、IENumerableの特定の実装を想定していません。実際、これはアレイと非常に多くの一般的に使用されるコレクションで機能します(System.Collections.genericの1つを考えられません。通常の方法と通常の拡張法のみを使用しました。単一項目のコレクションに最適化されたケースを持つことは珍しいことではありません。たとえば、リストを配列に変更するか、ハッシュセット(重複を自動的に削除する)、またはLinkedListまたはその他のいくつかのものを変更すると、機能し続けることができます。
それでも、特定の実装に依存していませんが、特定の機能、特に巻き戻し可能な機能に依存しています(Count()
icollection.countを呼び出すか、列挙可能を介して列挙します。その後、名前チェックが行われます。
誰かが結果の財産を見て、「うーん、それは少し無駄だ」と考えています。彼らはそれを置き換えます:
public IEnumerable<T> Results
{
get
{
using(IDataReader rdr = GetReader())
while(rdr.Read())
yield return Build(rdr);
}
}
これも完全に合理的であり、多くの場合、かなりのパフォーマンスが向上することになります。もしも CheckNames
問題のコーダーによって行われた即時の「テスト」にヒットしていません(多くのコードパスでヒットしないかもしれません)。セキュリティリスクを開くと、1つの名前よりもさらに悪化する可能性があります)。
しかし、ゼロ以上の結果でチェック名でヒットする単位テストは、それをキャッチするでしょう。
ちなみに、同等の(より複雑な場合)変更がNPGSQLの後方適合性機能の理由です。 list.add()を返品利回りに置き換えるだけではありませんが、executeReaderの仕組みに変更がo(n)からO(1)に匹敵する変更を与えて最初の結果を得ました。ただし、それ以前は、NPGSQLCONNECTIONにより、ユーザーは接続から別のリーダーを取得することができましたが、最初はまだ開いていませんでした。 IDBConnectionのドキュメントは、これを行うべきではないと述べていますが、それは実行中のコードがなかったという意味ではありませんでした。幸いなことに、そのような実行中のコードの1つはヌニットテストであり、そのようなコードが構成を変更するだけで機能し続けることを可能にする後方適合性機能が追加されました。