Можно ли создать собственный таймер TestContext для UnitTest/LoadTest в Visual Studio?

У некоторых из моих UnitTest есть Sleep, который определен в цикле. Я хочу профилировать не только каждую итерацию теста, но и общее время для всех итераций, чтобы показать любое нелинейное масштабирование. Например, если я профилирую «Общие», в него входит время сна. Я могу использовать Stopwatch Start/Stop, чтобы он включал только doAction(). Однако я не могу записать результаты секундомера в результаты TestContext.

[TestMethod]
    public void TestMethod1()
    {
        TestContext.BeginTimer("Overall");
        for (int i = 0; i < 5; i++)
        {
            TestContext.BeginTimer("Per");
            doAction();
            TestContext.EndTimer("Per");
            Sleep(1000);
        }
        TestContext.EndTimer("Overall");
    }

Кажется, что TestContext может быть унаследован и переопределен. Однако я не вижу примеров того, как записать это обратно в хранилище транзакций.

Есть ли реализация для этого, на которую я могу сослаться, или другая идея. Я хотел бы видеть это в том же отчете, который Visual Studio представляет для LoadTest. В противном случае я должен написать свой собственный отчет.

Кроме того, я попытался пронюхать SQL, который записывает их в базу данных LoadTest, но не смог понять, как это сделать. Должен быть вызов SPROC, но я думаю, что это все данные в конце теста.


