2010-02-01 6 views
2

Je suis nouveau sur Moq et je viens juste de commencer un projet déjà en développement. Je suis responsable de la mise en place des tests unitaires. Il y a une classe personnalisée pour la DatabaseFactory qui utilise EnterpriseLibrary et ressemble à ceci:MSTest avec Moq - configuration DAL

public Database CreateCommonDatabase() 
{ 
    return CreateDatabaseInstance(string.Empty); 
} 

private static Database CreateDatabaseInstance(string foo) 
{ 
    var database = clientCode == string.Empty 
     ? DatabaseFactory.CreateDatabase("COMMON") 
     : new OracleDatabase(new ClientConnections().GetConnectionString(foo))); 
    return database; 
} 

Maintenant, voici où cela s'accoutume (resultdata est une autre classe de DataSet type):

public ResultData GetNotifications(string foo, string foo2, Database database) 
{ 
    var errMsg = string.Empty; 
    var retval = 0; 
    var ds = new DataSet(); 

    var sqlClause = 
    @"[Some SELECT statement here that uses foo]"; 

    DbCommand cm = database.GetSqlStringCommand(sqlClause); 
    cm.CommandType = CommandType.Text; 

    // Add Parameters 
    if (userSeq != string.Empty) 
    { 
     database.AddInParameter(cm, ":foo2", DbType.String, foo2); 
    } 

    try 
    { 
     ds = database.ExecuteDataSet(cm); 
    } 
     catch (Exception ex) 
    { 
     retval = -99; 
     errMsg = ex.Message; 
    } 

    return new ResultData(ds, retval, errMsg); 
} 

Maintenant, à l'origine , la base de données n'a pas été transmise en tant que paramètre, mais la méthode créait une nouvelle instance de DatabaseFactory à l'aide de la méthode CreateCommonDatabase et l'utilisait à partir de là. Cependant, cela rend la classe impossible à tester car je ne peux pas l'empêcher de toucher la base de données. Donc, je suis allé avec Dependency Injection, et passer la base de données.

Maintenant, je suis coincé, car il n'y a aucun moyen de se moquer de la base de données afin de tester GetNotifications. Je me demande si je complique trop les choses ou s'il me manque quelque chose. Est-ce que je fais cela de la bonne façon, ou devrais-je repenser comment j'ai mis en place cette configuration?

Modifier pour ajouter plus d'informations *****

Je ne veux vraiment pas tester la base de données. Je veux que la classe Data.Notifications (ci-dessus) retourne une instance de ResultData, mais c'est tout ce que je veux vraiment tester. Si je vais un haut niveau, à la couche d'affaires, j'ai ceci:

public DataSet GetNotifications(string foo, string foo1, out int returnValue, out string errorMessage, Database database) 
{ 
    ResultData rd = new data.Notifications().GetNotifications(foo, foo1, database); 

    returnValue = rd.ResultValue; 
    errorMessage = rd.ErrorMessage; 

    return rd.DataReturned; 
} 

Ainsi, à l'origine, la base de données n'a pas été transmis, ce fut la classe Data.Notifications qui l'a créé - mais là encore, Si je l'ai laissé comme ça, je n'ai pas pu m'empêcher de cliquer sur la base de données pour tester cet objet de couche Business. J'ai modifié tout le code pour passer la base de données (qui est créée sur la page de base du site web), mais maintenant je ne suis pas sûr de ce que je vais faire. Je pensais que j'étais un test unitaire loin d'avoir résolu cela, mais apparemment, soit je me trompe ou j'ai un barrage mental sur le bon chemin.

+0

En plus de mon code de test, je voudrais aussi vous recommander de vous éloigner des DataSets, s'il n'est pas trop tard. Les jeux de données particulièrement non typés ont tendance à produire un code plus fragile, selon mon expérience. Bonne chance. –

+0

Anderson - Je suis totalement d'accord, mais ce n'est pas mon choix, malheureusement. Je vais devoir parler bientôt à l'architecte de la mise en œuvre de l'inversion de contrôle/de l'injection de dépendances, mais je pense que ces changements sont à peu près aussi loin que je pourrais le faire. –

