Partage via


Tester l’exécution et le contrôle dans MSTest

MSTest fournit des attributs pour contrôler la façon dont les tests s’exécutent, notamment la parallélisation, les modèles de thread, les délais d’expiration, les nouvelles tentatives et l’exécution conditionnelle en fonction de la plateforme ou de l’environnement.

Attributs de thread

Les attributs de thread contrôlent le modèle de fil d'exécution utilisé par les méthodes de test. Ces attributs sont essentiels lors du test des composants COM, des éléments d’interface utilisateur ou du code avec des exigences de thread spécifiques.

STATestClassAttribute

Toutes les méthodes de test d'une classe (y compris STATestClassAttribute et ClassInitialize) s'exécutent dans un appartement à un seul thread (STA). Utilisez cet attribut lors du test des objets COM qui nécessitent STA.

[STATestClass]
public class ComInteropTests
{
    [TestMethod]
    public void TestComComponent()
    {
        // This test runs in an STA thread
        var comObject = new SomeComObject();
        // Test COM interactions
    }
}

Note

Cet attribut est uniquement pris en charge sur Windows dans MSTest v3.6 et versions ultérieures.

STATestMethodAttribute

STATestMethodAttribute exécute une méthode de test spécifique dans un appartement à thread unique. Utilisez cet attribut pour les tests individuels qui ont besoin de STA alors que d’autres tests de la classe ne le font pas.

[TestClass]
public class MixedThreadingTests
{
    [STATestMethod]
    public void TestRequiringSTA()
    {
        // This test runs in an STA thread
    }

    [TestMethod]
    public void RegularTest()
    {
        // This test uses default threading
    }
}

Note

Cet attribut est uniquement pris en charge sur Windows dans MSTest v3.6 et versions ultérieures.

Conserver le contexte STA pour les continuations asynchrones

À compter de MSTest 4.1, la STATestMethodAttribute propriété inclut une UseSTASynchronizationContext propriété qui garantit l’exécution des continuations asynchrones sur le même thread STA. Lorsqu’il est activé, l’attribut crée un code personnalisé SynchronizationContext qui publie des continuations vers le thread STA, qui est essentiel pour tester les composants de l’interface utilisateur qui nécessitent un thread STA tout au long de leurs opérations asynchrones.

[TestClass]
public class UIComponentTests
{
    [STATestMethod(UseSTASynchronizationContext = true)]
    public async Task TestAsyncUIOperation()
    {
        // Initial code runs on STA thread
        var control = new MyControl();
        
        await control.LoadDataAsync();
        
        // Continuation also runs on STA thread,
        // ensuring UI operations remain valid
        Assert.IsTrue(control.IsDataLoaded);
    }
}

Conseil / Astuce

Utilisez cette option UseSTASynchronizationContext = true lors du test des composants Windows Forms ou WPF qui effectuent des opérations asynchrones et attendent que leurs continuations s’exécutent sur le même thread.

UITestMethodAttribute

L’attribut UITestMethod planifie l’exécution des tests sur le thread d’interface utilisateur. Cet attribut est conçu pour tester les applications UWP et WinUI qui nécessitent un accès au thread d’interface utilisateur.

[TestClass]
public class WinUITests
{
    [UITestMethod]
    public void TestUIComponent()
    {
        // This test runs on the UI thread
        var button = new Button();
        button.Content = "Click me";
        Assert.IsNotNull(button.Content);
    }
}

Note

Cet attribut nécessite l’adaptateur MSTest approprié pour les plateformes UWP ou WinUI. Pour plus d’informations, consultez la section prise en charge de la plateforme.

Attributs de parallélisation

Les attributs de parallélisation contrôlent si et comment les tests s’exécutent simultanément, ce qui améliore le temps d’exécution des tests.

ParallelizeAttribute

Par défaut, MSTest exécute des tests séquentiellement. L’attribut au niveau de l’assembly ParallelizeAttribute active l’exécution de test parallèle.

using Microsoft.VisualStudio.TestTools.UnitTesting;

[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)]

Étendue de parallélisation

Scope Comportement
ClassLevel Plusieurs classes de test s’exécutent en parallèle, mais les tests au sein d’une classe s’exécutent de manière séquentielle
MethodLevel Les méthodes de test individuelles peuvent s’exécuter en parallèle, quelle que soit leur classe

Threads de travail

La Workers propriété spécifie le nombre maximal de threads pour l’exécution parallèle :

  • 0 (par défaut) : utiliser le nombre de processeurs logiques sur l’ordinateur
  • Nombre entier positif : utilisez ce nombre de threads spécifiques
// Parallelize at class level with 2 worker threads
[assembly: Parallelize(Workers = 2, Scope = ExecutionScope.ClassLevel)]

Conseil / Astuce