person esac    schedule 03.06.2013    source источник
comment
Я поместил фиксированный сон в код примера, но на самом деле это случайный сон от 1 до 24 секунд, что совсем немного :(   -  person esac    schedule 03.06.2013
comment
Сон должен быть случайным. Фиксированный сон будет означать, что у вас есть волна активности/бездействия при тестировании с несколькими пользователями, что не является точным.   -  person esac    schedule 03.06.2013
comment
Мне бы не нужно. Перед циклом я мог бы сделать Stopwatch.StartNew(); перед doAction() я бы просто вызвал Start(), а перед спящим вызовом Stop(). Если я не сделаю сброс, он будет постепенно добавлять время к секундомеру, не включая Sleep(). Это время, которое я хотел бы зарегистрировать.   -  person esac    schedule 04.06.2013


Ответы (3)


Ну у меня была аналогичная проблема. Я хотел сообщить о некоторых дополнительных данных/отчетах/счетчиках из моих тестов в конечном результате теста, как это делает Visual Studio, и я нашел решение.

Во-первых, это невозможно сделать так, как вы пытаетесь. Между нагрузочным тестом и модульным тестом, где существует TestContext, нет прямой связи.

Во-вторых, вы должны понимать, как визуальная студия создает отчеты. Он собирает данные со счетчиков производительности . ОС. Вы можете редактировать эти счетчики, удалять ненужные и добавлять нужные.

Как редактировать счетчики

Конфигурация нагрузочного теста состоит из двух основных разделов, касающихся счетчиков. Это:

  • Counter Sets. Это наборы счетчиков, например agent, который добавляется по умолчанию. Если вы откроете этот набор счетчиков, вы увидите, что он собирает такие счетчики, как память, процессор, физический диск и т. д. Итак, в конце теста вы можете увидеть все эти данные со всех ваших агентов. Если вы хотите добавить больше счетчиков в этот набор счетчиков, вы можете дважды щелкнуть по нему (в редакторе нагрузочных тестов, см. рисунок ниже) и выбрать Add Counters. Это откроет окно со всеми счетчиками вашей системы и выберите те, которые вы хотите.

  • Counter Set Mappings. Здесь вы связываете наборы счетчиков с вашими машинами. По умолчанию [CONTROLLER MACHINE] и [AGENT MACHINES] добавляются с некоторыми наборами счетчиков по умолчанию. Это означает, что все счетчики, содержащиеся в наборах счетчиков, сопоставленных с [CONTROLLER MACHINE], будут собраны с вашего контроллера. То же самое относится ко всем вашим агентам.

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

Вы можете добавить больше наборов счетчиков и больше машин. Щелкнув правой кнопкой мыши на Counter Set Mappings --> Manage Counter Sets..., откроется новое окно, как показано ниже:

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

Как видите, я добавил дополнительную машину с именем db_1. Это имя компьютера машины, и он должен находиться в одном домене с контроллером, чтобы иметь к нему доступ и собирать счетчики. Я также пометил его как database server и выбрал набор счетчиков sql (по умолчанию для счетчиков sql, но вы можете отредактировать его и добавить любой счетчик, который хотите). Теперь каждый раз, когда выполняется этот нагрузочный тест, контроллер будет обращаться к машине с именем компьютера db_1 и собирать данные, которые будут представлены в окончательных результатах теста.


Теперь кодовая часть

Хорошо, после этого (большого) вступления пришло время посмотреть, как добавить ваши данные в окончательные результаты теста. Для этого необходимо создать собственные пользовательские счетчики производительности. Это означает, что новая категория счетчика производительности должны быть созданы машины, необходимые для сбора этих данных. В вашем случае во всех ваших агентах, потому что именно здесь выполняются UnitTests.

После того, как вы создали счетчики в агентах, вы можете отредактировать набор счетчиков Agents, как показано выше, и выбрать дополнительные настраиваемые счетчики.

Вот пример кода о том, как это сделать.

Сначала создайте счетчики производительности для всех ваших агентов. Запустите этот код только один раз на каждом компьютере с агентом (или вы можете добавить его в плагин загрузки теста):

void CreateCounter() 
{
    if (PerformanceCounterCategory.Exists("MyCounters"))
    {
        PerformanceCounterCategory.Delete("MyCounters");
    }

    //Create the Counters collection and add your custom counters 
    CounterCreationDataCollection counters = new CounterCreationDataCollection();
    // The name of the counter is Delay
    counters.Add(new CounterCreationData("Delay", "Keeps the actual delay", PerformanceCounterType.AverageCount64));
    // .... Add the rest counters

    // Create the custom counter category
    PerformanceCounterCategory.Create("MyCounters", "Custom Performance Counters", PerformanceCounterCategoryType.MultiInstance, counters);
}

А вот код вашего теста:

[TestClass]
public class UnitTest1
{
    PerformanceCounter OverallDelay;
    PerformanceCounter PerDelay;

    [ClassInitialize]
    public static void ClassInitialize(TestContext TestContext)
    {
        // Create the instances of the counters for the current test
        // Initialize it here so it will created only once for this test class
        OverallDelay= new PerformanceCounter("MyCounters", "Delay", "Overall", false));
        PerDelay= new PerformanceCounter("MyCounters", "Delay", "Per", false));
        // .... Add the rest counters instances
    }

    [ClassCleanup]
    public void CleanUp()
    {
        // Reset the counters and remove the counter instances
        OverallDelay.RawValue = 0;
        OverallDelay.EndInit();
        OverallDelay.RemoveInstance();
        OverallDelay.Dispose();
        PerDelay.RawValue = 0;
        PerDelay.EndInit();
        PerDelay.RemoveInstance();
        PerDelay.Dispose();
    }

    [TestMethod]
    public void TestMethod1()
    {
         // Use stopwatch to keep track of the the delay
         Stopwatch overall = new Stopwatch();
         Stopwatch per = new Stopwatch();

         overall.Start();

         for (int i = 0; i < 5; i++)
         {
             per.Start();
             doAction();
             per.Stop();

             // Update the "Per" instance of the "Delay" counter for each doAction on every test
             PerDelay.Incerement(per.ElapsedMilliseconds);
             Sleep(1000);

             per.Reset();
         }

         overall.Stop();

         // Update the "Overall" instance of the "Delay" counter on every test
         OverallDelay.Incerement(overall.ElapsedMilliseconds);
     }
}

Теперь, когда ваши тесты будут выполняться, они будут сообщать счетчику свои данные. В конце нагрузочного теста вы сможете увидеть счетчик на каждой машине агента и добавить его на графики. Это будет сообщено со значениями MIN, MAX и AVG.

Вывод

  1. Я думаю (после нескольких месяцев исследований), что это единственный способ добавить пользовательские данные из ваших тестов в окончательный отчет о нагрузочном тесте.
  2. Это может показаться слишком сложным. Ну если вы понимаете суть то оптимизировать не сложно. Я обернул эту функциональность в класс, чтобы было проще инициализировать, обновлять и, в конце концов, управлять счетчиками.
  3. Это очень очень полезно. Теперь я могу видеть статистику своих тестов, что было бы невозможно со счетчиками по умолчанию. Например, когда веб-запрос к веб-службе терпит неудачу, я могу поймать ошибку и обновить соответствующий счетчик (например, тайм-аут, ServiceUnavailable, RequestRejected...).

Надеюсь, я помог. :)

