blazor-on-cloud-hero-banner

Cet article est la suite de la Découverte de Blazor et nous allons, cette fois, voir la mise en place de tests d’intégrations, la création de pipeline de CI/CD et le déploiement des ressources dans le cloud d’une application web Blazor.

1. Tests

Les tests sont une étape importante dans le développement d’application. Pour réaliser des tests sur notre application Blazor, nous allons utiliser 2 outils complémentaires  : SpecFlow et Playwright.

  • SpecFlow est un framework de test qui utilise Xunit et qui permet d’écrire des scénarios de test, appelés features, qui sont ensuite traduit en fonctions C#, dans lesquelles on y place les assertions et contrôles pour valider les tests.

  • Playwright est une bibliothèque de tests automatisées end-to-end développée par Microsoft et qui permet de simuler les interactions et les actions des utilisateurs dans un navigateur web. Des outils sont fournis pour tester l’affichage des éléments sur la page, exécuter des actions, click sur un bouton etc.

Les présentations sont faites, maintenant, nous allons voir quelques notions utiles pour l’écriture des tests.

1.a Hook

Les hooks sont des méthodes qui s’exécute avant ou après des actions. Il existe plusieurs hooks disponible :

  • BeforeTestRun : S’exécute en amont de lancer les tests. Parfait pour préparer l’environnement de test (lancer un docker compose, créer une base de données, etc.). 
  • AfterTestRun : S’exécute après la fin des tests, pour supprimer l’environnement de test par exemple. 
  • BeforeFeature : Avant chaque feature. 
  • AfterFeature : Après chaque feature, idéal pour lancer une instance de Playwright par exemple
  • BeforeScenario : Avant chaque scénario. 
  • AfterScenario : Après chaque scénario. 
  • BeforeStep : Avant chaque étape. 
  • AfterStep : Après chaque étape.

Note : Il est possible d’écrire plusieurs hooks du même type et d’ordonner ou de spécialiser leurs exécutions à l’aide de l’attribut Order

Voici un exemple d’un hook BeforeTestRun qui déploie l’environnement de test Docker à l’aide d’un fichier docker compose :

[BeforeTestRun(Order=1)]
public static void DockerComposeUp()
{
	ICompositeService _compositeService = new Builder()
		.UseContainer()
		.UseCompose()
		.FromFile('./docker-compose.yml')
	...
}

1.b Feature et scénario

Une feature est un ensemble de scénario servant à tester une fonctionnalité de l’application. Un scénario permet de décrire un test en donnant la situation initial (le Given), ce qui doit se passer (le When) et le résultat attendu (le Then) et ce, écrit dans un langage naturel que même les personnes non-dev peuvent comprendre.

Exemple avec une feature “Login” qui est un scénario pour tester que la page /login a bien un bouton “Login” :

Feature: Login 

Scenario: Un utilisateur non identifié, veut se connecter 
	Given: Un utilisateur lambda accède à l’application 
	When: L’utilisateur arrive sur la page ‘/login’ 
	Then: La page de login doit avoir un bouton ‘Login’  

1.c Step

Les classes CSharp Steps, sont reliées aux features. C’est la partie technique du test, c’est là où se trouve le code qui permet aux tests d’être exécutés et vérifiés.
Ainsi chaque étape du scénario est bind à une méthode.

[Binding]
public class LoginSteps
{
	[Given("Un utilisateur lambda accède à l’application")]
	public async Task AccessToTheApplication()
	{
		await _loginPage.NavigateAsync();
	}

	[When("L’utilisateur arrive sur la page ‘(.*)’ ")]
	public async Task CheckArrivateLoginPage(string pageUrl)
	{
		await _loginPage.Url.Should().Be(pageUrl);
	}

	[Then("La page de login doit avoir un bouton ‘(.*)’")]
	public async Task ButtonLoginIsHere(string buttonName)
	{
		await _loginPage.AssertHasButton(buttonName);
	}
}

Note : Les scénarios ont une exécution parallèle par défaut, ce qui peut provoquer des incohérences dans le résultat des tests. Vous pouvez préciser d’exécuter un scénario en synchrone avec le décorateur DisableParallelization à mettre sur la classe Step en question.

1.d Locator

Les Locators de Playwright permettent de récupérer un objet (ou plusieurs objets) du DOM (Document Object Model) à l’instar de JQuery.
On peut donc récupérer la valeur d’un input, remplir un formulaire, cliquer sur un item du menu ou encore obtenir la couleur d’un élément sur page. 
Ils peuvent être couplés aux assertions Fluent par exemple pour contrôler le bon affichage d’une fonctionnalité par exemple.

