Выполнять юнит-тесты последовательно (а не параллельно)

Я пытаюсь выполнить модульное тестирование механизма управления хостом WCF, который я написал. Движок в основном создает экземпляры ServiceHost на лету в зависимости от конфигурации. Это позволяет нам динамически переконфигурировать, какие службы доступны, без необходимости выключать их все и перезапускать каждый раз, когда добавляется новый сервис или удаляется старый.

Однако я столкнулся с трудностью при модульном тестировании этого механизма управления хостом из-за того, как работает ServiceHost. Если ServiceHost уже был создан, открыт и еще не закрыт для определенной конечной точки, другой ServiceHost для той же конечной точки не может быть создан, что приведет к исключению. Из-за того, что современные платформы модульного тестирования распараллеливают выполнение своих тестов, у меня нет эффективного способа модульного тестирования этого куска кода.

Я использовал xUnit.NET, надеясь, что из-за его расширяемости я смог найти способ заставить его запускать тесты поочередно. Однако мне не повезло. Я надеюсь, что кто-то здесь, на SO, столкнулся с подобной проблемой и знает, как заставить модульные тесты запускаться последовательно.

ПРИМЕЧАНИЕ. ServiceHost - это класс WCF, написанный Microsoft. У меня нет возможности изменить его поведение. Хостинг каждой конечной точки службы только один раз - это тоже правильное поведение... однако это не особенно способствует модульному тестированию.

12 ответов

Каждый класс тестов является уникальным набором тестов, и тесты в нем будут выполняться последовательно, поэтому, если вы поместите все свои тесты в одну коллекцию, он будет выполняться последовательно.

В xUnit вы можете внести следующие изменения, чтобы добиться этого:

Следующее будет работать параллельно:

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");
        }
    }
}

Чтобы сделать его последовательным, вам просто нужно поместить оба тестовых класса в одну коллекцию:

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");
        }
    }
}

Для получения дополнительной информации вы можете обратиться по этой ссылке

Как указано выше, все хорошие юнит-тесты должны быть изолированы на 100%. Использование общего состояния (например, в зависимости от static свойство, которое изменяется каждым тестом) считается плохой практикой.

Тем не менее, на ваш вопрос о последовательном запуске тестов xUnit есть ответ! Я столкнулся с точно такой же проблемой, потому что моя система использует статический сервисный локатор (который не идеален).

По умолчанию xUnit 2.x запускает все тесты параллельно. Это можно изменить для каждой сборки, определив CollectionBehavior в вашем AssemblyInfo.cs в вашем тестовом проекте.

Для разделения на сборку используйте:

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

или для распараллеливания вообще не используйте:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Последний, вероятно, тот, который вы хотите. Дополнительную информацию о распараллеливании и настройке можно найти в документации по xUnit.

Для проектов.NET Core создайте xunit.runner.json с:

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

Кроме того, ваш project.json должен содержать

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

Для проектов.NET Core вы можете настроить xUnit с помощью xunit.runner.json файл, как описано в https://xunit.github.io/docs/configuring-with-json.html.

Параметр, который необходимо изменить, чтобы остановить параллельное выполнение теста, parallelizeTestCollections, который по умолчанию true:

Установите это в true если сборка готова запустить тесты внутри этой сборки параллельно друг против друга.... Установите это в false отключить все распараллеливание внутри этой тестовой сборки.

Тип схемы JSON: логическое значение
Значение по умолчанию:true

Так что минимальный xunit.runner.json для этого выглядит

{
    "parallelizeTestCollections": false
}

Как отмечено в документации, не забудьте включить этот файл в вашу сборку:

  • Если для параметра " Копировать в выходной каталог" выбрано значение " Копировать" в свойствах файла в Visual Studio или
  • Добавление

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

    на ваш .csproj файл или

  • Добавление

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

    на ваш project.json файл

в зависимости от типа вашего проекта.

