Partage via


Cycle de vie MSTest

MSTest fournit un cycle de vie bien défini pour les classes de test et les méthodes de test, ce qui vous permet d’effectuer des opérations d’installation et de destruction à différentes étapes de l’exécution des tests. Comprendre le cycle de vie vous aide à écrire des tests efficaces et à éviter les pièges courants.

Présentation du cycle de vie

Le cycle de vie est divisé en quatre étapes, exécutées du niveau le plus élevé (assemblage) au niveau le plus bas (procédure de test) :

  1. Niveau assembly : s’exécute une fois lorsque l’assembly de test est chargé et déchargé
  2. Niveau classe : s’exécute une fois par classe de test
  3. Niveau de test global : s’exécute avant et après chaque méthode de test dans l’assembly
  4. Niveau de test : s’exécute pour chaque méthode de test (y compris chaque ligne de données dans les tests paramétrables)

Cycle de vie au niveau de l'assemblage

Les méthodes de cycle de vie d’assembly s’exécutent une fois lorsque l’assembly de test charge et décharge. Utilisez-les pour une configuration ponctuelle coûteuse, comme l’initialisation de base de données ou le démarrage du service.

AssemblyInitialize et AssemblyCleanup

[TestClass]
public class AssemblyLifecycleExample
{
    private static IHost? _host;

    [AssemblyInitialize]
    public static async Task AssemblyInit(TestContext context)
    {
        // Runs once before any tests in the assembly
        _host = await StartTestServerAsync();
        context.WriteLine("Test server started");
    }

    [AssemblyCleanup]
    public static async Task AssemblyCleanup(TestContext context)
    {
        // Runs once after all tests complete
        // TestContext parameter available in MSTest 3.8+
        if (_host != null)
        {
            await _host.StopAsync();
        }
    }

    private static Task<IHost> StartTestServerAsync()
    {
        // Server initialization
        return Task.FromResult<IHost>(null!);
    }
}

Spécifications

  • Les méthodes doivent être public static
  • Type de retour : void, Taskou ValueTask (MSTest v3.3+)
  • AssemblyInitialize nécessite un TestContext paramètre
  • AssemblyCleanup accepte zéro paramètre ou un TestContext paramètre (MSTest 3.8+)
  • Un seul attribut de chaque type est autorisé par assemblage
  • Doit se trouver dans une classe marquée avec [TestClass]

Conseil / Astuce

Analyseurs associés :

  • MSTEST0012 : valide la AssemblyInitialize signature.
  • MSTEST0013 : valide la AssemblyCleanup signature.

Cycle de vie au niveau de la classe

Les méthodes de cycle de vie de classe s’exécutent une fois par classe de test, avant et après toutes les méthodes de test de cette classe. Utilisez-les pour configurer des tests partagés entre les tests d’une classe.

ClassInitialize et ClassCleanup

[TestClass]
public class ClassLifecycleExample
{
    private static HttpClient? _client;

    [ClassInitialize]
    public static void ClassInit(TestContext context)
    {
        // Runs once before any tests in this class
        _client = new HttpClient
        {
            BaseAddress = new Uri("https://api.example.com")
        };
    }

    [ClassCleanup]
    public static void ClassCleanup()
    {
        // Runs after all tests in this class complete
        _client?.Dispose();
    }

    [TestMethod]
    public async Task GetUsers_ReturnsSuccess()
    {
        HttpResponseMessage response = await _client!.GetAsync("/users");
        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}

Spécifications

  • Les méthodes doivent être public static
  • Type de retour : void, Taskou ValueTask (MSTest v3.3+)
  • ClassInitialize nécessite un TestContext paramètre
  • ClassCleanup accepte zéro paramètre ou un TestContext paramètre (MSTest 3.8+)
  • Un seul attribut autorisé par classe

Comportement d’héritage

Contrôler si ClassInitialize s'exécute pour les classes dérivées à l'aide de InheritanceBehavior:

[TestClass]
public class BaseTestClass
{
    [ClassInitialize(InheritanceBehavior.BeforeEachDerivedClass)]
    public static void BaseClassInit(TestContext context)
    {
        // Runs before each derived class's tests
    }
}

[TestClass]
public class DerivedTestClass : BaseTestClass
{
    [TestMethod]
    public void DerivedTest()
    {
        // BaseClassInit runs before this class's tests
    }
}
InheritanceBehavior Descriptif
None (valeur par défaut) Initialiser uniquement les processus de la classe déclarée
BeforeEachDerivedClass Initialiser les lancements avant chaque classe dérivée

Conseil / Astuce

Analyseurs associés :

