Керування порядком виконання модульних тестів у Visual Studio


80

Гаразд, я закінчив пошук хорошої інформації щодо цього. У мене є серія Unit Tests, які викликають статичний клас, який після ініціалізації встановлює властивості, які неможливо (або я не хочу) змінювати.

Моя проблема полягає в тому, що я не можу забезпечити встановлений порядок для запуску тестів. Якби я міг, я міг запустити їх таким чином, щоб статичні властивості були встановлені надійним способом, і я міг би на них стверджувати, але, на жаль, Microsoft.VisualStudio.TestTools.UnitTesting фреймворк просто запускає їх у, здавалося б, випадковому порядку. .

Отже, я знайшов цей http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx, який говорить у розділі "Зауваження" "Цей атрибут не використовується тестовою системою. Він надається користувачеві для власних цілей. " А? Яка тоді користь? Чи очікують вони, що я напишу власну обгортку для тестування, щоб скористатися цим казковим атрибутом (про який я міг би легко написати сам, якби хотів піти на такий рівень зусиль ...)

Отже, вистачає розмови; Підсумок, чи є спосіб контролювати порядок запуску моїх модульних тестів?

[TestMethod]
[Priority(0)]

тощо, здається, НЕ працює, що має сенс, оскільки Microsoft каже, що не буде.

Також, будь ласка, жодних коментарів щодо "порушення ізоляції". TestClass виділяє те, що я тестую, а не окремі TestMethods. Незважаючи на це, кожен тест можна запустити незалежно окремо, їх просто неможливо запустити разом у випадковому порядку, оскільки немає можливості зруйнувати статичний клас.

О, я також знаю про "Замовлений тест".


3
Чи можете ви пояснити, чому ваші тести залежать від замовлення? Я вважаю, що тести по суті поступово тестують статичний клас?
Тодд Боулз,

13
Ваші модульні тести не повинні залежати від замовлення. Цей статичний клас з мертвим мозком робить ваш код неперевіряним. Якщо ви не можете "зруйнувати", то це не єдина проблема, яка виникає у вас при модульному тестуванні.
Джон Сондерс,

4
Статичний клас не є моїм - так, він повинен був бути записаний як синглтон. На жаль, іноді вам просто доводиться грати в (дерьмові) карти, які вам роздають. Я використовую підробки якомога більше, щоб вилучити його з рівняння, але не можу усунути.
iGanja

3
Ви не можете кожного разу скинути контекст статичного класу в TestInitialize? Одним з основних принципів модульного тестування є незалежність, не намагайтеся контролювати порядок виконання. Ви не "порушуєте ізоляцію", а порушуєте основні принципи, завдяки яким тест є одиничним тестом.
Pierre-Luc Pineault

7
Причин для запуску замовлених тестів може бути багато. І коли потрібно запустити замовлені тести, йому справді не потрібні коментарі, які насправді не допомагають, як-от сказати, що не слід цього робити і т. Д. Я ввічливо прошу, щоб наступного разу пропустити такий коментар і намагайся бути корисним. Або просто взагалі пропустити нитку. Додам свою відповідь за хвилину.
Tiago Freitas Leal

Відповіді:


55

Об’єднайте свої тести в один гігантський тест. Щоб зробити метод тестування більш читабельним, ви можете зробити щось на зразок

[TestMethod]
public void MyIntegratonTestLikeUnitTest()
{
    AssertScenarioA();

    AssertScenarioB();

    ....
}

private void AssertScenarioA()
{
     // Assert
}

private void AssertScenarioB()
{
     // Assert
}

Насправді питання, яке ви маєте, наводить на думку, що ви, мабуть, повинні покращити перевірку впровадження.


2
Об'єднання тестів є справедливим підходом, але якщо перший метод тесту у списку не може підтвердити, жоден з інших не буде виконаний. Враховуючи залежність від порядку, властиву тестовій стратегії ОП, це може не становити проблеми.
Тодд Боулз,

1
Погоджено @ToddBowles, це може бути шлях. І звичайно, як ви вже сказали, з великим гігантським випробуванням із тонною тверджень ви втрачаєте деяку деталізацію, коли не вдається. +1
iGanja

