Comment bien accueillir un-e nouvel-le employé-e : checklist

icon Tags de l'article :

Décembre 10, 2018
Jusqu'à présent, je n'ai été vraiment bien accueilli que dans une seule société parmi les 15 où j'ai pu travailler (que ce soit sur des missions courtes ou longues).

Je trouve ça dommage. Vraiment dommage. Que la personne soit prestataire ou interne, je trouve ça triste de voir que si peu de sociétés font l'effort de préparer une "bonne" intégration.

J'ai donc décider de partager avec vous une checklist de tout ce qu'il faudrait anticiper / faire / communiquer pour offrir une vraie bonne intégration à un-e nouvel-le employé-e. (selon moi).

A préparer en amont

  • La place et le poste de travail (rien de pire que d'arriver et de devoir squatter le poste d'un mec en vacances ou de devoir utiliser un vieux laptop pourri)
  • La liste des logiciels à installer / configurer / utiliser, et pourquoi
  • Se réserver deux à trois heures le premier jour avec le nouveau/la nouvelle pour présenter la boite, les locaux, l'équipe, le projet, etc.
  • Planifier le premier repas avec toute l'équipe (pour éviter que chacun-e ne parte chercher dans son coin et que le nouveau/la nouvelle se retrouve seul-e, déjà vécu...)
  • Prévoir une liste de sujets qu'il faudra aborder et des plages horaires de formation dédiées à ces sujets (avec chacun des membres de l'équipe)
  • Le badge d'accès ou la clef des locaux
  • Demander à l'équipe d'inclure au maximum, dès son arrivée, la nouvelle personne dans leurs routines et leurs échanges (repas, pauses, réflexions, mini-réunions, etc.)

Les informations à donner (de préférence par écrit !)

  • Les horaires officielles
  • Les horaires officieuses / tolérées
  • Règles : ce qui est autorisé (flexibilité des horaires, télétravail, appels téléphoniques, absences médicales) et ce qui est interdit (quitter les locaux sans prévenir, jouer au ping pong/babyfoot à certaines heures, etc.)
  • Où se trouvent les toilettes
  • Où mange l'équipe en général (restaurants ? ils vont chercher ? si oui où ?)
  • La liste des logiciels à installer (ordre, versions, etc.)
  • Un plan du bureau/openspace avec les noms de chaque personne (si possible avec photos)
  • Un guide basique sur les logiciels et les routines associées (déclaration des heures par exemple)
  • Une fiche résumée de ce qui a été abordé le premier jour : méthodos de travail, outils, vision macro du projet, vision produit du projet, présentation rapide de l'équipe, détail de l'architecture du projet, etc.

Premiers jours

  • Avoir préparé la liste des premières tâches / premiers sujets pour le nouveau/la nouvelle
  • Avoir détaillé le plus possible ces tâches/sujets
  • Inviter la nouvelle personne dans les réunions (nouvelles, existantes et récurrentes)
  • Organiser un point quotidien de 30 minutes avec le nouveau/la nouvelle pour suivre son avancement, l'aider, donner des conseils, débloquer une situation, etc.
  • Ne PAS lui demander d'effectuer des tâches désagréables ou complexes qu'on ne ferait pas soi-même. Ou qui ne nous font pas du tout envie.
Pour rappel, un-e bon-ne manager c'est quelqu'un qui délègue ce qu'il/elle aime faire, pas ce qu'il/elle n'aime pas faire. Car ainsi il/elle pourra toujours être disponible pour aider, accompagner, encourager, et offrir plus d'autonomie à son équipe. Après-tout... l'objectif ultime d'un-e bon-ne manager c'est de créer l'équipe la plus autonome possible.