  • MSTEST0010 : valide la ClassInitialize signature.
  • MSTEST0011 : valide la ClassCleanup signature.
  • MSTEST0034 - recommande d’utiliser ClassCleanupBehavior.EndOfClass.

Cycle de vie au niveau du test global

Remarque

Les attributs de cycle de vie des tests globaux ont été introduits dans MSTest 3.10.0.

Les méthodes de cycle de vie des tests globaux s’exécutent avant et après chaque méthode de test dans l’ensemble de l’assembly, sans avoir à ajouter du code à chaque classe de test.

GlobalTestInitialize et GlobalTestCleanup

[TestClass]
public class GlobalTestLifecycleExample
{
    [GlobalTestInitialize]
    public static void GlobalTestInit(TestContext context)
    {
        // Runs before every test method in the assembly
        context.WriteLine($"Starting test: {context.TestName}");
    }

    [GlobalTestCleanup]
    public static void GlobalTestCleanup(TestContext context)
    {
        // Runs after every test method in the assembly
        context.WriteLine($"Finished test: {context.TestName}");
    }
}

Spécifications

  • Les méthodes doivent être public static
  • Type de retour : void, Taskou ValueTask
  • Doit avoir exactement un TestContext paramètre
  • Doit se trouver dans une classe marquée avec [TestClass]
  • Plusieurs méthodes avec ces attributs sont autorisées dans l’assembly

Remarque

Lorsqu’il existe plusieurs méthodes GlobalTestInitialize ou GlobalTestCleanup, l’ordre d’exécution n’est pas garanti. Ce TimeoutAttribute n’est pas pris en charge sur les méthodes GlobalTestInitialize.

Conseil / Astuce

Analyseur associé : MSTEST0050 - valide les méthodes globales des appareils de test.

Cycle de vie au niveau du test

Le cycle de vie au niveau du test s’exécute pour chaque méthode de test. Pour les tests paramétrables, le cycle de vie s’exécute pour chaque ligne de données.

Phase de configuration

Utiliser TestInitialize ou un constructeur pour la configuration par test :

[TestClass]
public class TestLevelSetupExample
{
    private Calculator? _calculator;

    public TestLevelSetupExample()
    {
        // Constructor runs before TestInitialize
        // Use for simple synchronous initialization
    }

    [TestInitialize]
    public async Task TestInit()
    {
        // Runs before each test method
        // Supports async, attributes like Timeout
        _calculator = new Calculator();
        await _calculator.InitializeAsync();
    }

    [TestMethod]
    public void Add_TwoNumbers_ReturnsSum()
    {
        int result = _calculator!.Add(2, 3);
        Assert.AreEqual(5, result);
    }
}

Constructeur et TestInitialize :

Aspect Constructeur TestInitialize
Prise en charge asynchrone Non Oui
Gestion du délai d’expiration Non Oui (avec [Timeout] attribut)
Ordre d’exécution Premier Après le constructeur
Héritage Base puis dérivée Base puis dérivée
Comportement des exceptions Nettoyage et Disponibilisation ne s’exécutent pas (aucune instance n’existe) Nettoyage et désallocation continuent d'être exécutés

Conseil / Astuce

Quelle approche dois-je utiliser ? Les constructeurs sont généralement préférés, car ils vous permettent d'utiliser readonly champs, ce qui applique l'immuabilité et rend votre classe de test plus facile à comprendre. Utilisez TestInitialize quand vous avez besoin d’une initialisation asynchrone ou d’une prise en charge du délai d’expiration.

Vous pouvez également combiner les deux approches : utilisez le constructeur pour l’initialisation synchrone simple des readonly champs et TestInitialize pour une configuration asynchrone supplémentaire qui dépend de ces champs.

Vous pouvez éventuellement activer les analyseurs de code pour appliquer une approche cohérente :

