Управление порядком выполнения модульных тестов в Visual Studio
Хорошо, я закончил искать хорошую информацию по этому вопросу. У меня есть серия модульных тестов, которые вызывают статический класс, который после инициализации устанавливает свойства, которые не могут (или не желают) изменяться.
Моя проблема в том, что я не могу принудительно установить порядок выполнения тестов. Если бы я мог, я мог бы запустить их таким образом, чтобы статические свойства были установлены надежным способом, и я мог бы утверждать их, но, к сожалению, платформа Microsoft.VisualStudio.TestTools.UnitTesting просто запускает их в на первый взгляд случайном порядке,
Итак, я нашел этот http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx который говорит в разделе "Примечания" "Этот атрибут не используется тестовой системой. предоставляется пользователю в пользовательских целях. " А? Что хорошего тогда? Ожидают ли они, что я напишу свою собственную тестовую оболочку, чтобы воспользоваться этим невероятным атрибутом (о котором я мог бы написать сам, если бы хотел достичь такого уровня усилий...)
Итак, хватит разглагольствовать; В итоге, есть ли способ контролировать порядок выполнения моих модульных тестов?
[TestMethod]
[Priority(0)]
и т.д., похоже, не работает, что имеет смысл, так как Microsoft говорит, что не будет.
Также, пожалуйста, никаких комментариев о "нарушении изоляции". TestClass изолирует то, что я тестирую, а не отдельные TestMethods. В любом случае, каждый тест может выполняться независимо независимо, просто они не могут выполняться вместе в случайном порядке, поскольку нет никакого способа разрушить статический класс.
О, я тоже знаю про "Заказанный тест".
14 ответов
Слияние ваших тестов в один гигантский тест будет работать. Чтобы сделать тестовый метод более читабельным, вы можете сделать что-то вроде
[TestMethod]
public void MyIntegratonTestLikeUnitTest()
{
AssertScenarioA();
AssertScenarioB();
....
}
private void AssertScenarioA()
{
// Assert
}
private void AssertScenarioB()
{
// Assert
}
На самом деле проблема, с которой вы столкнулись, предполагает, что вы, вероятно, должны улучшить тестируемость реализации.
Вы можете использовать плейлист
Щелкните правой кнопкой мыши на методе тестирования -> Добавить в список воспроизведения -> Новый список воспроизведения
порядок выполнения будет таким, как вы добавляете их в список воспроизведения, но если вы хотите изменить его, у вас есть файл
Как вы уже знаете, пуристы говорят, что запрещается проводить заказанные тесты. Это может быть правдой для модульных тестов. MSTest и другие инфраструктуры модульных тестов используются для запуска только модульных тестов, а также тестов пользовательского интерфейса, тестов полной интеграции, как вы это называете. Возможно, нам не следует называть их фреймворками модульного тестирования, или, может быть, нам следует использовать их в соответствии со своими потребностями Это то, что большинство людей делают в любом случае.
Я использую VS2015, и я ДОЛЖЕН запускать тесты в определенном порядке, потому что я выполняю тесты пользовательского интерфейса (Selenium).
Приоритет - вообще ничего не делает. Этот атрибут не используется тестовой системой. Он предоставляется пользователю в пользовательских целях.
заказанный тест - это работает, но я не рекомендую это, потому что:
- Упорядоченный тестовый текстовый файл, в котором перечислены ваши тесты в порядке их выполнения. Если вы измените имя метода, вы должны исправить файл.
- Порядок выполнения теста соблюдается внутри класса. Вы не можете указать, какой класс выполняет свои тесты первым.
- Упорядоченный тестовый файл связан с конфигурацией, Debug или Release
- У вас может быть несколько файлов упорядоченных тестов, но данный метод не может повторяться в разных файлах упорядоченных тестов. Таким образом, вы не можете иметь один заказанный файл для отладки, а другой для выпуска.
Другие предложения в этой теме интересны, но вы теряете возможность следить за ходом тестирования в Test Explorer.
Вы остаетесь с решением, которое пурист посоветует против, но на самом деле это решение работает: сортировка по порядку декларации.
MSTest executor использует взаимодействие, которое управляет получением порядка объявления, и этот прием будет работать до тех пор, пока Microsoft не изменит код тестового исполнителя.
Это означает, что тестовый метод, который объявлен в первую очередь, выполняется перед тем, который объявлен во втором месте, и т. Д.
Чтобы упростить вашу жизнь, порядок декларирования должен соответствовать алфавитному порядку, указанному в Test Explorer.
- A010_FirstTest
- A020_SecondTest
- так далее
- A100_TenthTest
Я настоятельно рекомендую некоторые старые и проверенные правила:
- используйте шаг 10, потому что вам нужно будет вставить метод тестирования позже
- Избегайте необходимости перенумеровывать свои тесты, используя щедрый шаг между номерами тестов
- используйте 3 цифры для нумерации тестов, если вы выполняете более 10 тестов
- используйте 4 цифры для нумерации тестов, если вы выполняете более 100 тестов
ОЧЕНЬ ВАЖНО
Чтобы выполнить тесты в порядке объявления, вы должны использовать Run All в Test Explorer.
Скажем, у вас есть 3 тестовых класса (в моем случае тесты для Chrome, Firefox и Edge). Если вы выбираете данный класс и щелкаете правой кнопкой мыши по Выполнить выбранные тесты, он обычно начинается с выполнения метода, объявленного в последнем месте.
Опять же, как я уже говорил, заявленный заказ и заявленный заказ должны совпадать, иначе у вас будут большие проблемы в кратчайшие сроки.
Я не вижу никого, кто бы упоминал метод атрибута ClassInitialize. Атрибуты довольно просты.
Создайте методы, помеченные атрибутом [ClassInitialize()] или [TestInitialize()], чтобы подготовить аспекты среды, в которой будет выполняться ваш модульный тест. Цель этого - установить известное состояние для запуска вашего модульного теста. Например, вы можете использовать метод [ClassInitialize()] или [TestInitialize()] для копирования, изменения или создания определенных файлов данных, которые будет использовать ваш тест.
Создайте методы, помеченные атрибутом [ClassCleanup()] или [TestCleanUp{}], чтобы вернуть среду в известное состояние после выполнения теста. Это может означать удаление файлов в папках или возвращение базы данных в известное состояние. Примером этого является сброс базы данных инвентаризации в исходное состояние после тестирования метода, который используется в приложении для ввода заказа.
[ClassInitialize()] Используйте ClassInitialize для запуска кода перед запуском первого теста в классе.
[ClassCleanUp ()] Используйте Class Cleanup для запуска кода после выполнения всех тестов в классе.
[TestInitialize()] Используйте TestInitialize для запуска кода перед запуском каждого теста.
[TestCleanUp ()] Используйте TestCleanup для запуска кода после каждого теста.
Поскольку вы уже упомянули функциональность Ordered Test, предоставляемую средой тестирования Visual Studio, я буду игнорировать это. Вы также, кажется, знаете, что то, что вы пытаетесь выполнить, чтобы протестировать этот статический класс, является "плохой идеей", поэтому я проигнорирую это.
Вместо этого давайте сосредоточимся на том, как вы на самом деле сможете гарантировать, что ваши тесты выполняются в том порядке, который вы хотите. Один вариант (предоставленный @gaog) - это "один метод тестирования, множество функций тестирования", вызывающий ваши функции тестирования в том порядке, в котором вы хотите, из одной функции, отмеченной знаком TestMethod
приписывать. Это самый простой способ, и единственным недостатком является то, что первая неудачная тестовая функция не позволит выполнить ни одну из оставшихся тестовых функций.
С вашим описанием ситуации, это решение, которое я бы предложил вам использовать.
Если выделенная жирным шрифтом проблема для вас, вы можете выполнить упорядоченное выполнение изолированных тестов, используя встроенную функциональность тестов, управляемых данными. Он более сложный и немного грязный, но он выполняет свою работу.
Короче говоря, вы определяете источник данных (например, CSV-файл или таблицу базы данных), который контролирует порядок, в котором вы должны выполнять свои тесты, и имена функций, которые фактически содержат функциональные возможности теста. Затем вы подключаете этот источник данных к тесту, управляемому данными, используете опцию последовательного чтения и выполняете свои функции в нужном вам порядке в качестве отдельных тестов.
[TestClass]
public class OrderedTests
{
public TestContext TestContext { get; set; }
private const string _OrderedTestFilename = "TestList.csv";
[TestMethod]
[DeploymentItem(_OrderedTestFilename)]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", _OrderedTestFilename, _OrderedTestFilename, DataAccessMethod.Sequential)]
public void OrderedTests()
{
var methodName = (string)TestContext.DataRow[0];
var method = GetType().GetMethod(methodName);
method.Invoke(this, new object[] { });
}
public void Method_01()
{
Assert.IsTrue(true);
}
public void Method_02()
{
Assert.IsTrue(false);
}
public void Method_03()
{
Assert.IsTrue(true);
}
}
В моем примере у меня есть вспомогательный файл с именем TestList.csv, который копируется в вывод. Это выглядит так:
TestName
Method_01
Method_02
Method_03
Ваши тесты будут выполняться в указанном вами порядке и в обычной изоляции тестов (т. Е. Если один из них не пройден, остальные все же будут выполнены, но с использованием статических классов).
Вышесказанное на самом деле является лишь основной идеей, если бы я использовал его в работе, я бы динамически генерировал имена тестовых функций и их порядок до запуска теста. Возможно, используя найденный вами PriorityAttribute и некоторый простой код отражения, чтобы извлечь методы тестирования в классе и упорядочить их соответствующим образом, затем запишите этот порядок в источник данных.
Как уже отмечали комментаторы, наличие тестов, зависящих от других тестов, указывает на недостаток конструкции. Тем не менее, есть способы достичь этого. Как уже было сказано в ранее заданном вопросе, вы можете создавать упорядоченные модульные тесты, которые в основном представляют собой один тестовый контейнер, обеспечивающий последовательность тестов.
Вот руководство по MSDN: http://msdn.microsoft.com/en-us/library/ms182631.aspx
Вот класс, который можно использовать для установки и запуска упорядоченных тестов независимо от среды MS Ordered Tests по любой причине - например, не нужно настраивать аргументы mstest.exe на машине сборки или смешивать упорядоченные с неупорядоченными в классе.
Исходная среда тестирования видит список упорядоченных тестов только как один тест, поэтому любая init/cleanup, такая как [TestInitalize()] Init(), вызывается только до и после всего набора.
Использование:
[TestMethod] // place only on the list--not the individuals
public void OrderedStepsTest()
{
OrderedTest.Run(TestContext, new List<OrderedTest>
{
new OrderedTest ( T10_Reset_Database, false ),
new OrderedTest ( T20_LoginUser1, false ),
new OrderedTest ( T30_DoLoginUser1Task1, true ), // continue on failure
new OrderedTest ( T40_DoLoginUser1Task2, true ), // continue on failure
// ...
});
}
Реализация:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace UnitTests.Utility
{
/// <summary>
/// Define and Run a list of ordered tests.
/// 2016/08/25: Posted to SO by crokusek
/// </summary>
public class OrderedTest
{
/// <summary>Test Method to run</summary>
public Action TestMethod { get; private set; }
/// <summary>Flag indicating whether testing should continue with the next test if the current one fails</summary>
public bool ContinueOnFailure { get; private set; }
/// <summary>Any Exception thrown by the test</summary>
public Exception ExceptionResult;
/// <summary>
/// Constructor
/// </summary>
/// <param name="testMethod"></param>
/// <param name="continueOnFailure">True to continue with the next test if this test fails</param>
public OrderedTest(Action testMethod, bool continueOnFailure = false)
{
TestMethod = testMethod;
ContinueOnFailure = continueOnFailure;
}
/// <summary>
/// Run the test saving any exception within ExceptionResult
/// Throw to the caller only if ContinueOnFailure == false
/// </summary>
/// <param name="testContextOpt"></param>
public void Run()
{
try
{
TestMethod();
}
catch (Exception ex)
{
ExceptionResult = ex;
throw;
}
}
/// <summary>
/// Run a list of OrderedTest's
/// </summary>
static public void Run(TestContext testContext, List<OrderedTest> tests)
{
Stopwatch overallStopWatch = new Stopwatch();
overallStopWatch.Start();
List<Exception> exceptions = new List<Exception>();
int testsAttempted = 0;
for (int i = 0; i < tests.Count; i++)
{
OrderedTest test = tests[i];
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
testContext.WriteLine("Starting ordered test step ({0} of {1}) '{2}' at {3}...\n",
i + 1,
tests.Count,
test.TestMethod.Method,
DateTime.Now.ToString("G"));
try
{
testsAttempted++;
test.Run();
}
catch
{
if (!test.ContinueOnFailure)
break;
}
finally
{
Exception testEx = test.ExceptionResult;
if (testEx != null) // capture any "continue on fail" exception
exceptions.Add(testEx);
testContext.WriteLine("\n{0} ordered test step {1} of {2} '{3}' in {4} at {5}{6}\n",
testEx != null ? "Error: Failed" : "Successfully completed",
i + 1,
tests.Count,
test.TestMethod.Method,
stopWatch.ElapsedMilliseconds > 1000
? (stopWatch.ElapsedMilliseconds * .001) + "s"
: stopWatch.ElapsedMilliseconds + "ms",
DateTime.Now.ToString("G"),
testEx != null
? "\nException: " + testEx.Message +
"\nStackTrace: " + testEx.StackTrace +
"\nContinueOnFailure: " + test.ContinueOnFailure
: "");
}
}
testContext.WriteLine("Completed running {0} of {1} ordered tests with a total of {2} error(s) at {3} in {4}",
testsAttempted,
tests.Count,
exceptions.Count,
DateTime.Now.ToString("G"),
overallStopWatch.ElapsedMilliseconds > 1000
? (overallStopWatch.ElapsedMilliseconds * .001) + "s"
: overallStopWatch.ElapsedMilliseconds + "ms");
if (exceptions.Any())
{
// Test Explorer prints better msgs with this hierarchy rather than using 1 AggregateException().
throw new Exception(String.Join("; ", exceptions.Select(e => e.Message), new AggregateException(exceptions)));
}
}
}
}
Если вы можете использовать фреймворк NUnit , это возможно с помощью
[Order]
атрибут.
см. документ MS для заказа тестов с использованием NUnit:
using NUnit.Framework;
namespace NUnit.Project
{
public class ByOrder
{
public static bool Test1Called;
public static bool Test2ACalled;
public static bool Test2BCalled;
public static bool Test3Called;
[Test, Order(5)]
public void Test1()
{
Test3Called = true;
Assert.IsTrue(Test1Called);
Assert.IsFalse(Test2ACalled);
Assert.IsTrue(Test2BCalled);
}
[Test, Order(0)]
public void Test2B()
{
Test2BCalled = true;
Assert.IsTrue(Test1Called);
Assert.IsFalse(Test2ACalled);
Assert.IsFalse(Test3Called);
}
[Test]
public void Test2A()
{
Test2ACalled = true;
Assert.IsTrue(Test1Called);
Assert.IsTrue(Test2BCalled);
Assert.IsTrue(Test3Called);
}
[Test, Order(-5)]
public void Test3()
{
Test1Called = true;
Assert.IsFalse(Test2ACalled);
Assert.IsFalse(Test2BCalled);
Assert.IsFalse(Test3Called);
}
}
}
Я не буду обращаться к порядку испытаний, извините. Другие уже сделали это. Кроме того, если вы знаете о "заказанных тестах" - это ответ MS VS на проблему. Я знаю, что эти заказные тесты неинтересны. Но они думали, что это будет "это", и в MSTest больше ничего об этом нет.
Я пишу об одном из ваших предположений:
так как нет способа снести статический класс.
Если ваш статический класс не представляет какое-либо внешнее для всего процесса внешнее состояние для вашего кода (например, состояние неуправляемой нативной библиотеки DLL, которая вызывается остальной частью вашего кода), ваше предположение о том, что there is no way
неправда.
Если ваш статический класс ссылается на это, то извините, вы совершенно правы, остальная часть этого ответа не имеет значения. Тем не менее, поскольку вы этого не сказали, я предполагаю, что ваш код "управляем".
Подумай и проверь AppDomain
штуковина. Редко это нужно, но это как раз тот случай, когда вы, вероятно, хотели бы использовать их.
Вы можете создать новый AppDomain, создать там тест и запустить там метод теста. Статические данные, используемые управляемым кодом, будут изолированы там, и по завершении вы сможете выгрузить домен приложений, и все данные, включая статические, будут испарены. Затем следующий тест инициализирует другой домен приложения и так далее.
Это будет работать, если у вас нет внешнего состояния, которое вы должны отслеживать. Домены приложений изолируют только управляемую память. Любая нативная DLL будет по-прежнему загружаться для каждого процесса, и их состояние будет общим для всех доменов приложений.
Кроме того, создание / разрушение доменов приложений, в общем, замедлит тестирование. Кроме того, у вас могут быть проблемы с разрешением сборки в дочернем домене приложения, но они могут быть решены с помощью разумного количества повторно используемого кода.
Кроме того, у вас могут возникнуть небольшие проблемы с передачей тестовых данных в дочерний домен приложения и обратно. Переданные объекты либо должны быть каким-то образом сериализуемыми, либо MarshalByRef
и т. д. Говорящий междоменный домен почти похож на IPC.
Однако, будьте осторожны, разговор будет на 100% управляемым. Если вы проявите особую осторожность и добавите немного работы в настройку AppDomain, вы сможете даже передать делегатов и запустить их в целевом домене. Затем, вместо того, чтобы сделать какую-то сложную междоменную настройку, вы можете свернуть свои тесты с чем-то вроде:
void testmethod()
{
TestAppDomainHelper.Run( () =>
{
// your test code
});
}
или даже
[IsolatedAppDomain]
void testmethod()
{
// your test code
}
если ваш тестовый фреймворк поддерживает создание таких оболочек / расширений. После некоторых первоначальных исследований и работ, их использование почти тривиально.
Протестировано в VS2019. Вы можете использовать атрибут TestPropertyClass для определения порядка выполнения (или любой другой классификации, которую вы хотите). Затем используйте кнопку «Группировать по» в обозревателе тестов, чтобы отсортировать по атрибуту («Rasgos» на испанском языке) и протестировать.
Больше информации здесь.
[TestMethod]
[TestProperty("ExecutionOrder", "1")]
public void oneMethod(){ Console.WriteLine("First method to test.") }
[TestMethod]
[TestProperty("ExecutionOrder", "2")]
public void anotherMethod() { Console.WriteLine("Second method to test.") }
Я вижу, что этой теме почти 6 лет, и теперь у нас есть новая версия Visual Studio, но я все равно отвечу. У меня была проблема с этим порядком в Visual Studio 19, и я понял это, добавив заглавную букву (вы также можете добавить маленькую букву) перед именем вашего метода и в алфавитном порядке, например:
[TestMethod]
public void AName1()
{}
[TestMethod]
public void BName2()
{}
И так далее. Я знаю, что это не выглядит привлекательно, но похоже, что Visual сортирует ваши тесты в обозревателе тестов в алфавитном порядке, независимо от того, как вы пишете это в своем коде. В этом случае плейлист у меня не работал.
Надеюсь, это поможет.
я использую толькоMsTest
.
Вот что я делаю в таких случаях...
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class TestOrderAttribute : TestPropertyAttribute
{
public TestOrderAttribute([Range(1, int.MaxValue)] int order) : base("ExecutionOrder", $"{order}")
{
}
}
И вы просто можете сделать что-то вроде:
[TestMethod, Owner(TC.Bobo), TestOrder(5)]
public async Task GetUserByIdAsync()
{
_authenticationService = GetRequiredService<IAuthenticationService>();
ErrorOr<UserResponse> result = await _authenticationService.GetUserById(1);
AssertInScope(() =>
{
result.IsError.Should().BeFalse();
result.Errors.Should().BeEmpty();
result.Value.Should().NotBeNull();
});
}
Обозреватель тестов будет выглядеть так:
Из
Для NUnit: порядок по приоритету
[Test, Order(5)]
Для MSTest: в MSTest тесты автоматически упорядочиваются по имени теста.
Решение 1. Имена методов с префиксами (например, T001[MethodName], T002[MethodName])
Решение 2. Использование пользовательских списков воспроизведения
Решение 3. Сделать все методы закрытыми и вызывать все методы из одного метода по порядку.
Решение 4. Из документа Microsoftдокумента Microsoft
namespace MSTest.Project
{
[TestClass]
public class ByAlphabeticalOrder
{
public static bool Test1Called;
public static bool Test2Called;
public static bool Test3Called;
[TestMethod]
public void Test2()
{
Test2Called = true;
Assert.IsTrue(Test1Called);
Assert.IsFalse(Test3Called);
}
[TestMethod]
public void Test1()
{
Test1Called = true;
Assert.IsFalse(Test2Called);
Assert.IsFalse(Test3Called);
}
[TestMethod]
public void Test3()
{
Test3Called = true;
Assert.IsTrue(Test1Called);
Assert.IsTrue(Test2Called);
}
}
}
они просто не могут быть запущены в случайном порядке, так как нет никакого способа разрушить статический класс
Вы можете называть пространства имен и классы в алфавитном порядке. например.:
- MyApp.Test.Stage01_Setup.Step01_BuildDB
- MyApp.Test.Stage01_Setup.Step02_UpgradeDB
- MyApp.Test.Stage02_Domain.Step01_TestMyStaff
- MyApp.Test.Этап03_ Интеграция.Step01_TestMyStaff
где MyApp.Test.Stage01_Setup
это пространство имен и Step01_BuildDB
это имя класса.