Au jour le jour

  • Mettre de côté les sujets parfaits pour un nouveau (par exemple un sujet pas du tout critique, simple mais long, et qui fait toucher à beaucoup de morceaux de code)
  • Documenter l'installation du poste : les problèmes rencontrés, les logiciels à installer, dans quelle versions, etc. Mettre à jour à chaque changement / arrivée d'une nouvelle personne.
  • Essayer de maintenir à jour un Wiki le plus clair possible (et avec suffisamment d'informations, même si la clarté et la concision sont plus importantes)
  • Essayer de garder à jour un plan des bureaux (avec les noms des personnes) et (si possible) un trombinoscope de l'équipe (rien de mieux pour un nouveau ou une nouvelle)

Ca semble extrême non ?

"Oui mais on a un nouveau que tous les 6 mois, ça fait un max de taf pour pas grand chose, la personne apprendra d'elle-même !"

Trois conséquences possibles (je dirais même probables) quand on fait mal les choses :
  • Si la personne est timide, introvertie ou mal à l'aise au début, elle risque de passer un très mauvais moment (impression de gêner, n'ose pas poser de questions, mal à l'aise en permanence...)
  • On passera toujours plus de temps à répondre aux questions et à penser à tout "après" qu'avant. Le temps de préparation de l'intégration de la personne sera toujours rentable (4 heures à préparer l'intégration c'est facilement 6 à 8 heures de gagnées derrière, car personne sera efficace plus vite, rencontrera moins de blocages, aura moins de questions, dérangera moins l'équipe, etc.).
  • Parfois des dérives vraiment problématiques peuvent apparaitre. Exemple : dans mon taf actuel où j'ai 12h à rattraper car on ne m'avait pas communiqué les bonnes horaires hebdomadaires attendues...

Après le plus important reste... les efforts humains. Une des meilleures intégrations que j'ai pu connaître s'est faite grâce à des gens qui venaient souvent vers moi, qui cherchaient à m'intégrer dans les conversations, qui étaient curieuses de mes passions, et qui plaisantaient avec moi !

De plus, en plus vous préparez une intégration, en plus la suivante sera facile et rapide à préparer ! ;)

De l'importance de bien s'approprier son environnement de dev

icon Tags de l'article : ,

Novembre 28, 2018
Depuis 1 mois, j'ai commencé une nouvelle mission sur des technos qui n'ont rien à voir avec ce que je faisais avant.

Adieu le .Net et le web, maintenant je fais du mobile !
Bon, ok, en Angular/Ionic. Mais quand même.

Du coup, en commençant sur une nouvelle techno, j'ai dû passer par une étape difficile : l'appropriation d'un nouvel IDE.
Et bien aujourd'hui, ça se fait vraiment bien.

J'étais (et je suis toujours) très attaché à mon Visual Studio. J'ai considéré, pendant des années, que c'était le meilleur IDE du monde. Tellement avancé, efficace, performant...
Et là, je suis passé sur Webstorm de JetBrains.

Déjà, inutile de se le cacher, l'IDE est magique. Performant, rapide, doué dans tout... C'est l'équivalent web de Visual Studio. Il est même meilleur pour la partie JavaScript. (il vaut largement ses 6$ par mois de licence)

Mais plein de questions sont apparues... Comment m'habituer à mon nouvel environnement de travail ? Comment ne pas prendre plein d'habitudes qui me feraient perdre mes anciennes habitudes ?

Car j'ai quand même fait le grand saut : Clavier azerty + Windows + Visual Studio ===> Clavier qwertz + Mac + JetBrains WebStorm.

Du coup que faire ?
Personnellement, j'ai décidé que je n'allais pas m'adapter.

Je m'explique : je ne veux pas avoir à réapprendre par coeur tous les raccourcis clavier que je maîtrise depuis 8 ans.
Je sais que j'en suis capable, je suis même plutôt doué (j'ai réappris l'intégralité des raccourcis sur VSCode en une semaine), mais je veux pouvoir repasser sur un Visual Studio sans galérer.

Du coup, il y a un mois, j'ai ramené un clavier azerty (perso) que j'utilise depuis, et j'ai configuré mon OSX et mon WebStorm pour qu'ils utilisent exactement les mêmes raccourcis clavier que mon IDE classique, à savoir Visual Studio.

Ok... c'est chiant. Ok, ça prend une demi-journée. Ok, il faut rechercher l'appellation de chaque raccourci. Ok il faut debind/rebind plein de touches. Mais le résultat est incroyable.
Passé cette configuration, on retrouve instantanément ses réflexes. Sa réactivité. Sa vitesse de développement.

D'ailleurs ces deux derniers jours, pour mes cours, j'ai dû repasser sur du Windows / Azerty / Visual Studio.
Et bien ça s'est fait sans soucis. Je développe toujours aussi vite qu'avant. Je n'ai rien perdu.

0 temps d'adaptation. 0

Bref, lorsque vous passez sur un nouvel IDE, posez vous les bonnes questions :
  • Est-ce que je veux réapprendre tous les raccourcis clavier ? Ou configurer les miens ?
  • Police de caractères ? Thème de couleurs ? Même question.
  • Est-ce qu'il n'existe pas des plugins pour telle ou telle feature qui me manque ?
  • Est-ce que je préfère galérer 2 heures à tout configurer, ou 1 semaine à tout réapprendre ?

N'oubliez pas une chose. Les raccourcis clavier et la maîtrise de son environnement de dev, c'est une différence majeure entre un ninja dev et un dev moyen.

Quelqu'un qui ne développe qu'à la souris ne sera jamais aussi bon et performant qu'un dev qui maîtrise vraiment son IDE et ses raccourcis clavier.

A titre d'exemple, voici tous les raccourcis clavier que j'utilise quotidiennement (oui, quotidiennement) :
  • atteindre un fichier CTRL + ,
  • atteindre une classe CTRL + MAJ + ,
  • rechercher dans le fichier CTRL + F
  • rechercher dans tous les fichiers CTRL + MAJ + F
  • commenter CTRL + K => C
  • décommenter CTRL + K => U
  • réindenter CTRL + K + D
  • résoudre le problème que remonte l'IDE là où je suis (tooltip d'aide) CTRL + ;
  • lancer l'application F5
  • stopper le build CTRL + PAUSE
  • plier les méthodes CTRL + M + O
  • déplier les méthodes CTRL + M + L
  • créer un bookmark CTRL + K + K
  • lister ses bookmarks CTRL + K + L
  • mettre un point d'arrêt F9
  • renommer F2
  • refactorer CTRL + R
  • switcher entre vue et code behind CTRL + MAJ + HAUT/BAS
  • débugger (F10/F11)