  • MSTEST0019 - Préférer les méthodes TestInitialize aux constructeurs
  • MSTEST0020 - Préférer les constructeurs aux méthodes TestInitialize

Phase d’exécution

La méthode de test s’exécute une fois l’installation terminée. Pour les méthodes de test async, MSTest attend le retour de Task ou ValueTask.

Avertissement

Par défaut, les méthodes de test asynchrones n’ont pas de SynchronizationContext. Cela ne s’applique pas aux UITestMethod tests dans UWP et WinUI, qui s’exécutent sur le thread d’interface utilisateur.

Phase de nettoyage

Utilisez TestCleanup ou IDisposable/IAsyncDisposable pour le nettoyage par test :

[TestClass]
public class TestLevelCleanupExample
{
    private HttpClient? _client;

    [TestInitialize]
    public void TestInit()
    {
        _client = new HttpClient();
    }

    [TestCleanup]
    public void TestCleanup()
    {
        if (_client != null)
        {
            _client.Dispose();
        }
    }

    [TestMethod]
    public async Task GetData_ReturnsSuccess()
    {
        HttpResponseMessage response = await _client!.GetAsync("https://example.com");
        Assert.IsTrue(response.IsSuccessStatusCode);
    }
}

Ordre d’exécution du nettoyage (dérivé de la base) :

  1. TestCleanup (classe dérivée)
  2. TestCleanup (classe de base)
  3. DisposeAsync (en cas d’implémentation)
  4. Dispose (en cas d’implémentation)

Conseil / Astuce

Vous pouvez éventuellement activer les analyseurs de code pour appliquer une approche de nettoyage cohérente :

  • MSTEST0021 - Préférer supprimer les méthodes TestCleanup
  • MSTEST0022 - Préférer TestCleanup aux méthodes Dispose

Si vous avez activé des analyseurs non MSTest, tels que des règles d’analyse de code .NET, vous pouvez voir CA1001 suggérer d’implémenter le modèle de suppression lorsque votre classe de test possède des ressources jetables. Ce comportement est attendu et vous devez suivre les instructions de l’analyseur.

Terminer l’ordre de niveau test

  1. Créer une instance de classe de test (constructeur)
  2. Définir la propriété TestContext (si elle est présente)
  3. Exécuter les méthodes GlobalTestInitialize
  4. Exécuter les méthodes TestInitialize (de la base à la dérivée)
  5. Exécuter la méthode de test
  6. Mettre à jour TestContext avec les résultats (par exemple, la propriété Outcome)
  7. Exécuter des TestCleanup méthodes (dérivées de la base)
  8. Exécuter les méthodes GlobalTestCleanup
  9. Exécuter DisposeAsync (s’il est implémenté)
  10. Exécuter Dispose (s’il est implémenté)

Conseil / Astuce

Analyseurs associés :

  • MSTEST0008 : valide la TestInitialize signature.
  • MSTEST0009 : valide la TestCleanup signature.
  • MSTEST0063 : valide le constructeur de classe de test.

Meilleures pratiques

  1. Utilisez l’étendue appropriée : placez la configuration au niveau le plus élevé qui est logique pour éviter le travail redondant.

  2. Maintenir le programme d’installation rapide : la configuration longue affecte tous les tests. Envisagez l’initialisation différée pour les ressources coûteuses.

  3. Nettoyer correctement : nettoyez toujours les ressources pour éviter les interférences de test et les fuites de mémoire.

  4. Gérez correctement l'asynchrone : utilisez des types de retour , et non async Task, pour les méthodes de cycle de vie asynchrones.

  5. Envisagez l’isolation des tests : chaque test doit être indépendant. Évitez l’état mutable partagé entre les tests.

  6. Utilisez GlobalTest avec parcimonie : les méthodes de cycle de vie globales s’exécutent pour chaque test, de sorte qu’elles restent légères.

Voir aussi