2010-08-27 2 views
1

S'il vous plaît, tenez compte de ce qui suit (je suis désolé pour la quantité de code, mais ceci est l'exemple minimal que je pouvais penser ...):Impossible de tester une méthode d'implémentation d'interface en C++. Comment puis-je tester cela?

class SomeDataThingy 
{ 
}; 

struct IFileSystemProvider 
{ 
    virtual ~IFileSystemProvider() {} 
    //OS pure virtual methods 
} 

struct DirectFileSystemProvider 
{ 
    //Simply redirects the pure virtuals from IFileSystemProvider to OS functions. 
} 

struct SomeDataBlock 
{ 
    //Stored inside SomeDataThingy; contains specific data 
    SomeDataBlock(const SomeDataThingy& subject, const IFileSystemProvider& os = DirectFileSystemProvider()) 
    { 
     //Calculate some data from the Operating System based upon a filename stored in SomeDataThingy. 
    } 
}; 

struct IFilter 
{ 
    virtual ~IFilter() {} 
    virtual int Matches(const SomeDataThingy&) const = 0; 
    virtual void Calculate(SomeDataThingy&) const = 0; 
}; 

class SomeFilter : public IFilter 
{ 
    int Matches(const SomeDataThingy& subject) const 
    { 
     if (!Subject.Contains<SomeDataBlock>()) 
      return UNKNOWN; 
     else 
      return /* This filter matches */ 
    } 
    void Calculate(SomeDataThingy& subject) const 
    { 
     std::auto_ptr<SomeDataBlock> data(new SomeDataBlock(subject)); 
     subject.Install<SomeDataBlock>(data); 
    } 
}; 

Je voudrais tester SomeFilter::calculate, ici. Le problème est que le constructeur de SomeDataBlock appelle le système de fichiers. SomeDataBlock lui-même est testé par un simulacre IFileSystemProvider. Cependant, je n'ai pas un moyen simple d'injecter le simulacre dans SomeFilter::Calculate; et malheureusement, je ne peux pas changer l'interface IFilter pour permettre au mock d'être passé en argument à Calculate, parce qu'il y a d'autres filtres pour lesquels un tel simulacre n'aurait aucun sens. Comment tester Calculate?

+0

Pouvez-vous sous-classer 'SomeDataThingy' et l'utiliser? –

+0

@ the-alchemist: Comment 'Calculate' sait-il quel' SomeDataThingy' instancier à ce point? –

Répondre

5

Pouvez-vous modifier le constructeur de SomeFilter? Si c'est le cas, vous pouvez injecter IFileSystemProvider de cette façon.

class SomeFilter : public IFilter 
{ 
public: 
    SomeFilter(const IFileSystemProvider& fs = DirectFileSystemProvider()) 
     : fs(fs) 
    { 
    } 

private: 
    int Matches(const SomeDataThingy& subject) const 
    { 
     if (!Subject.Contains<SomeDataBlock>()) 
      return UNKNOWN; 
     else 
      return /* This filter matches */ 
    } 
    void Calculate(SomeDataThingy& subject) const 
    { 
     std::auto_ptr<SomeDataBlock> data(new SomeDataBlock(subject, fs)); 
     subject.Install<SomeDataBlock>(data); 
    } 

    IFileSystemProvider fs; 
}; 

Vous pouvez aussi créer un membre du public sur SomeFilter pour permettre à l'utilisateur de fournir IFileSystemProvider, avant d'appeler Calculer, mais après la construction de l'objet.

+0

Je pense que la modification du constructeur est la voie à suivre. –

+0

C'est une de ces solutions qui vous gifle et vous fait vous demander ... pourquoi diable n'y ai-je pas pensé? : P –

+0

@Billy: Ne vous sentez pas trop mal. J'ai eu exactement le même sentiment quand j'ai rencontré cette solution pour un problème très similaire (quoique légèrement différent). Mon problème concernait le modèle de conception composite, et comment je pouvais personnaliser l'objet sans polluer l'interface abstraite. –

Questions connexes