Bref, si vous voulez devenir un ninja dev, prenez le temps de vous approprier votre environnement de dev !

En résumé : .Net Core, qu'est ce que c'est

icon Tags de l'article : , ,

Novembre 27, 2018
Hello tout le monde,

Comme j'ai repris les cours il y a peu et que je me suis mis à fond dans le .Net Core (principalement ASP.Net Core et Entity Framework Core), je me suis dit qu'il serait intéressant de commencer une petite série d'articles pour en parler. Expliquer les différences, ce que ça apporte, tout ça tout ça.

Déjà, en résumé, .Net Core, c'est le nouveau runtime/framework opensource de Microsoft.
Il s'agit d'une refonte from scratch du Framework .Net, destinée à être plus ouverte et plus performante.
Le code source est disponible sur github et ce bébé framework a déjà fêté ses 59 releases grâce à ses 149 contributeur(ices)s!

Du coup, grâce à ce nouveau framework, il est possible de créer des applications .Net pour tous supports : Linux, Mac, Android, etc...

Et si vous êtes un dinosaure du .Net comme moi, je sais que vous allez vous poser quelques questions sur .Net Core. Je vais essayer d'y répondre de mon mieux.

1) Est-ce que c'est vraiment plus performant ?

Oui. En tout cas pour la partie ASP.Net Core. Pour la partie Entity Framework, je ne pourrais dire avec précision, mais je pense que comme avec Entity Framework classique, ça dépend plus de l'utilisation qu'on en fait que du framework lui-même.

2) Est-ce que c'est mieux et/ou plus complet ?

Alors mieux... tout dépend de ce que vous cherchez. C'est plus léger, plus performant, et plus compatible/ouvert.
En revanche, vu qu'il s'agit d'un nouveau framework, c'est clairement moins complet que le .Net Framework. Ca évolue vite, mais au jour le jour.
Du coup, si vous utilisez des fonctionnalités très poussées de .Net, il se peut que vous vous retrouviez bloqué.
Personnellement, ce n'est pas encore mon cas. J'ai réussi à faire en ASP.Net Core tout ce que je faisais en ASP.Net MVC classique !

3) Est-ce que c'est documenté ?

Ohhhh ça oui. Il semblerait que Microsoft ait fait un énorme travail de documentation sur tout ce qu'il était possible de faire avec .Net Core. J'ai même trouvé des ressources pour Entity Framework Core sans équivalent Entity Framework classique !

4) Est-ce qu'il y a des packages NuGet ?