Це може бути не найкращим рішенням (повторне врахування статичного класу), але це, звичайно, найпростіший у реалізації і змушує мене знову працювати над іншими речами.
iGanja

Дивіться мій коментар нижче щодо атрибута ClassInitialize, також я вважаю, що OrderedTests досить прості у впровадженні і є прийнятним для MS способом.
MattyMerrix

3
Це може спрацювати, але це перешкоджає меті модульного тестування. Ідея модульного тестування полягає в тому, щоб розбити частини на шматки для швидкого тестування, а не розбити їх разом,
Крістіан Фіндлі

115

Ви можете використовувати список відтворення

Клацніть правою кнопкою миші на тестовому методі -> Додати до списку відтворення -> Новий список відтворення

порядок виконання буде таким, як ви додасте їх до списку відтворення, але якщо ви хочете змінити його, у вас є файл

введіть тут опис зображення


20
Щойно спробував це у VS2015, і виявляється, що список відтворення не впливає на порядок виконання. Швидше, вони виконуються в тому порядку, в якому оголошені методи.
Jrd

28
@Jrd У Visual Studio 2015 все трохи змінилося. У Провіднику рішень клацніть правою кнопкою миші проект модульного тесту, натисніть Додати> Впорядкований тест. Це додає новий файл до проекту. Відкривши цей файл, ви можете натиснути на методи тестування у своєму проекті та додати їх 1 або більше разів до цього тесту.
Zee,

Дивіться мій коментар нижче щодо атрибута ClassInitialize, також я вважаю, що OrderedTests досить прості у впровадженні і є прийнятним для MS способом.
MattyMerrix

Чи можна викликати список відтворення через командний рядок, щоб я міг використовувати його на сервері збірки?
red888

1
@EA Вам слід було врахувати дату та час, на які автор відповів. Цікаво -> Він відповів 2013, Ви прокоментували 2015, а я прокоментував у 2017 році.
Чудово

16

Як ви вже мали знати, пуристи кажуть, що заборонено проводити замовлені тести. Це може бути правдою для модульних тестів. MSTest та інші модульні модульні тести використовуються для запуску чистого модульного тестування, але також тестів інтерфейсу користувача, повних тестів інтеграції. Можливо, нам не слід називати їх фреймворками Unit Test, або, можливо, слід використовувати їх відповідно до наших потреб. Це те, що і так роблять більшість людей.

Я запускаю VS2015 і ПОВИНЕН запускати тести в певному порядку, оскільки я запускаю тести інтерфейсу користувача (селен).

Пріоритет - взагалі нічого не робить Цей атрибут не використовується тестовою системою. Він надається користувачеві для власних цілей.

ordertest - він працює, але я не рекомендую його, оскільки:

  1. Замовлений тест - текстовий файл із переліком тестів у тому порядку, в якому їх слід виконувати. Якщо ви змінюєте назву методу, ви повинні виправити файл.
  2. Порядок виконання тесту дотримується всередині класу. Ви не можете замовити, який клас виконує свої тести першими.
  3. Замовлений тест файл пов'язана із зміною, або Debug або Release
  4. Ви можете мати кілька файлів упорядкованого тесту, але заданий метод не можна повторити в різних файлах упорядкованого тесту . Таким чином, ви не можете мати один замовлений файл тесту для налагодження, а інший - для випуску.

Інші пропозиції в цій темі цікаві, але ви втрачаєте можливість стежити за ходом тесту в Test Explorer.

Вам залишається рішення, яке пурист не порадить, але насправді це рішення, яке працює: сортуйте за порядком декларації .

Виконавець MSTest використовує взаємодію, якій вдається отримати порядок декларації, і цей фокус працюватиме, поки Microsoft не змінить код виконавця тесту.

Це означає, що метод тестування, який оголошений в першу чергу, виконується перед тим, який оголошений в другому місці, тощо.

Щоб полегшити вам життя, порядок декларації повинен відповідати алфавітному порядку, який вказаний у Провіднику тестів.

  • A010_Перший тест
  • A020_Другий тест
  • тощо
  • A100_T десятий тест

Я настійно рекомендую кілька старих і перевірених правил:

  • скористайтесь кроком 10, оскільки згодом вам потрібно буде вставити метод тестування
  • уникніть необхідності перенумерувати тести, використовуючи щедрий крок між номерами тестів
  • використовуйте 3 цифри для нумерації тестів, якщо ви проводите більше 10 тестів
  • використовуйте 4 цифри для нумерації тестів, якщо ви проводите більше 100 тестів

