我想用Pex來測試一些代碼。我有一個具有四個具體實現的抽象類。我已經爲四種具體類型中的每一種創建了工廠方法。我也爲抽象類型創建了一個,除了this nice thread解釋,Pex不會使用抽象工廠方法,也不應該使用抽象工廠方法。如何告訴Pex不要存根具體實現的抽象類
問題是我的一些代碼依賴於所有的四個具體類型(因爲它是非常非常不可能再創建任何子類),但Pex使用Moles創建代碼一個存根。
如何強制Pex使用工廠方法之一(任何一個我不介意)創建抽象類的實例,而不必爲該抽象類創建Moles存根?有沒有一個PexAssume
指令可以實現這個目標?請注意,某些具體類型構成一種樹結構,因此說ConcreteImplementation
來自AbstractClass
,並且ConcreteImplementation
具有AbstractClass
類型的兩個屬性。我需要確保根本不在樹的任何地方使用存根。 (並非所有具體的實現具有AbstractClass
屬性。)
編輯:
看來,我需要添加的類結構本身是如何工作的一些詳細信息,但請記住,我們的目標仍然是如何讓Pex不要存根類。
這裏是抽象基類的簡化版本及其四個具體實現。
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
的一個實例之外,我幾乎無能爲力。我寧願不必重寫被測試的代碼以適應某種特定的測試框架,但是以這樣的方式編寫測試本身,以至於從未存根AbstractClass
就是我在這裏要做的。
我希望很明顯,我所做的與例如類型安全的枚舉構造不同。另外,我匿名發佈的對象(如你所知),並且我沒有包含所有的方法,所以如果你要評論告訴我Implementation4.Equals(Implementation4)
已損壞,請不要擔心,我知道它在這裏被打破,但我的實際代碼照顧這個問題。
另一個編輯:
這裏是工廠類之一的實例。它位於Pex生成的測試項目的工廠目錄中。
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
參數控制要使用哪個構造函數。其他工廠方法具有相似的功能。我記得閱讀,雖然我不能立即找到鏈接,這些工廠方法應該允許在每種可能的配置中創建對象。
不知道你用這個實現解決了什麼問題,但是如果有人創建了另一個從基類派生的類型,那麼它聽起來像他們也會破壞你的實現。這聽起來像可能會破壞擴展性並給用戶帶來驚喜,而這兩者都是設計氣味。你可以添加一個屬性(可能'內部')到你的派生類,並簡單地搜索?然後,您不必關心PEX創建存根,因爲您不必使用它,並且不會以導致您的代碼中斷的方式進行註釋。它也不會破壞用戶代碼。 –
@ MerlynMorgan-Graham感謝您的意見。事實上,這個項目比C#更適合F#,但未來的可維護性是一個問題。這種行爲更接近「有區別的聯盟」,而不是真正的繼承。也就是說,抽象基類的四個子類表示我設置的計算結構中的一組封閉操作。沒有人會擴展它,但抽象基類和具體子類都需要在其程序集外部可見。如果你有什麼其他的意思是_internal_,我不確定它是什麼。 – Andrew
如果只有那些派生類是有意義的,那麼爲什麼要擔心 - 它會實際上破壞某些東西嗎?如果是這樣,你如何檢測派生類存在?我試圖爲您的檢測機制提供替代方案。此外,你似乎有一個模式類似於類型安全的枚舉。您可以完全遵循該模式,並將所有實現內部化,並在四個實現的基類上創建靜態工廠屬性。正確命名它們,以便它們創建正確的類型,但將它們作爲基本類型返回。 –