Plein ! Evidemment il y en a moins que de packages pour .Net Framework classic, mais bien assez pour tout ce qu'on veut faire. La majorité des packages Nuget maintenus et/ou critiques ont une version Core aujourd'hui.

5) Est-ce qu'il y a des fonctionnalités en plus ?

Yep. Comme je disais, il s'agit d'un framework codé from scratch. Sans s'embarrasser de l'existant. Du coup les devs ont ajouté plein de choses qui manquaient.
Le premier exemple qui me vient en tête : l'injection de dépendances est gérée nativement !

6) Est-ce que tu es payé pour la rédaction de cet article ?

J'aimerais bien ! Mais ce n'est pas le cas. J'avais juste envie de vous présenter rapidement le framework, avant de commencer à publier du code sur ce sujet.

Voilà, j'espère que cet article vous a plus, et je vous dit à bientôt pour la suite !

Bookmarks dans Visual Studio

icon Tags de l'article : , ,

Aout 17, 2018
Woaw, je viens de découvrir l'existence des bookmarks dans Visual Studio...

Comme vous le savez peut-être, je suis un adepte des raccourcis clavier et de l'efficacité... mais je ne m'étais jamais posé la question de l'existence de bookmarks dans Visual Studio.

Pourtant c'est un besoin fréquent, noter un emplacement pour y revenir... A l'origine je me débrouillais en pinnant un fichier ou avec des breakpoints désactivés... Sauf qu'il y a une feature pensée pour !

Son fonctionnement est méga simple :

CTRL + K + K : Mettre/enlever un bookmark
CTRL + K + N : Aller au bookmark suivant
CTRL + K + N : Aller au bookmark précédent
CTRL + K + W : Afficher la liste des bookmarks

La liste ressemble à ça, et elle permet même de nommer ses bookmarks pour s'y retrouver plus facilement !.



En bref, une feature incontournable !

Architecture d'application web : SPA ou classique (en pages)

icon Tags de l'article : , , ,

Aout 16, 2018
Hello les gens,

Aujourd'hui je vais répondre à une question que beaucoup de développeurs se posent quand ils vont commencer un nouveau projet web : dois-je tenter l'aventure SPA (Single Page Application) ou dois-je rester sur une architecture classique (en pages web).

Pour répondre à ça, j'ai décidé de faire un petit listing des points positifs et négatifs d'une application SPA par rapport à une application classique.
A noter que c'est ma vision perso, après 6 ans à travailler sur des sites en pages web et 4 ans sur une application SPA.

Positif
  • Un code plus propre, mieux organisé, qui force à la rigueur
  • Un code JS beaucoup plus clair et parfaitement intégré aux vues
  • Moins de requêtes (stockage JS, moins de données à envoyer en permanence au serveur)
  • Possibilité d'avoir énormément de choses côté client (gros traitements, calculs, process en X étapes, etc.)
  • Maintenance et évolution plus facile
  • Pour l'utilisateur : une navigation plus agréable dans l'application

Négatif
  • Quel framework choisir ? Angular ? Vue.js ? React ? Un autre ?
  • Demande plus de rigueur en permanence (découpage en fichiers, architecture, dépendances JS, etc.)
  • Un temps de dev plus long (temps d'adaptation au langage, plus de javascript, temps pour trouver quel morceau de code correspond à quoi, plus de classes à écrire, ...)
  • Un debug plus compliqué au quotidien
  • Référencement et Routing
  • Monitoring des performances et tests d'intrusion moins évidents
  • Problématiques de mise à jour (si la personne ne refresh jamais, comment forcer la mise à jour sans lui faire perdre des données en cours de saisie ?)
  • Pour l'utilisateur : un premier chargement plus long

Pour moi, si j'avais à choisir entre une architecture SPA vs design classique, je poserais deux questions :
  • L'utilisateur de l'application fera-t-il de grosses sessions dessus ? (20 minutes ou plus)
  • Ais-je le temps et les moyens d'investir dans une SPA ? (car rien que la structure SPA rajoutera des problématiques à gérer au jour le jour)

Si vous avez un OUI aux deux questions, vous pouvez partir sur une architecture de type SPA sans hésiter. Le confort d'utilisation apporté à l'utilisateur par une SPA vaut vraiment le détour pour une application sur laquelle l'utilisateur passera des heures.

Après, personnellement, j'ai tendance à préférer une approche entre les deux :
  • Un site classique, découpé en pages
  • Chaque page utilisant un modèle de vue JS et un moteur de binding (dans mon cas Knockout)

Pourquoi ?
Après 4 ans passés à travailler sur une SPA, je me suis rendu compte que revenir à un design en pages web ne me posait pas de soucis. Au contraire, le développement devient plus simple.
La vraie problématique que je retrouve... c'est le JavaScript "à l'ancienne". Avoir un peu partout des méthodes qui font des modifications dans l'interface rend le tout extrêmement compliqué à relire et à améliorer.
Garder un design en pages web auquel on ajouterait un moteur de binding JS me parait être le meilleur compromis d'entre les deux mondes.

Après, évidemment... tout dépend de la taille de votre projet, de vos moyens... et de vos goûts ;)