Vous pouvez également configurer la parallélisation par le biais de runsettings ou detestconfig.json sans modifier le code.

Conseil / Astuce

Activez la parallélisation au niveau de l’assembly par défaut, même si de nombreux tests nécessitent actuellement une exécution séquentielle. Cette approche encourage l’écriture de nouveaux tests qui prennent en charge l’exécution parallèle à partir du début. Utilisez l’analyseur MSTEST0001 pour vous assurer que l’assembly déclare explicitement son intention de parallélisation avec [assembly: Parallelize] ou [assembly: DoNotParallelize]. Une fois la parallélisation activée, passez en revue chaque classe de test pour déterminer si elle prend en charge l’exécution simultanée en toute sécurité. Souvent, l’exclusion de quelques classes ou méthodes DoNotParallelize est suffisante, ce qui permet à la majorité de vos tests de s’exécuter en parallèle pour accélérer considérablement l’exécution des tests.

DoNotParallelizeAttribute

Le DoNotParallelizeAttribute empêche l’exécution parallèle pour des assemblies, classes ou méthodes spécifiques. Utilisez cet attribut lorsque les tests partagent l’état ou les ressources qui ne peuvent pas être accessibles en toute sécurité simultanément.

[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

[TestClass]
public class ParallelTests
{
    [TestMethod]
    public void CanRunInParallel()
    {
        // This test can run with others
    }
}

[TestClass]
[DoNotParallelize]
public class SequentialTests
{
    [TestMethod]
    public void MustRunSequentially()
    {
        // This class's tests run sequentially
    }
}

[TestClass]
public class MixedTests
{
    [TestMethod]
    public void CanRunInParallel()
    {
        // This test can run with others
    }

    [TestMethod]
    [DoNotParallelize]
    public void MustBeIsolated()
    {
        // This specific test doesn't run in parallel
    }
}

Note

Vous n’avez besoin DoNotParallelize que lorsque vous avez activé l’exécution parallèle avec l’attribut Parallelize .

Attributs de délai d’expiration

Les attributs de délai d’attente empêchent l’exécution indéfinie des tests et aident à identifier les problèmes de performances.

TimeoutAttribute

Spécifie TimeoutAttribute la durée maximale (en millisecondes) qu’une méthode de test ou de montage peut exécuter. Si l’exécution dépasse cette période, le test échoue.

[TestClass]
public class TimeoutTests
{
    [TestMethod]
    [Timeout(5000)] // 5 seconds
    public void TestWithTimeout()
    {
        // Test must complete within 5 seconds
    }
}

Conseil / Astuce

Vous pouvez configurer un délai d’expiration de test global par le biais de runsettings (TestTimeout) ou detestconfig.json (timeout.test) sans modifier de code.

Appliquer le délai d’expiration aux méthodes de montage

Vous pouvez également appliquer des délais d’expiration aux méthodes d’initialisation et de nettoyage :

[TestClass]
public class FixtureTimeoutTests
{
    [ClassInitialize]
    [Timeout(10000)]
    public static void ClassInit(TestContext context)
    {
        // Must complete within 10 seconds
    }

    [TestInitialize]
    [Timeout(2000)]
    public void TestInit()
    {
        // Must complete within 2 seconds
    }
}

Conseil / Astuce

Chaque méthode de montage qui accepte un [Timeout] attribut a un paramètre de configuration global équivalent. Configurez les délais d’expiration globalement par runsettings ou detestconfig.json à l’aide de paramètres tels que TestInitializeTimeout, ClassInitializeTimeout, AssemblyInitializeTimeout et leurs équivalents de nettoyage.

Note

Les délais d'expiration ne sont pas garantis d'être précisément exacts. Le test abandonne une fois que le temps spécifié passe, mais l’annulation réelle peut prendre un peu plus de temps.

Annulation coopérative

Par défaut, MSTest encapsule chaque méthode de test chronométrée dans une tâche ou un thread distinct. Lorsque le délai d’expiration est atteint, l’infrastructure cesse d’observer le test, mais la tâche sous-jacente continue de s’exécuter en arrière-plan. Ce comportement peut entraîner des problèmes :

  • La méthode de test continue d’accéder aux ressources et de muter l’état même après le délai d’expiration.
  • L’exécution en arrière-plan peut entraîner des conditions de course affectant les tests suivants.
  • Chaque méthode chronométrée entraîne une surcharge supplémentaire due à l'enveloppe de tâche/thread.

À compter de MSTest 3.6, utilisez la CooperativeCancellation propriété pour éviter ces problèmes. En mode coopératif, MSTest n’encapsule pas votre test dans une tâche supplémentaire. Au lieu de cela, lorsque le délai d’expiration est atteint, le cadre signale le jeton d’annulation. Votre code de test est chargé de vérifier régulièrement le jeton et de terminer correctement.

[TestClass]
public class CooperativeTimeoutTests
{
    [TestMethod]
    [Timeout(5000, CooperativeCancellation = true)]
    public async Task TestWithCooperativeCancellation(CancellationToken cancellationToken)
    {
        // Check the token periodically
        while (!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(100, cancellationToken);
            // Do work
        }
    }

    [TestMethod]
    [Timeout(5000, CooperativeCancellation = true)]
    public void SyncTestWithCooperativeCancellation(CancellationToken cancellationToken)
    {
        // Works with sync methods too
        for (int i = 0; i < 1000; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            // Do work
        }
    }
}

Avantages de l’annulation coopérative :

  • Réduction de la surcharge de charge de travail (aucune enveloppe de tâche ou de fil d'exécution supplémentaire par test).
  • Nettoyage des ressources plus propre, car votre code gère explicitement l’annulation.
  • S’aligne sur les modèles d’annulation .NET standard.
  • Comportement déterministe en évitant les conditions de course entre le code de test et l'exécution en arrière-plan non observée.

Note

L’annulation coopérative nécessite que votre code de test vérifie régulièrement le jeton d’annulation. Si votre code ne vérifie pas le jeton, le test ne s’arrête pas réellement lorsque le délai d’expiration est atteint.

Conseil / Astuce

Vous pouvez activer l’annulation coopérative globalement pour tous les attributs de délai d’expiration par le biais de runsettings ou detestconfig.json au lieu de le définir individuellement sur chaque attribut.

Conseil / Astuce

Analyseurs associés :

  • MSTEST0045 : recommande d’utiliser l’annulation coopérative pour les attributs de délai d’expiration.

Attributs de nouvelle tentative

Les attributs de nouvelle tentative permettent de gérer les tests flaky en réexécutant automatiquement les tests ayant échoué.

RetryAttribute

Introduit dans MSTest 3.8, le RetryAttribute retente automatiquement les méthodes de test qui échouent ou expirent. Configurez le nombre maximal de tentatives, le délai entre celles-ci et la stratégie de recul.

[TestClass]
public class RetryTests
{
    [TestMethod]
    [Retry(3)] // Retry up to 3 times if the test fails
    public void FlakeyNetworkTest()
    {
        // Test that might occasionally fail due to network issues
    }

    [TestMethod]
    [Retry(3, MillisecondsDelayBetweenRetries = 1000, BackoffType = DelayBackoffType.Exponential)]
    public void TestWithExponentialBackoff()
    {
        // Retries with increasing delays: 1s, 2s, 4s
    }

    [TestMethod]
    [Retry(5, MillisecondsDelayBetweenRetries = 500, BackoffType = DelayBackoffType.Constant)]
    public void TestWithConstantDelay()
    {
        // Retries with constant 500ms delay between attempts
    }
}

Options de configuration

Propriété Descriptif Par défaut
MaxRetryAttempts Nombre maximal de tentatives de nouvelle tentative (lecture seule, définie via le constructeur) Obligatoire
MillisecondsDelayBetweenRetries Délai de base entre les nouvelles tentatives (en ms) 0
BackoffType Constant ou Exponential délai Constant

Note

Un seul RetryAttribute peut être présent sur une méthode de test. Vous ne pouvez pas utiliser RetryAttribute sur les méthodes qui ne sont pas marquées avec TestMethod.

Conseil / Astuce

Analyseurs associés :

  • MSTEST0043 : recommande d’utiliser RetryAttribute sur les méthodes de test.

Implémentations de retries personnalisées

Créez une logique de nouvelle tentative personnalisée en hériter de RetryBaseAttribute:

public class CustomRetryAttribute : RetryBaseAttribute
{
    private readonly int _maxRetries;

    public CustomRetryAttribute(int maxRetries)
    {
        _maxRetries = maxRetries;
    }

    // Implement abstract members
    // Add custom logic for retry conditions
}

Attributs d’exécution conditionnelle

Les attributs d’exécution conditionnelle contrôlent si les tests s’exécutent en fonction de conditions spécifiques telles que le système d’exploitation ou l’environnement CI.

ConditionBaseAttribute

Il ConditionBaseAttribute s’agit de la classe de base abstraite pour l’exécution conditionnelle. MSTest fournit plusieurs implémentations intégrées.

Note

Par défaut, les attributs de condition ne sont pas hérités. L’application de ces classes à une classe de base n’affecte pas les classes dérivées. Les attributs de condition personnalisée peuvent remplacer ce comportement en redéfinissant AttributeUsage, mais il n’est pas recommandé de maintenir la cohérence avec les attributs de condition intégrés.

Conseil / Astuce

Analyseurs associés :

  • MSTEST0041 : recommande d’utiliser des attributs basés sur des conditions avec des classes de test.

OSConditionAttribute

Les OSConditionAttribute exécutent ou ignorent les tests en fonction du système d’exploitation. Utilisez l’énumération OperatingSystems des indicateurs pour spécifier les systèmes d’exploitation qui s’appliquent.

[TestClass]
public class OSSpecificTests
{
    [TestMethod]
    [OSCondition(OperatingSystems.Windows)]
    public void WindowsOnlyTest()
    {
        // Runs only on Windows
    }

    [TestMethod]
    [OSCondition(OperatingSystems.Linux | OperatingSystems.OSX)]
    public void UnixLikeOnlyTest()
    {
        // Runs on Linux or macOS
    }

    [TestMethod]
    [OSCondition(ConditionMode.Exclude, OperatingSystems.Windows)]
    public void SkipOnWindowsTest()
    {
        // Runs on any OS except Windows
    }
}

Systèmes d'exploitation pris en charge

Système d'exploitation Descriptif
Windows Microsoft Windows
Linux Distributions Linux
OSX macOS
FreeBSD FreeBSD

Combinez des systèmes d’exploitation avec l’opérateur OR au niveau du bit (|).

Conseil / Astuce

Analyseurs associés :

  • MSTEST0061 : recommande d’utiliser l’attribut OSCondition au lieu de vérifications d’exécution.

CIConditionAttribute

Les CIConditionAttribute exécutent ou ignorent les tests selon qu'ils s'exécutent dans un environnement d’intégration continue.

[TestClass]
public class CIAwareTests
{
    [TestMethod]
    [CICondition] // Default: runs only in CI
    public void CIOnlyTest()
    {
        // Runs only in CI environments
    }

    [TestMethod]
    [CICondition(ConditionMode.Include)]
    public void ExplicitCIOnlyTest()
    {
        // Same as above, explicitly stated
    }

    [TestMethod]
    [CICondition(ConditionMode.Exclude)]
    public void LocalDevelopmentOnlyTest()
    {
        // Skipped in CI, runs during local development
    }
}

IgnoreAttribute

L’objet IgnoreAttribute ignore inconditionnellement une classe de test ou une méthode. Si vous le souhaitez, fournissez une raison d’ignorer.

Conseil / Astuce

Analyseur associé : MSTEST0015 - La méthode de test ne doit pas être ignorée. Activez cet analyseur pour détecter les tests qui sont ignorés définitivement.

[TestClass]
public class IgnoreExamples
{
    [TestMethod]
    [Ignore]
    public void TemporarilyDisabled()
    {
        // This test is skipped
    }

    [TestMethod]
    [Ignore("Waiting for bug #123 to be fixed")]
    public void DisabledWithReason()
    {
        // This test is skipped with a documented reason
    }
}

[TestClass]
[Ignore("Entire class needs refactoring")]
public class IgnoredTestClass
{
    [TestMethod]
    public void Test1() { }  // Skipped

    [TestMethod]
    public void Test2() { }  // Skipped
}

Lorsque vous ignorez les tests en raison de problèmes connus, utilisez WorkItemAttribute ou GitHubWorkItemAttribute pour la traçabilité :

[TestClass]
public class TrackedIgnoreExamples
{
    [TestMethod]
    [Ignore("Waiting for fix")]
    [WorkItem(12345)]
    public void TestWithWorkItem()
    {
        // Linked to work item 12345
    }

    [TestMethod]
    [Ignore("Known issue")]
    [GitHubWorkItem("https://github.com/owner/repo/issues/42")]
    public void TestWithGitHubIssue()
    {
        // Linked to GitHub issue #42
    }
}

Meilleures pratiques

  1. Utilisez la parallélisation judicieusement : activez la parallélisation pour les tests indépendants, mais utilisez-la DoNotParallelize pour les tests qui partagent l’état.

  2. Définir les délais d’expiration appropriés : choisissez les délais d’expiration qui autorisent l’exécution normale, mais interceptent les tests bloqués. Envisagez d’utiliser des environnements CI lents.

  3. Privilégiez l’annulation coopérative : utilisez l’annulation coopérative afin d'éviter la surcharge des wrappers de tâches supplémentaires et empêcher l’exécution en arrière-plan des tests qui ont expiré. Activez l’analyseur MSTEST0045 pour appliquer cette pratique.

  4. Déclaration des tests ignorés : fournissez toujours une raison et une référence à l'élément de travail lorsque vous ignorez les tests.

  5. Utilisez les tentatives supplémentaires avec parcimonie : résolvez la cause première des tests instables plutôt que de vous appuyer sur les tentatives supplémentaires.

  6. Testez le code propre au système d’exploitation de manière appropriée : utilisez-le OSCondition pour exécuter des tests spécifiques à la plateforme uniquement lorsqu’ils sont applicables.

Voir aussi