💻 Projet de Développement
BTS SIO SLAM - Bloc 1 : Support et mise à disposition de services informatiques
🎯 Qu'est-ce qu'un Projet de Développement ?
🚀 Définition du Projet de Développement
Un projet de développement est une démarche structurée visant à créer une solution logicielle répondant à un besoin spécifique. Il implique la planification, la conception, le développement, les tests et le déploiement d'une application ou d'un système informatique.
Planification
Définition des objectifs, des ressources nécessaires, du planning et des livrables attendus.
Conception
Élaboration de l'architecture, des maquettes et des spécifications techniques détaillées.
Développement
Implémentation du code source selon les spécifications et les bonnes pratiques de développement.
Tests
Validation du fonctionnement, détection des bugs et vérification de la conformité aux exigences.
💡 Bon à savoir : Un projet de développement réussi combine compétences techniques, gestion de projet et communication efficace avec les parties prenantes.
📊 Méthodologies de Gestion de Projet
🔄 Approches Méthodologiques
Les méthodologies de gestion de projet fournissent un cadre structuré pour organiser, planifier et contrôler le développement d'un projet informatique. Chaque approche a ses avantages selon le contexte et les contraintes.
Méthode en Cascade
Approche séquentielle où chaque phase doit être terminée avant de passer à la suivante. Idéale pour les projets aux exigences stables.
Méthode Agile
Approche itérative et collaborative privilégiant l'adaptation au changement et la livraison continue de valeur.
Scrum
Framework agile organisant le travail en sprints courts avec des rôles définis (Product Owner, Scrum Master, équipe).
Kanban
Méthode visuelle de gestion des flux de travail basée sur des tableaux et la limitation du travail en cours.
💻 Exemple : Gestion de Projet Agile en C#
// Modèle pour la gestion d'un projet Agile
public class AgileProject
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public ProjectStatus Status { get; set; }
public List Sprints { get; set; } = new List();
public List Backlog { get; set; } = new List();
public List Team { get; set; } = new List();
public Sprint CreateNewSprint(string name, DateTime startDate, DateTime endDate)
{
var sprint = new Sprint
{
Id = Sprints.Count + 1,
Name = name,
StartDate = startDate,
EndDate = endDate,
Status = SprintStatus.Planning,
ProjectId = this.Id
};
Sprints.Add(sprint);
return sprint;
}
public void AddUserStory(string title, string description, int storyPoints, Priority priority)
{
var userStory = new UserStory
{
Id = Backlog.Count + 1,
Title = title,
Description = description,
StoryPoints = storyPoints,
Priority = priority,
Status = UserStoryStatus.New,
CreatedDate = DateTime.UtcNow
};
Backlog.Add(userStory);
}
public List GetPrioritizedBacklog()
{
return Backlog
.Where(us => us.Status != UserStoryStatus.Done)
.OrderBy(us => us.Priority)
.ThenByDescending(us => us.StoryPoints)
.ToList();
}
public ProjectMetrics CalculateMetrics()
{
var completedStories = Backlog.Count(us => us.Status == UserStoryStatus.Done);
var totalStories = Backlog.Count;
var completedSprints = Sprints.Count(s => s.Status == SprintStatus.Completed);
return new ProjectMetrics
{
CompletionPercentage = totalStories > 0 ? (double)completedStories / totalStories * 100 : 0,
Velocity = CalculateAverageVelocity(),
BurndownRate = CalculateBurndownRate(),
TeamProductivity = CalculateTeamProductivity()
};
}
private double CalculateAverageVelocity()
{
var completedSprints = Sprints.Where(s => s.Status == SprintStatus.Completed).ToList();
if (!completedSprints.Any()) return 0;
var totalStoryPoints = completedSprints.Sum(s => s.CompletedStoryPoints);
return (double)totalStoryPoints / completedSprints.Count;
}
}
public class Sprint
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public SprintStatus Status { get; set; }
public int ProjectId { get; set; }
public List UserStories { get; set; } = new List();
public int CompletedStoryPoints => UserStories.Where(us => us.Status == UserStoryStatus.Done).Sum(us => us.StoryPoints);
public void StartSprint()
{
Status = SprintStatus.Active;
foreach (var story in UserStories)
{
if (story.Status == UserStoryStatus.New)
{
story.Status = UserStoryStatus.InProgress;
}
}
}
public void CompleteSprint()
{
Status = SprintStatus.Completed;
// Déplacer les stories non terminées vers le backlog
var incompleteStories = UserStories.Where(us => us.Status != UserStoryStatus.Done).ToList();
// Logique pour remettre dans le backlog
}
}
public enum ProjectStatus { Planning, Active, OnHold, Completed, Cancelled }
public enum SprintStatus { Planning, Active, Review, Retrospective, Completed }
public enum UserStoryStatus { New, InProgress, Testing, Done }
public enum Priority { Low = 1, Medium = 2, High = 3, Critical = 4 }
🔄 Phases du Cycle de Développement
📈 Cycle de Vie du Développement Logiciel (SDLC)
Le cycle de vie du développement logiciel décrit les étapes successives nécessaires pour créer un logiciel de qualité, depuis l'analyse des besoins jusqu'à la maintenance en production.
1
📋 Analyse des Besoins
Collecte et analyse des exigences fonctionnelles et non-fonctionnelles. Définition du périmètre du projet et des critères d'acceptation.
2
🎨 Conception
Élaboration de l'architecture technique, des maquettes UI/UX, du modèle de données et des spécifications détaillées.
3
⚙️ Développement
Implémentation du code source, intégration des composants et développement des fonctionnalités selon les spécifications.
4
🧪 Tests
Tests unitaires, d'intégration, fonctionnels et de performance. Validation de la conformité aux exigences.
5
🚀 Déploiement
Mise en production, configuration des environnements, migration des données et formation des utilisateurs.
6
🔧 Maintenance
Support technique, corrections de bugs, évolutions fonctionnelles et optimisations de performance.
⚠️ Important : Ces phases peuvent être itératives dans les méthodologies agiles, permettant des ajustements continus selon les retours utilisateurs.
🛠️ Outils et Technologies
⚡ Écosystème de Développement
Un projet de développement moderne s'appuie sur un écosystème d'outils couvrant l'ensemble du cycle de vie : IDE, contrôle de version, bases de données, tests, déploiement et monitoring.
💻
IDE & Éditeurs
Visual Studio, VS Code, JetBrains
🔄
Contrôle de Version
Git, GitHub, GitLab, Azure DevOps
🗄️
Bases de Données
SQL Server, PostgreSQL, MongoDB
🧪
Tests
xUnit, NUnit, Selenium, Postman
🚀
Déploiement
Docker, Kubernetes, Azure, AWS
📊
Monitoring
Application Insights, Grafana, ELK
💻 Exemple : Configuration DevOps avec C# et Docker
# Dockerfile pour une application ASP.NET Core
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyProject.Api/MyProject.Api.csproj", "MyProject.Api/"]
COPY ["MyProject.Core/MyProject.Core.csproj", "MyProject.Core/"]
COPY ["MyProject.Infrastructure/MyProject.Infrastructure.csproj", "MyProject.Infrastructure/"]
RUN dotnet restore "MyProject.Api/MyProject.Api.csproj"
COPY . .
WORKDIR "/src/MyProject.Api"
RUN dotnet build "MyProject.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MyProject.Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyProject.Api.dll"]
# docker-compose.yml pour l'environnement de développement
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:80"
- "5001:443"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Server=db;Database=MyProjectDb;User=sa;Password=YourPassword123;
depends_on:
- db
volumes:
- ./logs:/app/logs
db:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=YourPassword123
- MSSQL_PID=Express
ports:
- "1433:1433"
volumes:
- sqlserver_data:/var/opt/mssql
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
sqlserver_data:
redis_data:
💻 Exemple : Pipeline CI/CD avec GitHub Actions
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Run unit tests
run: dotnet test --no-build --configuration Release --logger trx --collect:"XPlat Code Coverage"
- name: Generate test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: .NET Tests
path: '**/*.trx'
reporter: dotnet-trx
- name: Code coverage
uses: codecov/codecov-action@v3
with:
files: '**/coverage.cobertura.xml'
security-scan:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v3
- name: Run security scan
uses: securecodewarrior/github-action-add-sarif@v1
with:
sarif-file: 'security-scan-results.sarif'
deploy:
runs-on: ubuntu-latest
needs: [test, security-scan]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Login to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build and push Docker image
run: |
docker build -t myproject:${{ github.sha }} .
docker tag myproject:${{ github.sha }} myregistry.azurecr.io/myproject:latest
docker push myregistry.azurecr.io/myproject:latest
- name: Deploy to Azure Container Instances
uses: azure/aci-deploy@v1
with:
resource-group: 'my-resource-group'
dns-name-label: 'myproject-${{ github.sha }}'
image: 'myregistry.azurecr.io/myproject:latest'
registry-login-server: 'myregistry.azurecr.io'
registry-username: ${{ secrets.REGISTRY_USERNAME }}
registry-password: ${{ secrets.REGISTRY_PASSWORD }}
name: 'myproject-container'
location: 'West Europe'
✅ Bonnes Pratiques de Développement
🏆 Standards de Qualité
Les bonnes pratiques de développement garantissent la qualité, la maintenabilité et la sécurité du code. Elles couvrent l'architecture, le codage, les tests, la documentation et la collaboration en équipe.
Architecture Clean
Séparer les responsabilités en couches distinctes : présentation, logique métier, accès aux données. Respecter les principes SOLID.
Code Lisible
Utiliser des noms explicites, commenter le code complexe, respecter les conventions de nommage et maintenir une structure cohérente.
Contrôle de Version
Commits atomiques avec messages clairs, utilisation de branches pour les fonctionnalités, revues de code systématiques.
Tests Automatisés
Couverture de tests élevée, tests unitaires, d'intégration et end-to-end. TDD (Test-Driven Development) quand approprié.
Sécurité
Validation des entrées, authentification robuste, chiffrement des données sensibles, audit de sécurité régulier.
Monitoring
Logs structurés, métriques de performance, alertes automatiques, tableaux de bord pour le suivi en temps réel.
💻 Exemple : Architecture Clean avec C#
// Domain Layer - Entité métier
public class User
{
public int Id { get; private set; }
public string Email { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public DateTime CreatedAt { get; private set; }
public bool IsActive { get; private set; }
private User() { } // Pour EF Core
public User(string email, string firstName, string lastName)
{
if (string.IsNullOrWhiteSpace(email))
throw new ArgumentException("Email is required", nameof(email));
if (!IsValidEmail(email))
throw new ArgumentException("Invalid email format", nameof(email));
Email = email.ToLowerInvariant();
FirstName = firstName?.Trim();
LastName = lastName?.Trim();
CreatedAt = DateTime.UtcNow;
IsActive = true;
}
public void Deactivate()
{
IsActive = false;
}
public void UpdateProfile(string firstName, string lastName)
{
FirstName = firstName?.Trim();
LastName = lastName?.Trim();
}
private static bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
}
// Application Layer - Service métier
public interface IUserService
{
Task CreateUserAsync(CreateUserCommand command);
Task GetUserByIdAsync(int id);
Task> GetActiveUsersAsync();
Task DeactivateUserAsync(int id);
}
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IEmailService _emailService;
private readonly ILogger _logger;
public UserService(IUserRepository userRepository, IEmailService emailService, ILogger logger)
{
_userRepository = userRepository;
_emailService = emailService;
_logger = logger;
}
public async Task CreateUserAsync(CreateUserCommand command)
{
_logger.LogInformation("Creating user with email: {Email}", command.Email);
// Vérifier si l'utilisateur existe déjà
var existingUser = await _userRepository.GetByEmailAsync(command.Email);
if (existingUser != null)
{
throw new BusinessException("User with this email already exists");
}
// Créer le nouvel utilisateur
var user = new User(command.Email, command.FirstName, command.LastName);
// Sauvegarder
await _userRepository.AddAsync(user);
await _userRepository.SaveChangesAsync();
// Envoyer email de bienvenue
await _emailService.SendWelcomeEmailAsync(user.Email, user.FirstName);
_logger.LogInformation("User created successfully with ID: {UserId}", user.Id);
return user;
}
public async Task GetUserByIdAsync(int id)
{
var user = await _userRepository.GetByIdAsync(id);
if (user == null)
{
throw new NotFoundException($"User with ID {id} not found");
}
return user;
}
public async Task> GetActiveUsersAsync()
{
return await _userRepository.GetActiveUsersAsync();
}
public async Task DeactivateUserAsync(int id)
{
var user = await GetUserByIdAsync(id);
user.Deactivate();
await _userRepository.SaveChangesAsync();
_logger.LogInformation("User {UserId} deactivated", id);
}
}
// Infrastructure Layer - Repository
public interface IUserRepository
{
Task GetByIdAsync(int id);
Task GetByEmailAsync(string email);
Task> GetActiveUsersAsync();
Task AddAsync(User user);
Task SaveChangesAsync();
}
public class UserRepository : IUserRepository
{
private readonly ApplicationDbContext _context;
public UserRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task GetByIdAsync(int id)
{
return await _context.Users.FindAsync(id);
}
public async Task GetByEmailAsync(string email)
{
return await _context.Users
.FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant());
}
public async Task> GetActiveUsersAsync()
{
return await _context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.LastName)
.ToListAsync();
}
public async Task AddAsync(User user)
{
await _context.Users.AddAsync(user);
}
public async Task SaveChangesAsync()
{
await _context.SaveChangesAsync();
}
}
🚨 Attention : Ne jamais committer de secrets (mots de passe, clés API) dans le code source. Utilisez des variables d'environnement ou des services de gestion de secrets.
📈 Gestion de la Qualité et des Risques
🎯 Assurance Qualité
La gestion de la qualité dans un projet de développement implique la mise en place de processus, d'outils et de métriques pour garantir que le produit final répond aux exigences et aux standards de qualité définis.
Métriques de Qualité
Couverture de tests : > 80%
Complexité cyclomatique : < 10
Duplication de code : < 3%
Bugs critiques : 0
Complexité cyclomatique : < 10
Duplication de code : < 3%
Bugs critiques : 0
Gestion des Risques
Identification proactive des risques techniques, fonctionnels et organisationnels avec plans de mitigation appropriés.
Revues de Code
Processus systématique de relecture du code par les pairs pour détecter les bugs et améliorer la qualité.
Standards de Codage
Conventions de nommage, formatage, documentation et architecture respectées par toute l'équipe.
💻 Exemple : Tests Unitaires avec xUnit
// Tests unitaires pour le UserService
public class UserServiceTests
{
private readonly Mock _userRepositoryMock;
private readonly Mock _emailServiceMock;
private readonly Mock> _loggerMock;
private readonly UserService _userService;
public UserServiceTests()
{
_userRepositoryMock = new Mock();
_emailServiceMock = new Mock();
_loggerMock = new Mock>();
_userService = new UserService(_userRepositoryMock.Object, _emailServiceMock.Object, _loggerMock.Object);
}
[Fact]
public async Task CreateUserAsync_WithValidData_ShouldCreateUser()
{
// Arrange
var command = new CreateUserCommand
{
Email = "test@example.com",
FirstName = "John",
LastName = "Doe"
};
_userRepositoryMock.Setup(x => x.GetByEmailAsync(command.Email))
.ReturnsAsync((User)null);
_userRepositoryMock.Setup(x => x.AddAsync(It.IsAny()))
.Returns(Task.CompletedTask);
_userRepositoryMock.Setup(x => x.SaveChangesAsync())
.Returns(Task.CompletedTask);
_emailServiceMock.Setup(x => x.SendWelcomeEmailAsync(It.IsAny(), It.IsAny()))
.Returns(Task.CompletedTask);
// Act
var result = await _userService.CreateUserAsync(command);
// Assert
Assert.NotNull(result);
Assert.Equal(command.Email.ToLowerInvariant(), result.Email);
Assert.Equal(command.FirstName, result.FirstName);
Assert.Equal(command.LastName, result.LastName);
Assert.True(result.IsActive);
_userRepositoryMock.Verify(x => x.AddAsync(It.IsAny()), Times.Once);
_userRepositoryMock.Verify(x => x.SaveChangesAsync(), Times.Once);
_emailServiceMock.Verify(x => x.SendWelcomeEmailAsync(command.Email, command.FirstName), Times.Once);
}
[Fact]
public async Task CreateUserAsync_WithExistingEmail_ShouldThrowBusinessException()
{
// Arrange
var command = new CreateUserCommand
{
Email = "existing@example.com",
FirstName = "Jane",
LastName = "Smith"
};
var existingUser = new User(command.Email, "Existing", "User");
_userRepositoryMock.Setup(x => x.GetByEmailAsync(command.Email))
.ReturnsAsync(existingUser);
// Act & Assert
var exception = await Assert.ThrowsAsync(
() => _userService.CreateUserAsync(command));
Assert.Equal("User with this email already exists", exception.Message);
_userRepositoryMock.Verify(x => x.AddAsync(It.IsAny()), Times.Never);
_emailServiceMock.Verify(x => x.SendWelcomeEmailAsync(It.IsAny(), It.IsAny()), Times.Never);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("invalid-email")]
[InlineData("@example.com")]
[InlineData("test@")]
public async Task CreateUserAsync_WithInvalidEmail_ShouldThrowArgumentException(string invalidEmail)
{
// Arrange
var command = new CreateUserCommand
{
Email = invalidEmail,
FirstName = "Test",
LastName = "User"
};
// Act & Assert
await Assert.ThrowsAsync(
() => _userService.CreateUserAsync(command));
}
[Fact]
public async Task GetUserByIdAsync_WithExistingId_ShouldReturnUser()
{
// Arrange
var userId = 1;
var expectedUser = new User("test@example.com", "John", "Doe");
_userRepositoryMock.Setup(x => x.GetByIdAsync(userId))
.ReturnsAsync(expectedUser);
// Act
var result = await _userService.GetUserByIdAsync(userId);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedUser.Email, result.Email);
}
[Fact]
public async Task GetUserByIdAsync_WithNonExistingId_ShouldThrowNotFoundException()
{
// Arrange
var userId = 999;
_userRepositoryMock.Setup(x => x.GetByIdAsync(userId))
.ReturnsAsync((User)null);
// Act & Assert
var exception = await Assert.ThrowsAsync(
() => _userService.GetUserByIdAsync(userId));
Assert.Equal($"User with ID {userId} not found", exception.Message);
}
}
💡 Conseil : Utilisez des outils d'analyse statique comme SonarQube pour détecter automatiquement les problèmes de qualité et les vulnérabilités de sécurité.