Bonne journée et bon dev à tous/toutes !

Créer un template Razor utilisable en JavaScript

icon Tags de l'article : , , ,

Aout 15, 2018
Hello tout le monde,

Aujourd'hui on va voir comment créer un template Razor utilisable en JavaScript.
C'est une problématique assez courante, et la solution que je propose vient d'une implémentation sur un projet pro... et le résultat est pas si mal.

A noter qu'on est à des années lumières d'un vrai moteur de template, et je ne peux que vous recommander d'en implémenter un si c'est un vrai besoin de votre projet.
Cette solution n'est proposée que pour dépanner, ou à utiliser sur un projet où on ne peut/veut implémenter de moteur de template.


Du coup, allons-y !

Tout d'abord, on va créer une vue partielle pour notre template :

@model Projet.ObjetHtmlTemplate

<div class="objet">
    <h1>@Model.Name</h1>
    <p>@Model.Description</p>
    <span class="price">@Model.Price</span>
</div>

Nous allons maintenant créer notre ViewModel, ici ObjetHtmlTemplate.

Deux choses sont cependant à noter sur ce template :
  • Tous ces champs sont des strings
  • Le constructeur va permettre une initialisation des champs avec une chaine en dur contenant des antiquotes et le nom de la variable.

Voyons la classe avant que j'explique le "pourquoi" :

public class ObjetHtmlTemplate
{
    public string Name { get; set; }
	
	public string Description { get; set; }
	
	public string Price { get; set; }
	
	public ObjetHtmlTemplate(bool initForJs = false)
	{
		if(initForJs) 
		{
			Name = "` + Name + `";
			Description = "` + Description + `";
			Price = "` + Price + `";
		}
	}
}

Voilà. Je pense que vous commencez à voir où je veux en venir : nous allons juste créer une méthode en JS qui prendra en paramètres Name, Description et Price, et cela renverra... le HTML généré préalablement par Razor... mais avec nos valeurs JS !

La prochaine étape est donc ce petit morceau de JavaScript dans notre vue :

<script>
	var getObjetHtml = function(Name, Description, Price) {
		return `@Model.Partial("_ObjetHtmlTemplate", new ObjetHtmlTemplate(true))`;
	};
</script>

Magique non ? Maintenant on a une jolie méthode JavaScript "getObjetHtml" qui prend 3 paramètres, et qui va renvoyer le HTML généré à partir de ces 3 paramètres. <3

Et côté ASP.Net, on peut utiliser notre vue partielle comme à notre habitude :

<ul>
@foreach(var item in Model.Items)
{
	<li>@Html.RenderPartial("_ObjetHtmlTemplate", new ObjetHtmlTemplate() { Name = item.Name, Description = item.Description, Price = Math.Round(item.Price, 2) })<li>
}
</ul>

Et... c'est tout ! Simple et efficace, que demander de plus ?!

Allez, bonne journée et bon dev à tous/toutes !

Manipuler une collection de sous-objets dans une vue ASP.Net MVC

icon Tags de l'article : , , ,

Aout 13, 2018
Hello les devs !

Aujourd'hui on va parler d'une problématique assez commune : envoyer des sous-objets au serveur depuis une vue CSHTML.

En effet, quand on développe un site avec ASP.Net MVC, il peut arriver qu'on ait besoin, dans notre vue, de manipuler des objets qui sont en fait... une collection d'objets liés à notre objet principal.

Exemple : ma classe Guerrier qui a une propriété "Inventaire" contenant une collection d'"Objet".

Dans ma vue, j'aimerais donc pouvoir voir les objets dans l'inventaire, et si besoin... en ajouter/supprimer.

