私はいくつかのコードをテストするためにPexを使用しようとしています。私は4つの具体的な実装を持つ抽象クラスを持っています。私は4つの具体的なタイプのそれぞれについてファクトリメソッドを作成しました。 this nice threadが説明している以外は、抽象型のものも作成しましたが、Pexは抽象ファクトリメソッドを使用しません。具体的な実装を持つ抽象クラスをスタブしないようにPexに指示する方法
問題は、私のコードの中には、4つの具体的な型がすべて存在することに依存しているということです(これ以上のサブクラスは作成されない可能性が非常に高いためです)。しかしPexはMolesを使用してスタブ
抽象クラスのMolesスタブを作成せずに抽象クラスのインスタンスを作成するために、Pexにファクトリメソッド(いずれも気にしません)を使用するにはどうすればよいですか?これを達成する指示文はPexAssume
ですか?いくつかの具体的な型はツリー構造の型を形成するので、から派生したConcreteImplementation
と、AbstractClass
という2つのプロパティがConcreteImplementation
にあることに注意してください。ツリーのどこでもスタブが使用されないようにする必要があります。 (すべてではない、具体的な実装がAbstractClass
性質を持っている。)
編集:
どのように目標がまだあることを覚えているけれども、私はクラスの構造自体がどのように機能するかについて、いくつかのより多くの情報を追加する必要があることが表示されますPexをスタブクラスにしないでください。
ここでは、抽象基底クラスの簡略化されたバージョンと、その4つの具体的な実装について説明します。
public abstract class AbstractClass
{
public abstract AbstractClass Distill();
public static bool operator ==(AbstractClass left, AbstractClass right)
{
// some logic that returns a bool
}
public static bool operator !=(AbstractClass left, AbstractClass right)
{
// some logic that basically returns !(operator ==)
}
public static Implementation1 Implementation1
{
get
{
return Implementation1.GetInstance;
}
}
}
public class Implementation1 : AbstractClass, IEquatable<Implementation1>
{
private static Implementation1 _implementation1 = new Implementation1();
private Implementation1()
{
}
public override AbstractClass Distill()
{
return this;
}
internal static Implementation1 GetInstance
{
get
{
return _implementation1;
}
}
public bool Equals(Implementation1 other)
{
return true;
}
}
public class Implementation2 : AbstractClass, IEquatable<Implementation2>
{
public string Name { get; private set; }
public string NamePlural { get; private set; }
public Implementation2(string name)
{
// initializes, including
Name = name;
// and sets NamePlural to a default
}
public Implementation2(string name, string plural)
{
// initializes, including
Name = name;
NamePlural = plural;
}
public override AbstractClass Distill()
{
if (String.IsNullOrEmpty(Name))
{
return AbstractClass.Implementation1;
}
return this;
}
public bool Equals(Implementation2 other)
{
if (other == null)
{
return false;
}
return other.Name == this.Name;
}
}
public class Implementation3 : AbstractClass, IEquatable<Implementation3>
{
public IEnumerable<AbstractClass> Instances { get; private set; }
public Implementation3()
: base()
{
Instances = new List<AbstractClass>();
}
public Implementation3(IEnumerable<AbstractClass> instances)
: base()
{
if (instances == null)
{
throw new ArgumentNullException("instances", "error msg");
}
if (instances.Any<AbstractClass>(c => c == null))
{
thrown new ArgumentNullException("instances", "some other error msg");
}
Instances = instances;
}
public override AbstractClass Distill()
{
IEnumerable<AbstractClass> newInstances = new List<AbstractClass>(Instances);
// "Flatten" the collection by removing nested Implementation3 instances
while (newInstances.OfType<Implementation3>().Any<Implementation3>())
{
newInstances = newInstances.Where<AbstractClass>(c => c.GetType() != typeof(Implementation3))
.Concat<AbstractClass>(newInstances.OfType<Implementation3>().SelectMany<Implementation3, AbstractUnit>(i => i.Instances));
}
if (newInstances.OfType<Implementation4>().Any<Implementation4>())
{
List<AbstractClass> denominator = new List<AbstractClass>();
while (newInstances.OfType<Implementation4>().Any<Implementation4>())
{
denominator.AddRange(newInstances.OfType<Implementation4>().Select<Implementation4, AbstractClass>(c => c.Denominator));
newInstances = newInstances.Where<AbstractClass>(c => c.GetType() != typeof(Implementation4))
.Concat<AbstractClass>(newInstances.OfType<Implementation4>().Select<Implementation4, AbstractClass>(c => c.Numerator));
}
return (new Implementation4(new Implementation3(newInstances), new Implementation3(denominator))).Distill();
}
// There should only be Implementation1 and/or Implementation2 instances
// left. Return only the Implementation2 instances, if there are any.
IEnumerable<Implementation2> i2s = newInstances.Select<AbstractClass, AbstractClass>(c => c.Distill()).OfType<Implementation2>();
switch (i2s.Count<Implementation2>())
{
case 0:
return AbstractClass.Implementation1;
case 1:
return i2s.First<Implementation2>();
default:
return new Implementation3(i2s.OrderBy<Implementation2, string>(c => c.Name).Select<Implementation2, AbstractClass>(c => c));
}
}
public bool Equals(Implementation3 other)
{
// omitted for brevity
return false;
}
}
public class Implementation4 : AbstractClass, IEquatable<Implementation4>
{
private AbstractClass _numerator;
private AbstractClass _denominator;
public AbstractClass Numerator
{
get
{
return _numerator;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value", "error msg");
}
_numerator = value;
}
}
public AbstractClass Denominator
{
get
{
return _denominator;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value", "error msg");
}
_denominator = value;
}
}
public Implementation4(AbstractClass numerator, AbstractClass denominator)
: base()
{
if (numerator == null || denominator == null)
{
throw new ArgumentNullException("whichever", "error msg");
}
Numerator = numerator;
Denominator = denominator;
}
public override AbstractClass Distill()
{
AbstractClass numDistilled = Numerator.Distill();
AbstractClass denDistilled = Denominator.Distill();
if (denDistilled.GetType() == typeof(Implementation1))
{
return numDistilled;
}
if (denDistilled.GetType() == typeof(Implementation4))
{
Implementation3 newInstance = new Implementation3(new List<AbstractClass>(2) { numDistilled, new Implementation4(((Implementation4)denDistilled).Denominator, ((Implementation4)denDistilled).Numerator) });
return newInstance.Distill();
}
if (numDistilled.GetType() == typeof(Implementation4))
{
Implementation4 newImp4 = new Implementation4(((Implementation4)numReduced).Numerator, new Implementation3(new List<AbstractClass>(2) { ((Implementation4)numDistilled).Denominator, denDistilled }));
return newImp4.Distill();
}
if (numDistilled.GetType() == typeof(Implementation1))
{
return new Implementation4(numDistilled, denDistilled);
}
if (numDistilled.GetType() == typeof(Implementation2) && denDistilled.GetType() == typeof(Implementation2))
{
if (((Implementation2)numDistilled).Name == (((Implementation2)denDistilled).Name)
{
return AbstractClass.Implementation1;
}
return new Implementation4(numDistilled, denDistilled);
}
// At this point, one or both of numerator and denominator are Implementation3
// instances, and the other (if any) is Implementation2. Because both
// numerator and denominator are distilled, all the instances within either
// Implementation3 are going to be Implementation2. So, the following should
// work.
List<Implementation2> numList =
numDistilled.GetType() == typeof(Implementation2) ? new List<Implementation2>(1) { ((Implementation2)numDistilled) } : new List<Implementation2>(((Implementation3)numDistilled).Instances.OfType<Implementation2>());
List<Implementation2> denList =
denDistilled.GetType() == typeof(Implementation2) ? new List<Implementation2>(1) { ((Implementation2)denDistilled) } : new List<Implementation2>(((Implementation3)denDistilled).Instances.OfType<Implementation2>());
Stack<int> numIndexesToRemove = new Stack<int>();
for (int i = 0; i < numList.Count; i++)
{
if (denList.Remove(numList[i]))
{
numIndexesToRemove.Push(i);
}
}
while (numIndexesToRemove.Count > 0)
{
numList.RemoveAt(numIndexesToRemove.Pop());
}
switch (denList.Count)
{
case 0:
switch (numList.Count)
{
case 0:
return AbstractClass.Implementation1;
case 1:
return numList.First<Implementation2>();
default:
return new Implementation3(numList.OfType<AbstractClass>());
}
case 1:
switch (numList.Count)
{
case 0:
return new Implementation4(AbstractClass.Implementation1, denList.First<Implementation2>());
case 1:
return new Implementation4(numList.First<Implementation2>(), denList.First<Implementation2>());
default:
return new Implementation4(new Implementation3(numList.OfType<AbstractClass>()), denList.First<Implementation2>());
}
default:
switch (numList.Count)
{
case 0:
return new Implementation4(AbstractClass.Implementation1, new Implementation3(denList.OfType<AbstractClass>()));
case 1:
return new Implementation4(numList.First<Implementation2>(), new Implementation3(denList.OfType<AbstractClass>()));
default:
return new Implementation4(new Implementation3(numList.OfType<AbstractClass>()), new Implementation3(denList.OfType<AbstractClass>()));
}
}
}
public bool Equals(Implementation4 other)
{
return Numerator.Equals(other.Numerator) && Denominator.Equals(other.Denominator);
}
}
私がテストしようとしているものの心はあなたが再帰的に実行する可能性を秘めている見ることができるようDistill
方法、です。このパラダイムでは、スタブされたAbstractClass
は無意味なので、アルゴリズムのロジックを破壊します。スタブブックされたクラスをテストしようとしても、例外を投げたり、Implementation1
のインスタンスであるとふりをする以外に、私ができることがほとんどないので、やや役に立たないクラスです。私はそのような方法で特定のテストフレームワークに対応するためにテスト中のコードを書き直す必要はありませんが、スタブしないような方法でテスト自体を書くことは、私がここでやろうとしていることです。
私がやっていることが、例えば型保証された列挙体とは異なることが明らかであることを望みます。また、私はここに投稿するためのオブジェクトを匿名化しています(あなたが言うように)。私はすべてのメソッドを含んでいないので、Implementation4.Equals(Implementation4)
が壊れていると私に言い聞かせるなら、ここでは壊れていますが、私の実際のコードは問題を処理します。
別の編集:ここでは
は、工場出荷時のクラスの一つの例です。これはPex生成テストプロジェクトのFactoryディレクトリにあります。
public static partial class Implementation3Factory
{
[PexFactoryMethod(typeof(Implementation3))]
public static Implementation3 Create(IEnumerable<AbstractClass> instances, bool useEmptyConstructor)
{
Implementation3 i3 = null;
if (useEmptyConstructor)
{
i3 = new Implementation3();
}
else
{
i3 = new Implementation3(instances);
}
return i3;
}
}
これらの具体的な実装のための私の工場のメソッドでは、コンストラクタを使用して具体的な実装を作成することができます。この例では、useEmptyConstructor
パラメータは、使用するコンストラクタを制御します。他のファクトリメソッドも同様の機能を持っています。私はリンクをすぐに見つけることはできませんが、これらのファクトリメソッドはオブジェクトをすべての可能な設定で作成できるようにする必要があります。
その実装でどのような問題が解決しているのかはっきりしていませんが、基本クラスから派生した別の型を作成した人がいれば、実装を破るように思えます。それは拡張性を破ることができ、ユーザを驚かせることができるように思えます。どちらもデザインの匂いです。派生クラスに属性(おそらく 'internal')を追加して、それを単純に検索できますか?次に、PEXがスタブを作成するのを気にする必要はありません。なぜなら、スタブを使用する必要がないからです。スタブがコードを破損させるような方法で注釈付けされることはありません。また、ユーザーコードを破壊することもありません。 –
@ MerlynMorgan-Grahamあなたのご意見ありがとうございます。実際、このプロジェクトはC#よりもF#に適していますが、今後のメンテナンス性が懸念されます。その振る舞いは、真の継承よりも「差別化された組合」に近い。つまり、抽象基底クラスの4つのサブクラスは、私が設定した計算構造内の閉じた一連の操作を表します。誰もこれを拡張するつもりはありませんが、抽象基本クラスと具象サブクラスの両方をアセンブリの外側に表示する必要があります。あなたが_internal_を意味する何か他のものがあれば、私はそれが何であるか分かりません。 – Andrew
派生クラスのものだけが意味をなさないならば、なぜ心配か - 実際には何か壊れますか?もしそうなら、どのように派生クラスが存在するのかを検出しますか?私はあなたの検出メカニズムのための代替手段を提供しようとしていました。また、タイプセーフな列挙型に似たパターンがあるようです。そのパターンに完全に従い、すべての実装を内部的にして、4つの実装の基本クラスに静的なファクトリプロパティを作成するだけです。適切な名前を付けて、正しいタイプを作成しますが、基本タイプとして返します。 –