% -*- mode: latex; -*- mustache tags:  
\ifx\wholebook\relax\else

	\documentclass[10pt,twoside,english]{root/support/latex/sbabook/sbabook}
  \usepackage{import}
  \subimport{root/support/latex/}{common.tex}

  \hypersetup{pdfinfo = {
    Title = {L'application TinyBlog : extension du modèle et tests unitaires},
    Author = {},
    Keywords = {pharo, smalltalk}}}
  \begin{document}
\fi

\chapter{TinyBlog : Extension du mod\`{e}le et tests unitaires}\section{Correction semaine pr\'{e}c\'{e}dente}
Vous pouvez charger la correction de la semaine pr\'{e}c\'{e}dente en ex\'{e}cutant le code suivant :

\begin{displaycode}{smalltalk}
Gofer new 
   smalltalkhubUser: 'PharoMooc' project: 'TinyBlog';
   package: 'ConfigurationOfTinyBlog';
   load. 
#ConfigurationOfTinyBlog asClass loadWeek1Correction
\end{displaycode}

Vous pouvez ouvrir un browser de code sur la classe \textcode{TBPost}.
Maintenant que vous avez la correction, vous pouvez compl\'{e}ter le code de votre application TinyBlog si n\'{e}cessaire.
\section{Sauvegarder votre code }
Alors que vous pouvez sauver l'image Pharo qui contient tous les objets du syst\`{e}me et donc les classes elles-m\^{e}me, nous allons vous montrer comment les pharoers sauvent leur code: Vous allez sauver votre code sous forme de packages sur un serveur d\'{e}di\'{e} \`{a} l'aide de Monticello le gestionnaire de versions de Pharo. Smalltalkhub \url{http://smalltalkhub.com} est une des forges pour sauver le code Pharo, vous pouvez aussi utiliser SS3 \`{a} \url{http://ss3.gemstone.com}. 
\subsection{Cr\'{e}er un d\'{e}p\^{o}t de code}
\begin{itemize}
\item Cr\'{e}er un compte sur le site  \url{http://smalltalkhub.com/}.
\item Se connecter au site. 
\item Cr\'{e}er un projet (il se peut que vous ayez des probl\`{e}mes de connexion car le site est en beta version auquel cas essayez de changer de navigateur, si les probl\`{e}mes persistent utiliser \url{http://ss3.gemstone.com}).
\begin{itemize}
\item Nommez le \symbol{34}TinyBlog\symbol{34}.
\end{itemize}

\end{itemize}
\subsection{Sauver votre package}
\begin{itemize}
\item Dans Pharo, ouvrez le Monticello Browser via le menu du monde.
\item Ajouter un repository (de type soit SmalltalkHub soit HTTP pour \url{http://ss3.gemstone.com}).
\item S\'{e}lectionner le repository et s\'{e}lectionner l'item 'Add to package...' pour ajouter ce repository au package TinyBlog.
\item S\'{e}lectionner votre package et pressez le bouton 'Save'. 
\item Editer l'information de description et sauver. Votre code vient d'\^{e}tre sauv\'{e} sur le serveur.
\end{itemize}

Le code de votre application TinyBlog est maintenant sauvegard\'{e} dans votre d\'{e}p\^{o}t sur Smalltalkhub. Il est donc maintenant possible de charger votre code dans une nouvelle image Pharo. Par contre, cela va prendre un certain temps \`{a} charger car vous allez charger Seaside et plusieurs autres projets. 

Dans le cadre de ce projet, nous vous sugg\'{e}rons de toujours utiliser l'image avec tous les packages web charg\'{e}s que vous trouvez sur \url{http://mooc.pharo.org}. Cela vous permettra de pouvoir recharger votre package sans devoir vous soucier des d\'{e}pendances sur d'autres packages. 
\section{A propos de d\'{e}pendances}
Les bonnes pratiques lors de d\'{e}veloppements en Pharo sont de sp\'{e}cifier clairement les d\'{e}pendances sur les packages utilis\'{e}s afin d'avoir une reproductibilit\'{e} compl\`{e}te d'un projet. Une telle reproducibilit\'{e} permet alors l'utilisation de serveur de construction tel Travis ou Jenkins. 
Pour cela, une configuration (une classe sp\'{e}ciale) d\'{e}finit d'une part l'architecture du projet (dependances et packages du projet) et les versions des packages versionn\'{e}s. 

Dans le cadre de ce projet, nous n'abordons pas ce point plus avanc\'{e}. Un chapitre entier sur l'expression de configuration dans Deep Into Pharo est consacr\'{e} \`{a} ce point. 
 
\section{La classe TBBlog}
La classe \textcode{TBBlog} contient des posts. 
Nous allons d\'{e}velopper \textcode{TBBlog} en \'{e}crivant des tests puis en les impl\'{e}mentant.

\begin{displaycode}{smalltalk}
Object subclass: #TBBlog
   instanceVariableNames: 'posts'
   classVariableNames: ''
   package: 'TinyBlog'
\end{displaycode}

Nous initialisons les postes sur une collection vide.

\begin{displaycode}{smalltalk}
TBBlog >> initialize
   super initialize.
   posts := OrderedCollection new. 
\end{displaycode}
\section{Un seul blog}
Dans un premier temps nous supposons que nous allons g\'{e}rer qu'un seul blog. Dans le futur, vous pourrez ajouter la possibilit\'{e} de g\'{e}rer plusieurs blogs comme un par utilisateur de notre application. Pour l'instant, nous utilisons donc un singleton pour la classe \textcode{TBBlog}.

Comme la gestion du singleton est un comportement de classe, ces m\'{e}thodes sont d\'{e}finies sur le cot\'{e} class de la classe \textcode{TBBlog}. 

\begin{displaycode}{smalltalk}
TBBlog class
   instanceVariableNames: 'uniqueInstance'
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog class >> reset
   uniqueInstance := nil
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog class >> current
   "answer the instance of the TBRepository"
   ^ uniqueInstance ifNil: [ uniqueInstance := self new ]
\end{displaycode}

Quand la classe est charg\'{e}e, le singleton est r\'{e}initialis\'{e}. 

\begin{displaycode}{smalltalk}
TBBlog class >> initialize
   self reset
\end{displaycode}
\section{Tester les R\`{e}gles M\'{e}tiers}
Nous allons \'{e}crire des tests pour les r\`{e}gles m\'{e}tiers et ceci en mode TDD (Test Driven Development) c'est-\`{a}-dire en d\'{e}veloppant les tests en premier puis en d\'{e}finissant les fonctionalit\'{e}s jusqu'\`{a} ce que les tests passent.

Les tests unitaires sont regroup\'{e}s dans une \'{e}tiquette (tag) \textcode{TinyBlog-Tests} qui contient la classe \textcode{TBBlogTest} (voir menu item \symbol{34}Add Tag...\symbol{34}). Un tag est juste une \'{e}tiquette qui permet de trier de grouper les classes \`{a} l'int\'{e}rieur d'un package. Nous utilisons un tag pour ne pas avoir \`{a} g\'{e}rer deux packages diff\'{e}rents mais dans une r\'{e}elle solution vous d\'{e}finirions une package s\'{e}par\'{e} pour les tests.

\begin{displaycode}{smalltalk}
TestCase subclass: #TBBlogTest
   instanceVariableNames: 'blog post first'
   classVariableNames: ''
   category: 'TinyBlog-Tests'
\end{displaycode}

Avant le lancement des tests, la m\'{e}thode \textcode{setUp} initialise le contexte des tests.
Par exemple, elle efface son contenu, ajoute un post et en cr\'{e}\'{e} un autre qui provisoirement n'est pas enregistr\'{e}.

\begin{displaycode}{smalltalk}
TBBlogTest >> setUp
   blog := TBBlog current.
   blog removeAllPosts.

   first := TBPost title: 'A title' text: 'A text' category: 'First Category'.
   blog writeBlogPost: first.

   post := (TBPost title: 'Another title' text: 'Another text' category: 'Second Category') beVisible
\end{displaycode}

On en profite pour tester diff\'{e}rentes configurations. 
Les posts ne sont pas dans la m\^{e}me cat\'{e}gorie, l'un est visible, l'autre pas.

La m\'{e}thode \textcode{tearDown} ex\'{e}cut\'{e}e au terme des tests remet \`{a} z\'{e}ro le blog. 

\begin{displaycode}{smalltalk}
TBBlogTest >> tearDown
   TBBlog reset
\end{displaycode}

L'utilisation d'un Singleton montre ses limites. En effet, si vous d\'{e}ployez un blog puis ex\'{e}cutez les tests vous perdrez les posts que vous avez cr\'{e}\'{e}s car nous les remettons \`{a} z\'{e}ro.

Nous allons d\'{e}velopper les tests d'abord puis ensuite passer \`{a} l'impl\'{e}mentation des fonctionnalit\'{e}s.
\section{Un premier test}
Commen\c{c}ons par \'{e}crire un premier test qui ajoute un post et v\'{e}rifie qu'il est effectivement ajout\'{e}.

\begin{displaycode}{smalltalk}
TBBlogTest >> testAddBlogPost
   blog writeBlogPost: post.
   self assert: blog size equals: 2
\end{displaycode}

Ce test ne marche pas (n'est pas vert) car nous n'avons pas d\'{e}fini les m\'{e}thodes: \textcode{writeBlogPost:} et \textcode{removeAllPosts}.
Ajoutons-les.

\begin{displaycode}{smalltalk}
TBBlog >> removeAllPosts
   posts := OrderedCollection new
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> writeBlogPost: aPost
   "Write the blog post in database"
   posts add: aPost
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> size
   ^ posts size
\end{displaycode}

Le test pr\'{e}c\'{e}dent doit maintenant passer.

Ecrivons un test pour couvrir les fonctionalit\'{e}s que nous venons de d\'{e}velopper. 
\subsection{Obtenir le nombre de posts dans le blog}
\begin{displaycode}{smalltalk}
TBBlogTest >> testSize
   self assert: blog size equals: 1
\end{displaycode}
\subsection{Effacer l'int\'{e}gralit\'{e} des posts}
\begin{displaycode}{smalltalk}
TBBlogTest >> testRemoveAllBlogPosts
   blog removeAllPosts.
   self assert: blog size equals: 0
\end{displaycode}
\section{Quelques autres fonctionnalit\'{e}s}
Nous d\'{e}finissons les fonctionnalit\'{e}s et nous assurons que les tests passent. 
Les r\`{e}gles m\'{e}tiers sont regroup\'{e}es dans le protocole 'action' de la classe \textcode{TBBlog}.
\subsection{Obtenir l'ensemble des posts (visibles et invisibles)}
\begin{displaycode}{smalltalk}
TBBlogTest >> testAllBlogPosts
   blog writeBlogPost: post.
   self assert: blog allBlogPosts size equals: 2
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> allBlogPosts
   ^ posts
\end{displaycode}
\subsection{Obtenir tous les posts visibles}
\begin{displaycode}{smalltalk}
TBBlogTest >> testAllVisibleBlogPosts
   blog writeBlogPost: post.
   self assert: blog allVisibleBlogPosts size equals: 1
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> allVisibleBlogPosts
   ^ posts select: [ :p | p isVisible ]
\end{displaycode}
\subsection{Obtenir tous les posts d'une cat\'{e}gorie}
\begin{displaycode}{smalltalk}
TBBlogTest >> testAllBlogPostsFromCategory
   self assert: (blog allBlogPostsFromCategory: 'First Category') size equals: 1
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> allBlogPostsFromCategory: aCategory
   ^ posts select: [ :p | p category = aCategory ]
\end{displaycode}
\subsection{Obtenir tous les posts visibles d'une cat\'{e}gorie}
\begin{displaycode}{smalltalk}
TBBlogTest >> testAllVisibleBlogPostsFromCategory
   blog writeBlogPost: post.
   self assert: (blog allVisibleBlogPostsFromCategory: 'First Category') size equals: 0.
   self assert: (blog allVisibleBlogPostsFromCategory: 'Second Category') size equals: 1
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> allVisibleBlogPostsFromCategory: aCategory
   ^ posts select: [ :p | p category = aCategory and: [ p isVisible ] ]
\end{displaycode}
\subsection{V\'{e}rifier la gestion des posts non class\'{e}s}
\begin{displaycode}{smalltalk}
TBBlogTest >> testUnclassifiedBlogPosts
   self assert: (blog allBlogPosts select: [ :p | p isUnclassified ]) size equals: 0
\end{displaycode}
\subsection{Obtenir la liste des cat\'{e}gories}
\begin{displaycode}{smalltalk}
TBBlogTest >> testAllCategories
   blog writeBlogPost: post.
   self assert: blog allCategories size equals: 2
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> allCategories
   ^ (self allBlogPosts collect: [ :p | p category ]) asSet
\end{displaycode}
\section{Futures \'{e}volutions}
Plusieurs \'{e}volutions peuvent \^{e}tre apport\'{e}es telles que: obtenir uniquement la liste des cat\'{e}gories contenant au moins un post visible, effacer une cat\'{e}gorie et les posts contenus, renommer une cat\'{e}gorie, d\'{e}placer un post d'une cat\'{e}gorie \`{a} une autre, rendre visible ou invisible une cat\'{e}gorie et son contenu, etc. Nous vous encourageons \`{a} les d\'{e}velopper.

Afin de nous aider \`{a} tester l'application nous d\'{e}finissons une m\'{e}thode permettant de cr\'{e}er quelques posts. 

\begin{displaycode}{smalltalk}
TBBlog class >> createDemoPosts
   "TBBlog createDemoPosts"
   self current 
      writeBlogPost: ((TBPost title: 'Welcome in TinyBlog' text: 'TinyBlog is a small blog engine made with Pharo.' category: 'TinyBlog') visible: true);
      writeBlogPost: ((TBPost title: 'Report Pharo Sprint' text: 'Friday, June 12 there was a Pharo sprint / Moose dojo. It was a nice event with more than 15 motivated sprinters. With the help of candies, cakes and chocolate, huge work has been done' category: 'Pharo') visible: true);
      writeBlogPost: ((TBPost title: 'Brick on top of Bloc - Preview' text: 'We are happy to announce the first preview version of Brick, a new widget set created from scratch on top of Bloc. Brick is being developed primarily by Alex Syrel (together with Alain Plantec, Andrei Chis and myself), and the work is sponsored by ESUG. 
      Brick is part of the Glamorous Toolkit effort and will provide the basis for the new versions of the development tools.' category: 'Pharo') visible: true);
      writeBlogPost: ((TBPost title: 'The sad story of unclassified blog posts' text: 'So sad that I can read this.') visible: true);
      writeBlogPost: ((TBPost title: 'Working with Pharo on the Raspberry Pi' text: 'Hardware is getting cheaper and many new small devices like the famous Raspberry Pi provide new computation power that was one once only available on regular desktop computers.' category: 'Pharo') visible: true)
\end{displaycode}

Vous pouvez ensuite inspecter le r\'{e}sultat de l'\'{e}valuation du code suivant :

\begin{displaycode}{smalltalk}
	TBBlog createDemoPosts ; current
\end{displaycode}
\section{Conclusion}
Vous devez avoir le mod\`{e}le complet de TinyBlog ainsi que des tests et maintenant vous \^{e}tes pr\^{e}t pour des fonctionalit\'{e}s plus avanc\'{e}es comme le stockage ou un premier serveur HTTP. C'est aussi un bon moment pour sauver votre code.


\ifx\wholebook\relax\else
\end{document}
\fi