Comment faire ça ?

Et bien en fait c'est ultra simple : le moteur d'ASP.Net MVC sait tout à fait mapper des sous-objets à partir de nommages façon "tableau".

Exemple : <input type="text" name="Guerrier.Inventaire[0].Nom" value="Epée des mille vérités" />

Grâce à la syntaxe "Inventaire[0].Nom", le moteur d'ASP.Net va comprendre de lui-même qu'il s'agit du premier objet dans la liste "Inventaire" et va essayer de le créer et mapper ses propriétés.

Quelques choses à noter tout de même :
  • Attention à la numérotation, si vous avez juste un sous-objet numéroté [1], il ne sera pas mappé et récupéré.
  • Côté serveur vous ne récupérerez que ce qui a été envoyé par le client. S'il s'agit de données à sauvegarder, vous devrez faire une comparaison entre ce que vous avez en base et ce que l'utilisateur a envoyé.
  • Si vous n'avez pas de valeurs, vous n'aurez pas d'objets.
  • Si vous n'avez pas d'objets, vous ne récupérerez pas une collection vide, mais null.
  • Si jamais les champs sont disabled, ils ne seront ni récupérés, ni mappés (pratique pour activer/désactiver un objet en javascript).

Voilà,

Bonne semaine et bon dev à tous/toutes !

Bug avec bootstrap-datepicker dans sa version v4.17.45

icon Tags de l'article : , , ,

Aout 10, 2018
Hello,

Juste un micro article pour vous prévenir d'un bug dans la version 4.17.45 du bootstrap-datepicker...

Si vous êtes comme moi et que vous passez par des packages nugets, méfiez-vous : la dernière version en Nuget est la 4.17.45 qui contient des bugs.

(Dont un très chiant : si vous faites une sélection particulière en fonction du format, par exemple une sélection par mois uniquement, au 2eme clic le mode de sélection aura disparu).

Les bugs sont corrigés sur la dernière version en ligne : la 4.17.47.

N'hésitez pas à mettre à jour manuellement votre code, étant donné que le package Nuget est à la masse :(

Bon dev à tous/toutes.

Ecrire des tests unitaires pour une méthode de contrôleur d'API qui attend un fichier (ou comment mocker HttpContext)

icon Tags de l'article : , ,

Juillet 02, 2018
Hello tout le monde,

Aujourd'hui un cas de dev très spécifique : comment tester unitairement une méthode de contrôleur MVC / d'API qui attend un fichier ?

Exemple de méthode :

[HttpPost]
[Route("fromDocument")]
public IHttpActionResult CreateFromDocument()
{
	if (!Request.Content.IsMimeMultipartContent())
	{
		return InternalServerError(Resource.Error_RequestNotMimeMultipartContent);
	}

	if (HttpContext.Current.Request.Files.Count != 1)
	{
		return BadRequest(Resource.Error_FileContentIncorrect);
	}

	// We will only consider the first file
	HttpPostedFile uploadedFile = HttpContext.Current.Request.Files[0];

	if (uploadedFile.ContentLength > ConstantApplicationSettings.UploadFileSizeLimitInBytes)
	{
		return BadRequest();
	}

	uploadedFile.InputStream.Position = 0;

	string originFileName = uploadedFile.FileName.Replace("\"", string.Empty);
	_itemBusinessLogic.CreateDocument(item.Id, originFileName, uploadedFile.InputStream, CurrentUserId);

	return Ok();
}

Comment tester cette méthode ? En effet, de l'extérieur le HttpContext n'est pas accessible. Du coup comment faire croire au contrôleur qu'il recoit bien un fichier alors que ce n'est pas le cas ?

Pour ça, il faut passer par plusieurs étapes.

Déjà, la première chose importante à assimiler : il ne faut PAS mocker HttpContext.
La classe est vraiment énorme et embarque énormément de choses. La mocker ou la stubber c'est prendre le risque d'avoir du code qui casse en production sans qu'on s'en rende compte, malgré des tests unitaires parfaits.

Non, la vraie bonne pratique c'est d'interfacer nos besoins d'objets liés à HttpContext. Nous allons donc créer une interface IHttpContextAdapter :

public interface IHttpContextAdapter
{
	HttpFileCollectionBase GetFiles();
}