Наконец, в дополнение к вышесказанному, если вы используете Visual Studio, убедитесь, что вы случайно не нажали кнопку " Выполнить параллельные тесты", что приведет к параллельному запуску тестов, даже если вы выключили параллелизацию в xunit.runner.json, Разработчики пользовательского интерфейса Microsoft хитро сделали эту кнопку немаркированной, трудно заметной и находящейся примерно в сантиметре от кнопки "Выполнить все" в Test Explorer, просто чтобы максимально увеличить вероятность того, что вы нажмете ее по ошибке и не будете знать, почему ваши тесты вдруг терпят неудачу:

Скриншот с обведенной кнопкой

Это старый вопрос, но я хотел написать решение для людей, ищущих новых, как я:)

Примечание. Я использую этот метод в тестах интеграции Dot Net Core WebUI с xunit версии 2.4.1.

Создайте пустой класс с именем NonParallelCollectionDefinitionClass и затем присвойте этому классу атрибут CollectionDefinition, как показано ниже. (Важная часть - параметр DisableParallelization = true.)

using Xunit;

namespace WebUI.IntegrationTests.Common
{
    [CollectionDefinition("Non-Parallel Collection", DisableParallelization = true)]
    public class NonParallelCollectionDefinitionClass
    {
    }
}

После этого добавьте атрибут Collection в класс, который вы не хотите, чтобы он запускался параллельно, как показано ниже. (Важная часть - это имя коллекции. Оно должно совпадать с именем, используемым в CollectionDefinition)

namespace WebUI.IntegrationTests.Controllers.Users
{
    [Collection("Non-Parallel Collection")]
    public class ChangePassword : IClassFixture<CustomWebApplicationFactory<Startup>>
    ...

Когда мы это делаем, сначала запускаются другие параллельные тесты. После этого запускаются другие тесты с атрибутом Коллекция ("Непараллельная коллекция").

Вы можете использовать плейлист

щелкните правой кнопкой мыши на методе тестирования -> Добавить в список воспроизведения -> Новый список воспроизведения

затем вы можете указать порядок выполнения, по умолчанию, когда вы добавляете их в список воспроизведения, но вы можете изменить файл списка воспроизведения, как вы хотите

Я не знаю деталей, но, похоже, вы пытаетесь провести интеграционное тестирование, а не юнит-тестирование. Если бы вы могли изолировать зависимость от ServiceHost, это, вероятно, сделает ваше тестирование проще (и быстрее). Так (например) вы можете проверить следующее независимо:

  • Класс чтения конфигурации
  • ServiceHost factory (возможно, в качестве интеграционного теста)
  • Класс двигателя, который принимает IServiceHostFactory и IConfiguration

Инструменты, которые могут помочь, включают в себя изолирующие (насмешливые) платформы и (необязательно) IoC-контейнеры. Увидеть:

Может быть, вы можете использовать Advanced Unit Testing. Это позволяет вам определить последовательность, в которой вы запускаете тест. Поэтому вам, возможно, придется создать новый файл cs для размещения этих тестов.

Вот как вы можете согнуть тестовые методы для работы в нужной вам последовательности.

[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.");
  ...
}

Дайте мне знать, работает ли это.

Ни один из предложенных ответов пока не помог мне. У меня есть приложение ядра dotnet с XUnit 2.4.1. Я добился желаемого поведения с помощью обходного пути, установив вместо этого блокировку в каждом модульном тесте. В моем случае меня не заботил порядок выполнения, просто тесты были последовательными.

public class TestClass
{
    [Fact]
    void Test1()
    {
        lock (this)
        {
            //Test Code
        }
    }

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}

Я добавил атрибут [Collection("Sequential")] в базовый класс:

    namespace IntegrationTests
    {
      [Collection("Sequential")]
      public class SequentialTest : IDisposable
      ...


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }

Для меня, в приложении.Net Core Console, когда я хотел запускать тестовые методы (не классы) синхронно, единственное, что сработало, было описано в этом блоге:xUnit: Control the Test Execution Order

Вот правильное решение для последовательного выполнения модульных тестов.
Xunit предоставляет класс CollectionBehaviorAttribute для управления поведением выполнения модульных тестов. Это может помочь предотвратить состояния гонки, конфликты общих ресурсов и другие проблемы, которые могут возникнуть при одновременном запуске тестов.

      using Xunit;
using Moq;    
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace UnitTests
{ 
    public class UnitTest
    {
    }
}
Другие вопросы по тегам