person chaliasos    schedule 14.06.2013
comment
Мне нравится это решение. Я уже реализовал некоторые категории/счетчики производительности клиентов для своей SUT. Не думал расширять это до этого. Я хотел бы видеть его в той же области, что и таймеры TestContext, но не буду придираться. Любая идея, можно ли добавить счетчики в сводный отчет - это единственный недостаток, который я могу придумать для этого метода. - person esac; 15.06.2013
comment
Сводный отчет является фиксированным отчетом, и я не думаю, что его можно настроить. Тем не менее, я не использую его много. Действительно интересная особенность нагрузочного тестирования в Visual Studio — это отчеты в формате Excel. Вот где вам действительно нужны счетчики. Вы сможете сравнить разные версии вашей SUT, чтобы увидеть улучшения, как ваша SUT реагирует на разную нагрузку и так далее. Попробуйте, и вы узнаете, насколько мощно это можно сделать. - person chaliasos; 15.06.2013
comment
Кроме того, у меня есть еще один вопрос к вам. Я не понимаю, почему вы реализовали цикл. Я думаю, вы хотите реализовать сценарий, в котором пользователь выполняет doAction 5 раз с задержкой. Но это уже реализовано. Используйте функцию Think Times в своем нагрузочном тесте, чтобы каждый виртуальный пользователь ждал несколько секунд перед запуском другого теста. - person chaliasos; 15.06.2013
comment
Моделирование бизнес-процессов. Пользователь всегда будет создавать заказ и добавлять строки в заказ. Количество строк будет варьироваться, и «время обдумывания» между каждой строкой будет переменным. Представьте себе, что вы принимаете заказ по телефону, и человек на другом конце провода дает вам товар, вы его вводите, а затем он заикается: «Ну, эм, я думаю, я возьму», и в дело вступает пункт №2. линии выполняются в цикле, и, поскольку они различаются, их нельзя разделить. - person esac; 16.06.2013
comment
Хорошо, вы лучше меня знаете, что вы хотите проверить, как это реализовать. К вашему сведению, вы можете получить тот же результат, используя Test Mix Model = Based on sequential order и добавив пять раз UnitTest, например TestDoAction. Так будет более понятно, что вы тестируете. Потому что, если вам нужно добавить DoActionB после второго запуска цикла, вам придется отредактировать свой модульный тест. Наличие каждого действия в отдельных модульных тестах позволит вам комбинировать их любым способом в нагрузочном тесте без редактирования или добавления новых. :) - person chaliasos; 16.06.2013
comment
@Schaliasos Кстати, отличный пост, а что, если мне не нужен счетчик, а просто нравится атрибут сценария? Пример: когда мой TestMethod выполняется в сценарии загрузки, я хочу зарегистрировать тип среды, в которой он работал: например, QA/PreProd/Prod. Я бы заполнил этот атрибут либо в ClassInitialization, либо в самом TestMethod. Я хочу иметь возможность создавать отчеты и фильтровать их в Excel впоследствии. - person eetawil; 21.02.2018

Я не знаю, как бы вы добавили значение в TestContext и, следовательно, сохранили его с помощью этого механизма. Альтернативой может быть просто запись результатов синхронизации в виде текста в выходные потоки трассировки, отладки или консоли, чтобы они сохранялись в журнале выполнения теста. Чтобы увидеть эти выходные данные, необходимо учитывать три свойства Ведение журнала активных Настройки запуска. По умолчанию они сохраняют журналы только для первых 200 неудачных тестов. Если задать для параметра Частота сохранения журналов завершенных тестов значение 1, журналы всех тестов будут сохраняться до тех пор, пока не будет достигнуто Максимальное количество журналов тестов. Шаги показаны более подробно в: http://blogs.msdn.com/b/billbar/archive/2009/06/09/vsts-2010-load-test-feature-saving-test-logs.aspx

Недостатком этого подхода является то, что файлы журналов можно просмотреть в Visual Studio только по одному, щелкнув ссылку Журнал тестирования в одном из окон результатов. Я пытался найти способ извлечения журналов веб-тестирования из базы данных результатов тестов SQL вместо того, чтобы щелкать ссылки для каждого журнала в Visual Studio. Я считаю, что журналы модульных тестов хранятся таким же образом. Я описал эту проблему и то, что мне удалось сделать до сих пор, в https://stackoverflow.com/questions/16914487/how-do-i-extract-test-logs-from-visual-studios-load-результатытеста

Обновлять. Я считаю, что то, что задано в вопросе, не может быть предоставлено API-интерфейсами, доступными непосредственно в среде нагрузочного тестирования Visual Studio. Адаптеры данных и диагностики могут быть написаны для тестов веб-производительности и, возможно, также для модульных тестов. С помощью такого кода адаптера можно записывать данные из приложения или набора тестов и записывать их в результаты теста. Существует несколько блогов Microsoft и страниц MSDN о написании адаптеров данных и диагностики.

