Dans une série d'articles de ce titre, nous examinerons les implémentations pour les tests et la production, en utilisant des modèles de conception. Dans l'article précédent (http://qiita.com/progrommer/items/a04a74d8b9f43eaef4b9), j'ai présenté le modèle Proxy et le modèle Monostate, qui ont des concepts de base et des formes faciles à utiliser. Cette fois, je décrirai comment concevoir la classe à tester unitaire. Dans la continuité de la dernière fois, le thème est l'injection de dépendances (DI).
L'exemple de programme est un mélange de Python, C # et C ++, mais nous prévoyons de le compléter en temps voulu. S'il vous plaît, pardonnez-moi.
Certaines implémentations de classe (ou de fonction) difficiles à tester sont créées et utilisées au même endroit.
// Python
class Some:
def get(self):
return 42
def func():
some = Some()
value = some.get()
if value < 10:
pass
// C++
struct Some {
int get() const {return 42;}
};
void func() {
if (Some().get() < 10) return;
}
Dans cet exemple, une instance de la classe Some est créée à la volée et la fonction est appelée. Cela rend impossible toute intervention dans Some, même si vous essayez de tester la fonction func.
Soyez prudent lorsque vous créez un membre dans le constructeur, puis que vous l'utilisez dans n'importe quelle fonction. Dans l'exemple suivant, le code de test de la classe Target peut remplacer m_some après avoir généré Some, et il y a de la place pour une intervention. À première vue, cela semble bon. Cependant, si vous appelez Some à partir d'une autre classe ou fonction, vous ne pouvez pas tester cette classe ou cette fonction.
// C++
Target::Target()
: m_some(new Some("abc"))
{
}
Target::exec() {
m_some->get();
}
//Une autre fonction à tester
void func() {
Target target;
target.exec(); //Original Some est généré et utilisé dans Target
}
Si la génération et l'utilisation fixes mentionnées ci-dessus sont effectuées, le test unitaire devient difficile. Une fois qu'une seule de ces classes est créée, il est difficile de la démêler. Par conséquent, il est souhaitable de concevoir la classe en séparant la génération et l'utilisation du stade le plus précoce. Il existe plusieurs façons de procéder, mais voici quelques exemples qui sont en fait faciles à utiliser.
Il existe plusieurs modèles de "génération" dans le modèle de conception, mais utilisons l'un d'entre eux, le modèle de fabrique abstraite.
Dans l'exemple suivant, la classe utilisateur Client utilise un générateur appelé Creator. Vous créez les instances Logger et File dont vous avez besoin à partir de l'instance Creator que vous avez reçue. La substance de Creator est TestCreator créé dans main (), et il a été remplacé par une classe de test qui sort sur la console et génère un fichier factice.
Nom de classe dans la figure | Exemple de nom de classe |
---|---|
ICreator | Creator |
TargetCreator | Aucun |
Target1 | Logger |
Target2 | File |
Target1Object | Aucun |
Target2Object | Aucun |
ClientTest | fonction principale |
DummyCreator | TestCreator |
Dummy1Object | ConsoleLogger |
Dummy2Object | DummyFile |
# Python
class Logger:
def write(self, msg: str) -> None:
pass
class ConsoleLogger(Logger):
def write(self, msg: str) -> None:
print(msg)
class File:
pass
class DummyFile(File):
pass
class Creator:
def create_logger(self) -> Logger:
return None
def create_file(self) -> File:
return None
class TestCreator(Creator):
def create_logger(self) -> Logger:
return ConsoleLogger()
def create_file(self) -> File:
return DummyFile()
class Client:
def __init__(self, creator: Creator):
self._logger = creator.create_logger()
self._file = creator.create_file()
def exec(self) -> None:
#Quelque chose de traitement
self._logger.write('Réussite')
def main():
creator = TestCreator()
client = Client(creator)
client.exec()
Un autre exemple de modèle de «génération» est Singleton. Singleton ne peut pas intervenir car la génération est fermée à l'intérieur de sa classe. Le but est de ne pas pouvoir intervenir, mais il est difficile à utiliser pour les tests.
Ici, nous assouplissons un peu la structure de Singleton et préparons une bouche pour l'intervention. L'instance est protégée et toutes les méthodes utilisent NVI ou simplement virtuelles.
Voici un exemple de remplacement de la classe Singleton et de la classe de test en C #. Le deuxième appel à Print () qui bascule vers Dummy imprime "Dummy" pour la même méthode. Au moment du test unitaire, il doit être défini dans le processus préparatoire afin que Dummy soit appelé dès le début.
// C#
public class Some {
protected static Some instance;
static Some() {
instance = new Some();
}
static public Some getInstance() {
return instance;
}
virtual public void Print() {
Console.WriteLine("Some");
}
}
internal class SomeDummy : Some {
internal static void UseDummy() {
instance = new SomeDummy();
}
public override void Print() {
Console.WriteLine("Dummy");
}
}
public class Client {
static void Main(string[] args) {
{
var target = Some.getInstance();
target.Print(); // "Some"Et sortie
}
SomeDummy.UseDummy(); //Remplacer Singleton
{
var target = Some.getInstance();
target.Print(); // "Dummy"Et sortie
}
}
}
L'usine abstraite peut être ennuyeuse car elle doit passer l'usine à la génération de classe. Des applications telles que la fourniture d'une usine comme Singleton ou Monostate peuvent être envisagées. Bien sûr, au moment du test unitaire, renvoyez une usine qui génère une classe factice pour le test. En utilisant ceci pour remplacer le premier exemple:
// Python
class FactoryServer:
some_creator = SomeCreator()
def func():
some = FactoryServer.some_creator()
value = some.get()
if value < 10:
pass
Un exemple de séparation de la génération et de l'utilisation est présenté.
Dans l'exemple de l'usine abstraite, nous avons montré une structure dans laquelle la classe réelle et la classe pour les tests unitaires peuvent être librement remplacées en faisant abstraction de l'objet pour la création et l'utilisation. Dans l'exemple Singleton, le produit est intervenu pour utiliser une instance de test. En tant qu'application, en combinant Singleton / Monostate et Abstract factory, nous avons donné une configuration dans laquelle l'intervention de test peut être établie en même temps tout en conservant la simplicité de mise en œuvre.
Recommended Posts