Comme vous le voyez, nous allons renvoyer un HttpFileCollectionBase. Pas un HttpFileCollection car il s'agit d'une classe sealed, et donc impossible à mock sans passer par de l'interception.

Du coup, implémentons cette interface dans la classe qui nous servira dans nos contrôleurs d'API :

public class HttpContextAdapter : IHttpContextAdapter
{
    public HttpFileCollectionBase GetFiles()
    {
        return new HttpFileCollectionWrapper(HttpContext.Current.Request.Files);
    }
}

Voilà. Comme vous l'avez compris, le HttpFileCollectionWrapper va permettre de transformer le HttpFileCollection en HttpFileCollectionBase, classe qu'on peut mocker / stubber car non sealed.

Du coup, injectons maintenant dans notre constructeur cette interface, et utilisons la :

public class ItemService : ApiControllerBase, IItemService
{
	public readonly IItemBusinessLogic _itemBusinessLogic;
	public readonly IHttpContextAdapter _httpContextAdapter;

    public ItemService(IItemBusinessLogic itemBusinessLogic, IHttpContextAdapter httpContextAdapter)
    {
		_itemBusinessLogic = itemBusinessLogic;
        _httpContextAdapter = httpContextAdapter;
    }
	
	[...]
}

Voilà. Et du coup, au lieu d'appeler HttpContext.Current, nous allons appeler notre _httpContextAdapter :

// avant modification
if (HttpContext.Current.Request.Files.Count != 1)
[...]
HttpPostedFile uploadedFile = HttpContext.Current.Request.Files[0];

// après modification
if (_httpContextAdapter.GetFiles().Count != 1)
[...]
HttpPostedFileBase uploadedFile = _httpContextAdapter.GetFiles().Get(0);

Ca avance, ça avance.

Mais on a toujours un problème : lorsque le contrôleur va vérifier le fichier censé être en entrée... il va faire un Request.Content.IsMimeMultipartContent()...
Or... le mock de notre HttpContext ne suffira pas à faire passer cette méthode.

Heureusement, j'ai trouvé une méthode d'extension qui permet de faire ça !
Elle va tout simplement réinitialiser le ControllerContext de notre ApiController en ajoutant un "faux" fichier de 100 bytes, avec les bons headers :

public static void InitializeMimeMultipartContent(this ApiController controller, string fileName = "filename.txt")
{
	var user = controller.User;
	Uri uri = controller.Request.RequestUri;

	var configuration = new HttpConfiguration();
	var request = new HttpRequestMessage(HttpMethod.Post, string.Empty);
	var content = new MultipartFormDataContent();

	var fileContent = new ByteArrayContent(new byte[100]);
	fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
	{
		FileName = fileName
	};
	content.Add(fileContent);
	request.Content = content;
	request.Properties[HttpPropertyKeys.HttpConfigurationKey] = configuration;

	controller.ControllerContext = new HttpControllerContext(new HttpConfiguration(), new HttpRouteData(new HttpRoute(string.Empty)), request);

	controller.User = user;
	controller.Request.RequestUri = uri;
}

La seule chose à noter est que, comme on va réinitialiser le ControllerContext, on perdra des données qui sont probablement actuellement utilisées (l'utilisateur courant, l'url courante, etc.), d'où le fait qu'on mette de côté l'utilisateur et l'url avant la réinitialisation, pour les remettre dans le contrôleur ensuite.

Et voilà, nous avons tous les outils pour pouvoir tester efficacement notre méthode !

Voici donc un test unitaire commenté utilisant tout ce qu'on vient de coder (avec RhinoMocks, mais vous devriez pouvoir adapter ce code à votre moteur de mocking) :

