구체적인 구현이있는 추상 클래스를 스텁하지 않도록 Pex에 지시하는 방법
일부 코드를 테스트하기 위해 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
것이 여기서 수행하려는 작업입니다.
예를 들어, 내가하는 일이 유형이 안전한 enum 구조와 어떻게 다른지 분명하기를 바랍니다. 또한 여기에 게시하기 위해 객체를 익명으로 처리했으며 (알 수 있듯이) 모든 방법을 포함하지 않았으므로 문제가 있다는 의견을 말하려 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
매개 변수는 사용할 생성자를 제어합니다. 다른 공장 방법은 유사한 기능을 가지고 있습니다. 링크를 즉시 찾을 수는 없지만 이러한 팩토리 메서드는 가능한 모든 구성에서 개체를 만들 수 있어야한다는 것을 읽은 기억이납니다.
[PexUseType]
추상 클래스에 대한 비추 상 하위 유형이 존재한다고 속성을 사용하여 Pex에 알려 주려고 했습니까 ? Pex가 비추 상 하위 유형을 인식하지 못하는 경우 Pex의 제약 조건 솔버는 비추 상 하위 유형의 존재에 의존하는 코드 경로가 실행 불가능하다고 판단합니다.
'ProgramingTip' 카테고리의 다른 글
스캐너가 next () 또는 nextFoo ()를 후 nextLine ()을 건너 뛰나요? (0) | 2020.12.09 |
---|---|
iOS : HKObserverQuery의 백그라운드 업데이트 완료 사용 (0) | 2020.12.09 |
경고 : Visual Paradigm에서 코드 생성에 사용할 수있는 클래스 모델이 없습니다. (0) | 2020.12.09 |
더 많은 iCloud Core 데이터 동기화 문제 (0) | 2020.12.09 |
검색 릭 링크를 찾는 방법은 무엇입니까? (0) | 2020.12.08 |