// Sélectionne tous les items d'un accordéon Havit
private ILocator AccordionItems => Page.Locator("main article .hx-accordion .hx-accordion-item");
// Rempli un champ qui a pour label "Password" avec la valeur "password_123"
await Page.GetByLabel("Password").FIllAsync("password_123");
// Sélectionne un buton nommé "Login" puis clic dessus
private ILocator LoginButton => Page.Locator("button [name=Login]");
await LoginButton.ClickAsync();

On peut faire plein d’autres choses avec les Locator. Je vous laisse aller voir la doc.

2. CI/CD

La CI et CD pour Continuous Integration – Continuous Delivery, consiste en l’automatisation de la livraison et de l’intégration, de manière continue.

Une pipeline est une suite d’actions à exécuter pour effectuer une ou plusieurs tâches. Tel que, le build d’un package, exécuter des tests ou bien, déployer une infrastructure au moyen de scripts

Couplé au gestionnaire de code source, ces pipelines peuvent être déclenchées avant ou après des actions particulières sur le repo (Pull request sur la main, après chaque commit, nouvelle release …). 

Il existe de nombreux outils et plateformes de CI/CD. Les plus connus : Azure Pipelines, GitHub Actions, GitLab, Jenkins ou CircleCi ou encore Travis CI.

Un exemple d’une pipeline que j’ai créé pour build et déployer mon application Blazor :

trigger:
- master
pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'
  artifactName: 'blazingMonitor'
...
# Build l'application
- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    msBuildArgs: '/p:DeployOnBuild=true'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

# Publication de l'application
- task: DotNetCoreCli@2
  inputs:
    command: 'publish'
    projects: '$(solution)'
    arguments: '--configuration $(buildConfiguration)'
    publishWebProject: true

3. Infrastructure as Code

La dernière étape, la création de l’infrastructure réseau pour notre solution Blazor.
Et pour faire ça, on va utiliser un outil qui s’appelle Terraform. Cet outil, permet de décrire et déployer des infrastructures en IaC (Infrastructure as Code). C’est à dire qu’on va écrire les spécifications de notre infra dans un ensemble de fichiers (le plus souvent en .yaml), comme le font Kubernetes ou Docker pour les containers. En l’occurrence Terraform utilise le langage déclarative HCL.

Ces fichiers peuvent donc être édités et stocker sur un gestionnaire de code sources et exécuté par des pipelines de CI/CD comme vu précédemment.
De plus, Terraform est compatible avec tous les principaux cloud providers (AWS, Azure, GCP, Oracle, OVH…) et met à disposition une très bonne doc pour utiliser ses outils !

Maintenant, commençons à créer notre infrastructure sur Azure.
Pour cela, notre application à besoin d’une base de données PostgreSQL et d’un app service de type web .Net.

Notes : On suppose que Terraform CLI et azurerm sont déjà installés et prêts à être utilisés.

  1. Installation des package et configuration du cloud provider
terraform {
  required_version = ">=0.12"
  required_providers {
    azurerm = {
	    source = "hashicorp/azurerm"
	    version = "~>3.0"
    }
  }
}
provider "azurzem" {
  features {}
  
  subscription_id = "your-subscription-id"
  tenant_id = "your-tenant-id"
  client_id = "your-client-id"
  client_secret = "your-client-secret"
  
  skip_provider_registration = true
}

Configuration du cloud provider Azurerm avec credentials pour se connecter à Azure

  1. Déclaration des variables
variable "resource_location" {
  default = "francecentral"
}
variable "resource_group_name" {
  default = "blazor-resource-group"
}
  1. Déclaration des ressources
# Web App
resource "azurerm_windows_web_app" "BlazorAppService" {
  name = "BlazorAppService"
  location = var.resource_location
  resource_group_name = var.resource
  service_plan_id = "your-plan-id"
  site_config {
    websockets_enabled = true
  }
  depends_on = [azurerm_postgresql_flexible_server.BlazorDB]
}

# Database
resource "azurerm_postgresql_flexible_server" "BlazorDB" {
  name = "blazordb"
  resource_group_name = var.resource_group_name
  location = var.resource_location
  version = "14"
  sku_name = "Sku_plan_name"
  storage_mb = "32768"
}
  1. Création du plan et déploiement

Exécuter ces commandes dans une console à la racine du répertoire contenant vos fichiers Terraform

# Création du plan
terraform plan
# Lancer la création de l'infra
terraform apply
# Supprimer l'infra
terraform apply -destroy

4. Conclusion

Maintenant vous savez comment faire des tests end-to-end et déployer une application Blazor sur le cloud ☁🦖.
Je recommande vivement d’utiliser SpecFlow et Playwright pour les tests. Ce sont deux technos complémentaires qui fonctionnent très bien ensemble et qui sont très simple d’utilisation et assez cool.

La CI/CD et le IaC sont des domaines qui concernent plus un métier de DevOps ou d’architecte, mais il est toujours intéressant de voir jusqu’où on peut aller avec un projet.

liens très utiles :