SharePoint 2007 : Accès aux données et Test Unitaires

Pour reprendre l'exemple du Quizz là où je l'avais laissé il y a quelques semaines (cf le post de janvier ), il est désormais temps de se mettre à coder et à commencer par une des parties critiques de toutes applications : l'accès aux données.

Dans notre cas, l'accès au données se décompose en 3 parties : CategoryRepository, QuizzRepository, QuestionRepository ,

Prenon l'exemple de QuestionRepository est définit par l'interface suivante :

   1:      public interface IQuestionRepository
   2:      {
   3:          /// <summary>
   4:          /// Gets the next question.
   5:          /// </summary>
   6:          /// <param name="index">The index.</param>
   7:          /// <param name="category">The category.</param>
   8:          /// <returns>A Question</returns>
   9:          /// <exception cref="System.ArgumentException">Thrown when index is lower than 0</exception>
  10:          Question GetNextQuestion(int index, Category category);
  11:   
  12:          /// <summary>
  13:          /// Gets the next question.
  14:          /// </summary>
  15:          /// <param name="index">The index.</param>
  16:          /// <returns>A Question</returns>
  17:          /// <exception cref="System.ArgumentException">Throw when index is lower than 0</exception>
  18:          Question GetNextQuestion(int index);
  19:   
  20:          /// <summary>
  21:          /// Gets the question.
  22:          /// </summary>
  23:          /// <param name="index">The index.</param>
  24:          /// <returns>A Question</returns>
  25:          /// <exception cref="System.ArgumentException">Thrown when index is lower than 0</exception>
  26:          Question GetQuestion(int index);
  27:      }

A moi maintenant de développer l'accès aux données SharePoint en fonction de mes besoins (qui restent ici excessivement simples puisqu'il ne s'agit que d'un simple accès en lecture).

