2011-09-21 13 views
59

Sto provando a usare Pex per testare del codice. Ho una classe astratta con quattro implementazioni concrete. Ho creato metodi di fabbrica per ciascuno dei quattro tipi di calcestruzzo. Ne avevo anche creato uno per il tipo astratto, eccetto come spiega this nice thread, Pex non userà il metodo factory astratto, né dovrebbe.Come dire a Pex di non stubare una classe astratta che ha implementazioni concrete

Il problema è che alcuni del mio codice dipende dai quattro tipi concreti che sono tutto ci sono (dato che è molto, molto improbabile che verranno creati eventuali ulteriori sottoclassi), ma Pex sta rompendo il codice utilizzando Moles per creare uno stub.

Come posso forzare Pex ad utilizzare uno dei metodi di fabbrica (nessuno, non mi interessa) per creare istanze della classe astratta senza creare mai stub Moles per quella classe astratta? Esiste una direttiva PexAssume che lo realizzerà? Si noti che alcuni tipi di calcestruzzo formano un tipo di struttura ad albero, per cui dire ConcreteImplementation deriva da AbstractClass e ConcreteImplementation ha due proprietà di tipo AbstractClass. Ho bisogno di assicurarmi che nessun tronco sia usato in nessun punto dell'albero. (Non tutte le implementazioni concrete hanno AbstractClass proprietà.)

Edit:

Sembra che ho bisogno di aggiungere qualche informazione in più su come la struttura di classe in sé funziona, però ricordare che l'obiettivo è ancora come ottenere Pex non per sopprimere le classi.

Ecco le versioni semplificate della classe base astratta e le sue quattro implementazioni concrete.

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); 
    } 
} 

Il cuore di quello che sto cercando di testare è il metodo Distill, che come potete vedere ha il potenziale per eseguire in modo ricorsivo. Poiché uno stubbed AbstractClass non ha senso in questo paradigma, interrompe la logica dell'algoritmo. Anche provare a testare una classe con stubbing è in qualche modo inutile, dal momento che non posso fare altro che gettare un'eccezione o far finta che si tratti di un'istanza di Implementation1. Preferirei non dover riscrivere il codice sotto test per adattare un framework di test specifico in questo modo, ma scrivere il test stesso in modo tale da non eseguire mai lo stub AbstractClass è quello che sto cercando di fare qui.

Spero sia chiaro come ciò che sto facendo differisca da un costrutto enum sicuro dal tipo, per esempio. Inoltre, ho oggetti resi anonimi per la pubblicazione qui (come puoi dire), e non ho incluso tutti i metodi, quindi se hai intenzione di commentare per dirmi che Implementation4.Equals(Implementation4) è rotto, non preoccuparti, sono consapevole che è rotto qui, ma il mio codice reale si prende cura del problema.

Un'altra modifica:

Ecco un esempio di una delle classi di fabbrica. È nella directory Factories del progetto di test generato da 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; 
    } 
} 

Nei miei metodi di fabbrica per queste implementazioni concrete, è possibile utilizzare qualsiasi costruzione per creare l'attuazione concreta. Nell'esempio, il parametro useEmptyConstructor controlla quale costruttore utilizzare. Gli altri metodi di fabbrica hanno caratteristiche simili. Ricordo di aver letto, anche se non riesco a trovare immediatamente il collegamento, che questi metodi di fabbrica dovrebbero consentire la creazione dell'oggetto in ogni possibile configurazione.

+2

Non sei sicuro di quale problema stai risolvendo con quell'implementazione, ma se qualcuno dovesse mai creare un altro tipo derivato dalla classe base, sembra che anche loro potrebbero interrompere la tua implementazione. Sembra che potrebbe sia rompere l'estensibilità che sorprendere l'utente, entrambi odori di design. Puoi invece aggiungere un attributo (possibilmente 'internal') alle tue classi derivate, e semplicemente cercarlo? Quindi non devi preoccuparti che PEX crei uno stub, dal momento che non devi usarlo, e non sarà annotato in un modo che causa la rottura del tuo codice. Inoltre non si romperà il codice utente. –

+0

@ MerlynMorgan-Graham Grazie per l'input. In realtà, questo progetto è più adatto a F # rispetto a C#, ma la manutenibilità futura è una preoccupazione. Il comportamento è più vicino alla "unione discriminata" che alla vera eredità. Detto questo, le quattro sottoclassi della classe base astratta rappresentano un insieme chiuso di operazioni all'interno di una struttura di calcolo che ho impostato. Nessuno estenderà questo aspetto, ma sia la classe base astratta che le sottoclassi concrete devono essere visibili al di fuori del loro assemblaggio. Se c'è qualcos'altro che intendi per _internal_, non sono sicuro di cosa sia. – Andrew

+0

Se solo quelli per le classi derivate hanno senso, allora perché preoccuparsi - In realtà * spezzerà * qualcosa? In tal caso, come si rileva che le classi derivate esistono? Stavo cercando di fornire un'alternativa per il tuo meccanismo di rilevamento. Inoltre, sembra che tu abbia un modello simile a un enum sicuro dal tipo. È possibile seguire completamente tale schema e rendere tutte le implementazioni interne, e solo creare proprietà di fabbrica statiche sulla classe base per le quattro implementazioni. Assegnare loro un nome corretto in modo da creare il tipo giusto, ma restituirli come tipo base. –

risposta

1

Hai provato a dire a Pex che utilizza l'attributo [PexUseType], che esistono sottotipi non astratti per la tua classe astratta? Se Pex non è a conoscenza di sottotipi non astratti, il risolutore di vincoli di Pex determinerebbe che un percorso di codice che dipende dall'esistenza di un sottotipo non astratto è impossibile.

Problemi correlati