ДУЖЕ ВАЖЛИВО

Для того, щоб виконати тести за наказом декларації, ви повинні використовувати Виконати всі у Провіднику тестів.

Скажімо, у вас є 3 тестові класи (у моєму випадку тести для Chrome, Firefox та Edge). Якщо ви вибрали даний клас і клацніть правою кнопкою миші Виконати вибрані тести він зазвичай починається з виконання методу, заявленого в останньому місці.

Знову ж таки, як я вже говорив раніше, заявлений порядок та замовлення в списку повинні збігатися, інакше за найближчий час у вас виникнуть великі проблеми.


Виконання функціонального набору тестів, які записуються в базу даних. Насправді байдуже, чи справді вони бігають по порядку. Але якщо вони трапляються, останній - це найповніший тест. Будьте приємні, якщо це було натякнуто бігти останнім. Так. Я теж глузую. Одиничні тести зберігаються окремо від функціональних тестів. FT запускаються лише вручну проти локального / тестового середовища як частина мого основного запуску розгортання. Я не запускаю функціональні тести на незначні зміни та виправлення. Це спрацювало чудово!
TamusJRoyce

Я використовую VS2019, і тести виконуються в алфавітному порядку без будь-якого мого втручання. Це саме те, що мені потрібно. Я проголосував за вашу відповідь через вашу пораду називати тести десятками, щоб було дещо вставити пізніше, не перейменовуючи все, що вже зроблено.
MsTapp

12

Я не бачу, щоб хтось згадував ClassInitializeметод атрибутів. Атрибути досить прямі.

Створіть методи, позначені атрибутом [ClassInitialize()]або, [TestInitialize()]для підготовки аспектів середовища, в якому буде виконуватися ваш модульний тест. Метою цього є встановлення відомого стану для запуску вашого модульного тесту. Наприклад, ви можете використовувати метод [ClassInitialize()]або [TestInitialize()]для копіювання, зміни або створення певних файлів даних, які буде використовувати ваш тест.

Створіть методи, позначені атрибутом [ClassCleanup()]або, [TestCleanUp{}]щоб повернути середовище у відомий стан після запуску тесту. Це може означати видалення файлів у папках або повернення бази даних у відомий стан. Прикладом цього є скидання бази даних запасів у початковий стан після тестування методу, який використовується у програмі введення замовлення.

  • [ClassInitialize()]Використовуйте ClassInitializeдля запуску коду перед запуском першого тесту в класі.

  • [ClassCleanUp()]Використовуйте ClassCleanupдля запуску коду після запуску всіх тестів у класі.

  • [TestInitialize()]Використовуйте TestInitializeдля запуску коду перед запуском кожного тесту.

  • [TestCleanUp()]Використовуйте TestCleanupдля запуску коду після запуску кожного тесту.


4
Існують також AssemblyInitialize та AssemblyCleanup, які запускаються відповідно на початку та в кінці кожного тестового запуску.
MattyMerrix

6

Оскільки ви вже згадали про функцію Замовленого тесту, яку надає фреймворк 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, який ви знайшли, та якийсь простий код відображення для вилучення методів тестування у класі та відповідного їх упорядкування, а потім напишіть це замовлення до джерела даних.


4

Ось клас, який може бути використаний для встановлення та запуску впорядкованих тестів, незалежно від середовища замовлених тестів MS, з будь-якої причини - наприклад, не потрібно коригувати аргументи 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)));
            }
        }
    }
}

2

Я не буду звертатися до порядку тестів, вибачте. Інші це вже робили. Крім того, якщо ви знаєте про "замовлені тести" - ну, це відповідь MS VS на проблему. Я знаю, що ці замовлені тести не приносять задоволення. Але вони думали, що це буде «це», і в MSTest насправді про це більше нічого немає.

Я пишу про одне з ваших припущень:

оскільки немає способу зруйнувати статичний клас.

Якщо ваш статичний клас не представляє якийсь зовнішній стан вашого коду для всього процесу (наприклад, стан некерованої власної бібліотеки DLL, який P / викликається рештою коду), ваше припущення there is no wayне відповідає дійсності.