J'aurais pu décider d'implémenter l'accès au données via Linq4SP mais pour l'instant dans le cadre de ce post, je vais me restreindre à du bon vieux CAML et uniquement à l'implémentation de l'interface IQuestionRepository.

   1:  /// <summary>
   2:      /// This class is used to access the items inside a Quizz List at a specific Website
   3:      /// </summary>
   4:      [CLSCompliant(false)]
   5:      public class QuestionRepository : IQuestionRepository
   6:      {
   7:          #region Private Members
   8:          /// <summary>
   9:          /// The Quizz list where all the Queries will be processed
  10:          /// </summary>
  11:          private SPList quizzList;
  12:          #endregion
  13:   
  14:          #region Constructors
  15:   
  16:          /// <summary>
  17:          /// Initializes a new instance of the <see cref="QuestionRepository"/> class.
  18:          /// </summary>
  19:          /// <param name="web">The web used.</param>
  20:          /// <param name="listName">Name of the list.</param>
  21:          public QuestionRepository(SPWeb web, string listName)
  22:          {
  23:              if (web == null || string.IsNullOrEmpty(listName))
  24:              {
  25:                  throw new ArgumentException("Parameters 'web' or 'listName' are required and shouldn't be null or empty");
  26:              }
  27:   
  28:              if (!web.Lists.SovTryGet(listName, out this.quizzList))
  29:              {
  30:                  throw new ArgumentOutOfRangeException(
  31:                      String.Format(
  32:                      CultureInfo.InvariantCulture,
  33:                      "The list {0} doesn't exist inside the website '{1}'",
  34:                      listName,
  35:                      web.Title));
  36:              }
  37:          }
  38:          #endregion
  39:   
  40:          #region Public Methods
  41:          /// <summary>
  42:          /// Gets the next question.
  43:          /// </summary>
  44:          /// <param name="index">The index.</param>
  45:          /// <param name="category">The category.</param>
  46:          /// <returns>A Question</returns>
  47:          /// <exception cref="System.ArgumentException">Thrown when index is lower than 0</exception>
  48:          public Question GetNextQuestion(int index, Category category)
  49:          {
  50:              if (index < 0)
  51:              {
  52:                  throw new ArgumentException("GetNextQuestion : index is lower than 0");
  53:              }
  54:   
  55:              Question question = null;
  56:   
  57:              StringBuilder queryQuizzBuilder = new StringBuilder();
  58:   
  59:              queryQuizzBuilder.Append("         <OrderBy>");
  60:              queryQuizzBuilder.Append("              <FieldRef Name=\"ID\" />");
  61:              queryQuizzBuilder.Append("         </OrderBy>");
  62:              queryQuizzBuilder.Append("         <Where>");
  63:   
  64:              if (category != null && category.Id > 0)
  65:              {
  66:                  queryQuizzBuilder.Append("              <And>");
  67:                  queryQuizzBuilder.Append("                   <Eq>");
  68:                  queryQuizzBuilder.Append("                        <FieldRef Name=\"QuizzCategory\" LookupId=\"TRUE\" />");
  69:                  queryQuizzBuilder.AppendFormat("                        <Value Type=\"Lookup\">{0}</Value>", category.Id);
  70:                  queryQuizzBuilder.Append("                   </Eq>");
  71:              }
  72:   
  73:              queryQuizzBuilder.Append("                   <Gt>");
  74:              queryQuizzBuilder.Append("                        <FieldRef Name=\"ID\" />");
  75:              queryQuizzBuilder.AppendFormat("                        <Value Type=\"Counter\">{0}</Value>", index);
  76:              queryQuizzBuilder.Append("                   </Gt>");
  77:   
  78:              if (category != null && category.Id > 0)
  79:              {
  80:                  queryQuizzBuilder.Append("              </And>");
  81:              }
  82:   
  83:              queryQuizzBuilder.Append("         </Where>");
  84:   
  85:              string queryQuizzCaml = queryQuizzBuilder.ToString();
  86:   
  87:              SPQuery queryQuizz = new SPQuery();
  88:              queryQuizz.Query = queryQuizzCaml;
  89:              queryQuizz.RowLimit = 1;
  90:   
  91:              SPListItemCollection resultQuestion = this.quizzList.GetItems(queryQuizz);
  92:   
  93:              if (resultQuestion.Count > 0)
  94:              {
  95:                  SPListItem itemQuizz = resultQuestion[0];
  96:   
  97:                  question = new Question
  98:                  {
  99:                      Id = (int)itemQuizz[SPBuiltInFieldId.ID],
 100:                      Content = itemQuizz[Fields.QuizzQuestion] as string,
 101:                      Answers = this.GetAnswersForQuestion(itemQuizz),
 102:                      Category = this.GetCategoryForQuestion(itemQuizz)
 103:                  };
 104:              }
 105:   
 106:              return question;
 107:          }
 108:   
 109:          /// <summary>
 110:          /// Gets the next question.
 111:          /// </summary>
 112:          /// <param name="index">The index.</param>
 113:          /// <returns>A Question</returns>
 114:          /// <exception cref="System.ArgumentException">Throw when index is lower than 0</exception>
 115:          public Question GetNextQuestion(int index)
 116:          {
 117:              return this.GetNextQuestion(index, null);
 118:          }
 119:   
 120:          /// <summary>
 121:          /// Gets the question.
 122:          /// </summary>
 123:          /// <param name="index">The index.</param>
 124:          /// <returns>A Question</returns>
 125:          /// <exception cref="System.ArgumentException">Thrown when index is lower than 0</exception>
 126:          public Question GetQuestion(int index)
 127:          {
 128:              if (index < 0)
 129:              {
 130:                  throw new ArgumentException("GetQuestion : index is lower than 0");
 131:              }
 132:   
 133:              Question question = null;
 134:   
 135:              StringBuilder queryQuizzBuilder = new StringBuilder();
 136:   
 137:              queryQuizzBuilder.Append("         <Where>");
 138:              queryQuizzBuilder.Append("                   <Eq>");
 139:              queryQuizzBuilder.Append("                        <FieldRef Name=\"ID\" />");
 140:              queryQuizzBuilder.AppendFormat("                        <Value Type=\"Counter\">{0}</Value>", index);
 141:              queryQuizzBuilder.Append("                   </Eq>");
 142:   
 143:              queryQuizzBuilder.Append("         </Where>");
 144:   
 145:              string queryQuizzCaml = queryQuizzBuilder.ToString();
 146:   
 147:              SPQuery queryQuizz = new SPQuery();
 148:              queryQuizz.Query = queryQuizzCaml;
 149:              queryQuizz.RowLimit = 1;
 150:   
 151:              SPListItemCollection resultQuestion = this.quizzList.GetItems(queryQuizz);
 152:   
 153:              if (resultQuestion.Count > 0)
 154:              {
 155:                  SPListItem itemQuizz = resultQuestion[0];
 156:   
 157:                  question = new Question
 158:                  {
 159:                      Id = (int)itemQuizz[SPBuiltInFieldId.ID],
 160:                      Content = itemQuizz[Fields.QuizzQuestion] as string,
 161:                      Answers = this.GetAnswersForQuestion(itemQuizz),
 162:                      Category = this.GetCategoryForQuestion(itemQuizz)
 163:                  };
 164:              }
 165:   
 166:              return question;
 167:          }
 168:          #endregion
 169:      }
 170:  }