+0

bien au moins vous serez la voix de quelque raison. Je travaillerais pour que votre code soit aussi proche que possible de l'injection de dépendance, de sorte que lorsque vous serez capable d'utiliser un outil comme Unity (ou un autre conteneur IoC), vous serez prêt à retourner un commutateur. –

Répondre

2

Vous devez être en mesure de créer un objet Base de données simulé si les méthodes qu'il contient sont virtuelles. Si ce n'est pas le cas, vous avez un petit problème.

Je ne sais pas quel type "base de données" est, mais vous avez quelques options.

  1. Si vous possédez le code source de base de données, je recommande l'extraction d'une IDatabase d'interface, plutôt que de traiter un type de classe de base de données. Cela éliminera une certaine complexité et vous donnera quelque chose d'extrêmement testable.

  2. Si vous n'avez pas accès à la classe Database, vous pouvez toujours résoudre ce problème avec une autre couche d'abstraction. Dans ce cas, de nombreuses personnes utilisent un modèle Repository qui enveloppe la couche d'accès aux données. D'une manière générale dans ce cas, la plupart des gens laissent tester des classes Respository à des tests d'intégration (tests sans isolation) plutôt qu'à des tests unitaires.

est Voici comment vous configurer votre test en utilisant l'option # 1:

[TestMethod] 
public void GetNotifications_PassedNullFoo_ReturnsData() 
{ 
    //Arrange 
    Mock<IDatabase> mockDB = new Mock<IDatabase>(); 
    mockDB.Setup(db => db.ExecuteDataSet()).Returns(new DataSet() ...); 

    //Act 
    FooClass target = new fooClass(); 
    var result = target.GetNotifications(null, "Foo2", mockDB.Object); 

    //Assert 
    Assert.IsTrue(result.DataSet.Rows.Count > 0); 
} 

Mon code ensemble de données est un peu rouillé, mais nous espérons que cela vous donne l'idée générale.

+0

Base de données est dans l'espace de noms MS EnterpriseLibrary, donc c'est le code MS, pas mon code. Et les méthodes sont statiques, c'est pourquoi je ne peux pas les moquer. On m'a dit que nous utiliserions Unity dans les mois à venir, ce qui, si je comprends bien, résoudrait le problème, mais ce n'est pas une option pour aujourd'hui. –

+0

@Misty Fowler: alors vous iriez avec l'option # 2 et créer un référentiel qui utilise ensuite ces méthodes statiques, mais est lui-même une classe d'instance avec un ensemble de méthodes d'instance qui implémentent une interface plus simple, comme IFooRepository . Vous aurez une implémentation standard de ceci et lors d'un test unitaire, vous injecterez votre maquette, plutôt que la vraie. Une fois que vous obtenez Unity, vous pouvez simplement faire container.RegisterType () puis container.Resolve () et il fera automatiquement l'injection pour vous. –

+0

@Misty Fowler: # 2 va probablement vous rendre encore plus prêt pour Unity. –

1

Basé sur le code que vous avez donné, je pense que vous voudriez voulez pour parler à la base de données, et pas une version mocked.

La raison en est que votre code GetNotifications contient des instructions spécifiques à la base de données et que vous souhaitez que celles-ci soient validées au niveau du moteur DB. Il vous suffit donc de transmettre une base de données connectée à votre instance de base de données de test. Si vous avez amené l'abstraction de test à un niveau supérieur, où vous avez construit des tests unitaires pour l'appel de base de données et une version de ce test utilisant une base de données simulée, vous devrez exécuter des tests d'intégration. tripler le travail pour la même quantité de couverture de code. À mon avis, il est beaucoup plus efficace de faire un test d'intégration aux frontières de niveau que vous contrôlez puis d'écrire des tests unitaires pour les deux côtés du contrat et tests d'intégration.

+0

Je ne peux pas coller le code dont j'ai besoin ici. Donc, je vais modifier mon message original. –