[Test]
public void ItemController_CreateFromDocument_WithValidData_Ok()
{
	string fileName = "file.png";
	
	// on va d'abord initialiser le contrôleur pour faire croire qu'il s'agit d'un MimeMultipartContent
	_itemController.InitializeMimeMultipartContent(fileName);

	// ensuite on va mocker les appels à GetFile() de IHttpContextAdapter
	HttpFileCollectionBase files = MockRepository.GenerateStub<HttpFileCollectionBase>();
    HttpPostedFileBase file = MockRepository.GenerateStub<HttpPostedFileBase>();
	_httpContextAdapter.Stub(p => p.GetFiles()).Return(files);
	files.Stub(p => p.Get(0)).Return(file);

	// ici on mocke le "nombre" de fichiers envoyés
	files.Stub(p => p.Count).Return(1);
	
	// maintenant, on mocke le fichier "envoyé", avec les propriétés checkées par notre contrôleur
	file.Stub(p => p.FileName).Return(fileName);
	file.Stub(p => p.ContentLength).Return(50000);
	
	// on pense aussi à renvoyer un stub pour le stream (pour éviter que les appels au stream ne plantent)
	file.Stub(p => p.InputStream).Return(MockRepository.GenerateStub<Stream>());

	// Une fois le test prêt, on appelle notre méthode de contrôleur
	HttpResponseMessage httpResponseMessage = _itemController.CreateFromDocument().GetResponse();

	// Et on n'a plus qu'à vérifier que la réponse est bien celle attendue, et que la couche métier a été appelé
	ApiControllerAssert.IsOk(httpResponseMessage);
	_itemBusinessLogic.AssertWasCalled(p => p.CreateDocument(
		Arg<string>.Is.Equal(fileName),
		Arg<Stream>.Is.NotNull,
		Arg<int>.Is.Equal(CurrentUserId)));
}

En espérant que ce soit utile à quelqu'un :)

Bon dev tout le monde !

Test unitaire : mocker un IQueryable manipulable de façon transparente

icon Tags de l'article :

Juin 28, 2018
Hello,

Petite problématique rencontrée aujourd'hui : comment tester unitairement un morceau de code de la couche métier qui ressemble à ceci :

public void RemovePicture(long groupId, long pictureId)
{
    var usersUsingThePicture = _groupDataAccess.Get().Include(p => p.User).Where(p => p.GroupId == groupId && p.User.PictureId == pictureId).Select(p => p.User).ToList();

    foreach (var user in usersUsingThePicture)
    {
        user.PictureId = null;
        _userDataAccess.Update(user);
    }

    base.SaveChanges();
    base.Remove(pictureId);
}

On voit qu'ici j'ai du code métier qui va supprimer une image utilisée par plusieurs membres, en supprimant d'abord la référence à cette image pour mes utilisateurs, avant de la supprimer de la base.

Pour faire ça, j'appelle ma couche d'accès aux données avec ma méthode Get.
Celle-ci va me renvoyer un IQueryable, IQueryable que je vais ensuite exploiter (via l'Include, le Where, le Select et le ToList) pour récupérer uniquement les utilisateurs qui ont cette image.

Sauf que voilà, dans mon test unitaire, il faut que ma couche d'accès aux données, qui est mockée, me renvoie un IQueryable...

Comment faire ça ?

En réalité, c'est ultra simple (ici avec RhinoMocks, mais applicable aux autres packages de Mocking) :

[Test]
public void GroupLogic_RemovePicture_WithUserUsingThePicture_Success()
{
    // Arrange
    long pictureId = 54689;
    long groupId = 941333;

    var userWithPicture = Builder<User>.With(p => p.PictureId = pictureId).Build();
    var groupUserWithPicture = Builder<GroupUser>.With(p => p.GroupId = groupId).With(p => p.User = userWithPicture).Build();

    var userWithoutPicture = Builder<User>.Build();
    var groupUserWithoutPicture = Builder<GroupUser>.With(p => p.GroupId = groupId).With(p => p.User = userWithoutPicture).Build();

    _groupDataAccess.Stub(p => p.Get(Arg<Expression<Func<GroupUser, object>>[]>.Is.Anything))
        .Return(new List<GroupUser>() { groupUserWithPicture, groupUserWithoutPicture }.AsQueryable());

    // Act
    _groupLogic.RemovePicture(groupId, pictureId);

    // Assert
    _userDataAccess.AssertWasCalled(p => p.Update(Arg<User>.Matches(u => u.Id == userWithPicture.Id));
    _userDataAccess.AssertWasNotCalled(p => p.Update(Arg<User>.Matches(u => u.Id == userWithoutPicture.Id));
}

Il suffit de générer ses objets avec les données nécessaires, d'en faire une collection, et de renvoyer, dans le Stub/Setup notre collection à l'aide d'un .AsQueryable().
On n'a plus derrière qu'à exploiter nos mocks en vérifiant que les appels attendus ont bien été faits.

Et voilà !

Et si besoin de mocker un dbset