Якщо ваш статичний клас посилається на це, то, вибачте, ви абсолютно праві, решта цієї відповіді не має значення. Тим не менше, оскільки ви цього не сказали, я припускаю, що ваш код "керований".

Подумайте і перевірте AppDomainріч. Рідко це потрібно, але це саме той випадок, коли ви, мабуть, хотіли б ними скористатися.

Ви можете створити новий AppDomain, створити там тест і створити там метод тесту. Статичні дані, що використовуються керованим кодом, будуть там ізольовані, і після завершення ви зможете вивантажити AppDomain, і всі дані, включаючи статику, випаруються. Потім наступний тест ініціалізує інший домен програми тощо.

Це буде працювати, якщо у вас немає зовнішнього стану, який ви повинні відстежувати. AppDomains ізолюють лише керовану пам’ять. Будь-яка рідна бібліотека DLL все одно буде завантажуватися за процес, і їх стан буде спільним для всіх AppDomains.

Крім того, створення / руйнування додаткових доменів, добре, сповільнить тести. Крім того, у вас можуть бути проблеми з роздільною здатністю збірки в дочірньому домені програми, але їх можна вирішити за допомогою розумної кількості багаторазового коду.

Крім того, у вас можуть виникнути невеликі проблеми з передачею даних тесту дочірньому AppDomain і назад від нього. Передані об'єкти повинні бути якимось чином серіалізуються, або бути, MarshalByRefабо т. Д. Говоряча міждомен майже схожа на IPC.

Однак подбайте тут, це буде 100% керована розмова. Якщо ви проявите додаткову обережність і додасте трохи роботи до налаштування AppDomain, ви зможете навіть передавати делегатів і запускати їх у цільовому домені. Потім, замість того, щоб робити волохаті міждоменні налаштування, ви можете обернути свої тести чимось на зразок:

void testmethod()
{
    TestAppDomainHelper.Run( () =>
    {
        // your test code
    });
}

або навіть

[IsolatedAppDomain]
void testmethod()
{
    // your test code
}

якщо ваш тестовий фреймворк підтримує створення таких обгортки / розширень. Після деяких початкових досліджень та робіт їх використання майже тривіально.


Я розгляну це. Можливо, не сьогодні. :)
iGanja

0

Я бачу, що цій темі майже 6 років, і тепер у нас є нова версія Visual studio, але я все одно відповім. У мене була проблема із замовленням у Visual Studio 19, і я зрозумів це, додавши велику літеру (ви також можете додати маленьку літеру) перед назвою вашого методу та в алфавітному порядку так:

[TestMethod]
        public void AName1()
        {}
[TestMethod]
        public void BName2()
        {}

І так далі. Я знаю, що це не виглядає привабливо, але, схоже, Visual сортує ваші тести в провіднику тестів в алфавітному порядку, неважливо, як ви це пишете у своєму коді. У цьому випадку плейлист для мене не працював.

Сподіваюся, що це допоможе.


Буде непогано, якщо це спрацює. Мені доведеться спробувати. Дякую!
iGanja

1
Здається, це працює у VS 2019, але мені це не подобається. Хто хоче перейменувати всі свої методи тестування таким чином? Це негарно і неінтуїтивно. Їм дійсно потрібно надати спосіб встановити порядок виконання тесту.
Майк Лоуері

-2

їх просто неможливо запустити разом у випадковому порядку, оскільки немає можливості зруйнувати статичний клас

Ви можете називати простори імен та класи в алфавітному порядку. напр .:

  • MyApp.Test. Stage01 _Setup. Крок01 _BuildDB
  • MyApp.Test. Stage01 _Setup. Крок 02 _UpgradeDB
  • MyApp.Test. Stage02 _Домен. Крок01 _TestMyStaff
  • MyApp.Test. Етап03 _Інтеграція. Крок01 _TestMyStaff

де MyApp.Test.Stage01_Setup- простір імен, а Step01_BuildDB- ім’я класу.


Порядок виконання виконується за методом декларації методу, а не за алфавітом.
Tiago Freitas Leal

2
@TiagoFreitasLeal Неправда у VS 2019! Microsoft постійно змінює спосіб роботи.
Mike Lowery
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.