PS : l'objet Question est un objet métier crée par mes soins, composé d'un ID, d'un Contenu, de Réponses (définit par leur contenu et leur validité) et d'un Catégorie.

Bon alors tout ça, c'est très bien mais comment être réellement sur que ça fonctionne, que mes requêtes sont correctes, etc...

Et pourquoi on ne ferait pas des test unitaires qui nous permettraient de valider ces méthodes ?

image

Ok pourquoi pas, mais comment ? Et bien regardons déjà pour la première méthode : GetNextQuestionShouldReturnFirstQuestion.

   1:  using System.Collections.Generic;
   2:  using Microsoft.SharePoint;
   3:  using Microsoft.VisualStudio.TestTools.UnitTesting;
   4:  using Microsoft.VisualStudio.TestTools.UnitTesting.Web;
   5:  using TechDays.Demo.SharePoint.BusinessEntities;
   6:   
   7:  namespace TechDays.Demo.SharePoint.Model.Tests
   8:  {
   9:      /// <summary>
  10:      /// Test Class that will handle all the Tests for the Question Repository
  11:      /// </summary>
  12:      [TestClass]
  13:      public class QuestionRepositoryTests
  14:      {
  15:          /// <summary>
  16:          /// List of Questions
  17:          /// </summary>
  18:          private readonly List<Question> questions;
  19:   
  20:          /// <summary>
  21:          /// Initializes a new instance of the QuestionRepositoryTests class.
  22:          /// </summary>
  23:          public QuestionRepositoryTests()
  24:          {
  25:              this.questions = new List<Question>();
  26:   
  27:              #region Test Collection Initialization
  28:   
  29:              Question q1 = new Question()
  30:              {
  31:                  Id = 1,
  32:                  Content = "Dans quel pays, le chat domestique était vénéré à l'époque des pharaons ?",
  33:                  Category = new Category() { Id = 1, Title = "Carnivore" },
  34:                  Answers = new List<Answer>() 
  35:                  { 
  36:                      new Answer() { Content = "Egypte", IsValid = true }, 
  37:                      new Answer() { Content = "Tunisie", IsValid = false },
  38:                      new Answer() { Content = "Espagne", IsValid = false }
  39:                  }
  40:              };
  41:   
  42:              Question q2 = new Question()
  43:              {
  44:                  Id = 2,
  45:                  Content = "Quel est le reptile le plus grand ?",
  46:                  Category = new Category() { Id = 3, Title = "Reptile" },
  47:                  Answers = new List<Answer>() 
  48:                  { 
  49:                      new Answer() { Content = "Cochon d'inde", IsValid = false },
  50:                      new Answer() { Content = "Autruche", IsValid = false },
  51:                      new Answer() { Content = "Crocodile", IsValid = true }
  52:                  }
  53:              }; 
  54:              #endregion
  55:   
  56:              this.questions.Add(q1);
  57:              this.questions.Add(q2);
  58:          }
  59:   
  60:          /// <summary>
  61:          /// When GetNextQuestion(0) is called, it should return the first question.
  62:          /// </summary>
  63:          [TestMethod]
  64:          [HostType("ASP.Net")]
  65:          [UrlToTest("http://localhost/default.aspx")]
  66:          public void GetNextQuestionShouldReturnFirstQuestion()
  67:          {
  68:              Question expected = this.questions[0];
  69:              string listName = "Quizz Animaux";
  70:   
  71:              QuestionRepository repository = new QuestionRepository(SPContext.Current.Web, listName);
  72:   
  73:              Question result = repository.GetNextQuestion(0);
  74:   
  75:              Assert.AreEqual(expected.Content, result.Content);
  76:              Assert.AreEqual(expected.Category.Title, result.Category.Title);
  77:              Assert.AreEqual(expected.Answers.Count, result.Answers.Count);
  78:              Assert.AreEqual(expected.Answers[0].Content, result.Answers[0].Content);
  79:              Assert.AreEqual(expected.Answers[0].IsValid, result.Answers[0].IsValid);
  80:              Assert.AreEqual(expected.Answers[1].Content, result.Answers[1].Content);
  81:              Assert.AreEqual(expected.Answers[1].IsValid, result.Answers[1].IsValid);
  82:              Assert.AreEqual(expected.Answers[2].Content, result.Answers[2].Content);
  83:              Assert.AreEqual(expected.Answers[2].IsValid, result.Answers[2].IsValid);
  84:          }
  85:      }
  86:  }

