web-dev-qa-db-fra.com

Exécuter des tests unitaires en série (plutôt qu'en parallèle)

J'essaie de tester unitaire un moteur de gestion d'hôte WCF que j'ai écrit. Le moteur crée essentiellement des instances ServiceHost à la volée en fonction de la configuration. Cela nous permet de reconfigurer dynamiquement les services disponibles sans avoir à tous les arrêter et les redémarrer chaque fois qu'un nouveau service est ajouté ou qu'un ancien est supprimé.

Cependant, j'ai rencontré des difficultés pour tester ce moteur de gestion d'hôte à cause du fonctionnement de ServiceHost. Si un ServiceHost a déjà été créé, ouvert et pas encore fermé pour un point de terminaison particulier, un autre ServiceHost pour le même point de terminaison ne peut pas être créé, ce qui entraîne une exception. Étant donné que les plates-formes modernes de test unitaire parallélisent leur exécution de test, je n'ai aucun moyen efficace de tester à l'unité ce morceau de code.

J'ai utilisé xUnit.NET, en espérant qu'en raison de son extensibilité, je pourrais trouver un moyen de le forcer à exécuter les tests en série. Cependant, je n'ai pas eu de chance. J'espère que quelqu'un ici sur SO a rencontré un problème similaire et sait comment exécuter les tests unitaires en série.

REMARQUE: ServiceHost est une classe WCF, écrite par Microsoft. Je n'ai pas la possibilité de changer son comportement. Héberger chaque point de terminaison de service une seule fois est également le bon comportement ... cependant, il n'est pas particulièrement propice aux tests unitaires.

69
jrista

Comme indiqué ci-dessus, tous les bons tests unitaires doivent être isolés à 100%. L'utilisation d'un état partagé (par exemple, en fonction d'une propriété static qui est modifiée par chaque test) est considérée comme une mauvaise pratique.

Cela dit, votre question sur l'exécution des tests xUnit en séquence a une réponse! J'ai rencontré exactement le même problème car mon système utilise un localisateur de service statique (ce qui est loin d'être idéal).

Par défaut, xUnit 2.x exécute tous les tests en parallèle. Cela peut être modifié par assemblage en définissant le CollectionBehavior dans votre AssemblyInfo.cs dans votre projet de test.

Pour une séparation par assemblage, utilisez:

using Xunit;
[Assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]

ou pour aucune parallélisation du tout:

[Assembly: CollectionBehavior(DisableTestParallelization = true)]

Ce dernier est probablement celui que vous souhaitez. Plus d'informations sur la parallélisation et la configuration peuvent être trouvées sur le documentation xUnit .

78
Squiggle

Chaque classe de test est une collection de tests unique et les tests sous-jacents s'exécuteront en séquence, donc si vous placez tous vos tests dans la même collection, elle s'exécutera séquentiellement.

Dans xUnit, vous pouvez apporter les modifications suivantes pour y parvenir:

Ce qui suit se déroulera en parallèle:

namespace IntegrationTests
{
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

Pour le rendre séquentiel, il vous suffit de mettre les deux classes de test sous la même collection:

namespace IntegrationTests
{
    [Collection("Sequential")]
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    [Collection("Sequential")]
    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

Pour plus d'informations, vous pouvez vous référer à ce lien

62
Abhinav Saxena

Pour les projets .NET Core, créez xunit.runner.json avec:

{
  "parallelizeAssembly": false,
  "parallelizeTestCollections": false
}

De plus, votre csproj doit contenir

<ItemGroup>
  <None Update="xunit.runner.json"> 
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

Pour les anciens projets .Net Core, votre project.json Devrait contenir

"buildOptions": {
  "copyToOutput": {
    "include": [ "xunit.runner.json" ]
  }
}
48
Dmitry Polomoshnov

Pour les projets .NET Core, vous pouvez configurer xUnit avec un xunit.runner.json fichier, comme indiqué sur https://xunit.github.io/docs/configuring-with-json.html .

Le paramètre que vous devez modifier pour arrêter l'exécution de test parallèle est parallelizeTestCollections, qui est par défaut true:

Définissez cette valeur sur true si l'assembly souhaite exécuter des tests à l'intérieur de cet assembly en parallèle les uns contre les autres. ... Définissez cette valeur sur false pour désactiver toute parallélisation dans cet assemblage de test.

Type de schéma JSON: booléen
Valeur par défaut: true

Donc, un minimum xunit.runner.json à cet effet ressemble

{
    "parallelizeTestCollections": false
}

Comme indiqué dans la documentation, n'oubliez pas d'inclure ce fichier dans votre build, soit en:

  • Setting Copy to Output Directory to Copy if newer in the file's Properties in Visual Studio, or
  • Ajouter

    <Content Include=".\xunit.runner.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    

    à ton .csproj fichier, ou

  • Ajouter

    "buildOptions": {
      "copyToOutput": {
        "include": [ "xunit.runner.json" ]
      }
    }
    

    à ton project.json fichier

selon votre type de projet.

Enfin, en plus à ce qui précède, si vous utilisez Visual Studio, assurez-vous que vous n'avez pas accidentellement cliqué sur le bouton Exécuter des tests en parallèle, ce qui entraînera l'exécution des tests en parallèle même si vous avez désactivé la parallélisation dans xunit.runner.json. Les concepteurs d'interface utilisateur de Microsoft ont astucieusement rendu ce bouton sans étiquette, difficile à remarquer et à environ un centimètre du bouton "Tout exécuter! dans l'Explorateur de tests, juste pour maximiser les chances que vous le touchiez par erreur et ne sais pas pourquoi vos tests échouent soudainement:

Screenshot with the button circled

15
Mark Amery

vous pouvez utiliser Playlist

clic droit sur la méthode de test -> Ajouter à la playlist -> Nouvelle playlist

vous pouvez ensuite spécifier l'ordre d'exécution, la valeur par défaut étant, lorsque vous les ajoutez à la liste de lecture, mais vous pouvez modifier le fichier de liste de lecture comme vous le souhaitez

enter image description here

6
HB MAAM

Je ne connais pas les détails, mais il semble que vous essayiez de faire des tests d'intégration plutôt que unité test . Si vous pouviez isoler la dépendance sur ServiceHost, cela rendrait probablement vos tests plus faciles (et plus rapides). Ainsi (par exemple), vous pouvez tester les éléments suivants indépendamment:

  • Classe de lecture de configuration
  • Usine ServiceHost (éventuellement comme test d'intégration)
  • Classe de moteur qui prend un IServiceHostFactory et un IConfiguration

Les outils qui pourraient aider à inclure des cadres d'isolation (de simulation) et (facultativement) des cadres de conteneurs IoC. Voir:

5
TrueWill

Vous pouvez peut-être utiliser Advanced Unit Testing . Il vous permet de définir la séquence dans laquelle vous exécutez le test . Vous devrez donc peut-être créer un nouveau fichier cs pour héberger ces tests.

Voici comment vous pouvez plier les méthodes de test pour qu'elles fonctionnent dans l'ordre que vous souhaitez.

[Test]
[Sequence(16)]
[Requires("POConstructor")]
[Requires("WorkOrderConstructor")]
public void ClosePO()
{
  po.Close();

  // one charge slip should be added to both work orders

  Assertion.Assert(wo1.ChargeSlipCount==1,
    "First work order: ChargeSlipCount not 1.");
  Assertion.Assert(wo2.ChargeSlipCount==1,
    "Second work order: ChargeSlipCount not 1.");
  ...
}

Faites-moi savoir si cela fonctionne.

3
Graviton