person AdrianHHH    schedule 11.06.2013
comment
Это не будет сообщать об этом как Min/Max/Avg - только как время на тест. Мне пришлось бы иметь его в совокупной форме, и это сделало бы его очень ручным процессом. - person esac; 11.06.2013
comment
Комментарий к вашему обновлению - если это невозможно, то как это делает LoadTest? Нельзя ли эмулировать тот же оператор SPROC/SQL, который использует Visual Studio? Думаю, мне все равно, есть ли «официальный» API, просто способ сделать это. - person esac; 12.06.2013
comment
На мой взгляд, Load Test справляется, потому что это базовая функция, встроенная в программу, а не через общедоступный API. Есть много улучшений, которые было бы неплохо иметь в Visual Studio. Вы можете попробовать запросить свои идеи на visualstudio.uservoice.com/forums/121579-visual-studio. - person AdrianHHH; 13.06.2013
comment
TestContext может быть получен из -- чего я не знаю, так это SQL для записи в базу данных. Так что технически я мог бы использовать сочетание общедоступного и частного API, что мне не мешает karlz.net/blog/index.php/2012/11/17/ - person esac; 14.06.2013

Самый простой способ - это оригинальный подход ОП, просто кажется, что есть некоторые ошибки, с которыми я столкнулся, и другие, похоже, тоже. Во-первых, по какой-то причине TestContext.BeginTimer(string); не всегда существует, см. это для доказательства, но, похоже, без решения. Но есть еще одна проблема неправильного создания и использования свойства.

  1. Если у вас нет свойства для хранения TestContext и вы попытаетесь использовать TestContext.BeginTimer();, вы получите сообщение "Cannot Access Non-Static Method 'BeginTimer' in a static context". Причина, по которой некоторые люди делают это, заключается в том, что в большинстве примеров свойство TestContext имеет значение `TestContext TestContext;' См. 3 по той причине, что примеры используют это.
  2. Если вы назначаете свойство TestContext, скажем, ClassInitialize или AssemblyInitialize, вы, кажется, получаете что-то не совсем правильное, вы получаете один экземпляр тестового контекста, с которым в прошлом у меня не было проблем для модульных тестов и закодированных тестов пользовательского интерфейса, но нагрузочные тесты с этим не справляются. Если вы это сделаете, вы увидите ошибку "There is already an active timer with the name 'TimerName' passed to BeginTimer".

  3. Таким образом, окончательное решение: обязательно настройте свой TestContext как полное свойство, если вы сделаете это, свойство будет установлено механизмом выполнения теста независимо для каждого запуска нагрузочного теста. Это означает, что вам не нужно устанавливать значение самостоятельно.

Итак, вам нужно что-то вроде следующего:

 private TestContext m_testContext;

    public TestContext TestContext
    {
         get { return m_testContext; }
         set { m_testContext = value; }
    }

Если вы поместите точку останова в установщик, вы увидите, что после инициализации класса, но до TestInitialize вызывается 'установщик TestContext', и значение присваивается из UnitTestExecuter.SetTestContext(). Теперь тест остается именно таким, каким вы пытались его сделать.

public void TestMethod1()
{
    TestContext.BeginTimer("Overall");
    for (int i = 0; i < 5; i++)
    {
        TestContext.BeginTimer("Per");
        doAction();
        TestContext.EndTimer("Per");
        Sleep(1000);
    }
    TestContext.EndTimer("Overall");
}

Теперь, когда вы посмотрите на результаты нагрузочного теста, вы увидите выходные данные таймеров в разделе Scenario > TestCaseName > Transactions > TimerName.

Вот как выглядит мой вывод с моими таймерами Cache, Create-, Login

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

Который содержит

  • Сред. Время отклика
  • Сред. Время транзакции
  • Всего транзакций
  • Транзакций/сек

Все это потом можно посмотреть на графике.

В примере OP, если вы запустили нагрузочный тест с 10 пользователями, каждый из которых запустил тест 1 раз, а DoWork занял 0 секунд, вы увидите:

  • всего 10 тестов
  • 10 значений для «Общего» по 5 секунд каждое,
  • 50 значений для "Per" по 0 секунд каждое.

Что, я думаю, является предполагаемым результатом.

Эти проблемы заняли у меня несколько часов, чтобы выяснить и после некоторого тестирования точно определить и проверить, но в конце концов это кажется самым простым и лучшим решением.

С другой стороны, это правильная реализация TestContext для правильной работы тестирования, управляемого данными, поскольку каждый тест может получать свои правильные данные из контекста.

person Metaphysico    schedule 03.11.2016