Petit rappel important, pour que ce test fonctionne il faut savoir que j'ai actuellement un environnement de test déjà créé sur ma machine de développement (cf post précédent à ce sujet) avec toutes les données de tests dont j'ai besoin.(cf image ci-dessous)

image

Dans le constructeur QuestionRepositoryTests, j'initialise simplement une collection de réponses attendues afin que ce soit plus pratique à manipuler dans mes méthodes de tests.

Dans la méthode de test GetNextQuestionShouldReturnFirstQuestion, j'instancie mon repository "normalement", appelle la méthode GetNextQuestion "normalement" et vérifie simplement que la réponse récupérée est bien celle attendue.

La petite particularité se situe au niveau des attributs de la méthode de test GetNextQuestionShouldReturnFirstQuestion : [HostType("ASP.Net")] et [UrlToTest(http://localhost/default.aspx)]. Ces attributs me permettent de m'attacher à l'environnement SharePoint qui est sur ma machine de développement et faire les requêtes CAML comme si j'étais dans le contexte SharePoint.

Le seul inconvénient majeur étant, bien entendu, la dépendance sur l'environnement de test qui est toujours discutable pour les puristes.

Dans le prochain post, vous verrez qu'il est possible de faire des tests unitaires avec TypeMock qui nous permettront d'eviter cette dépendance.

<Philippe/>

Publié mardi 7 avril 2009 09:00 par phil
Classé sous , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

About phil

Philippe Sentenac est Consultant SharePoint à Wygwam en région Parisienne. Il intervient essentiellement sur des missions liées à SharePoint (2007 et 2010 ) mais aussi autour du Web 2.0. Plus généralement, il s'intéresse à l'ASP.Net (MVC) , à Silverlight, et à tout ce qui est orienté Web en rapport avec les nouvelles technologies, qu'il pratique depuis 2006. Féru de développement, il est passionné par les problématiques de méthodologies et d'industrialisation du développement.

Les 10 derniers blogs postés

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01

- SharePoint Online: Script PowerShell pour supprimer une colonne dans tous les sites d’une collection par Blog Technique de Romelard Fabrice le 11-27-2018, 18:01