Pull to refresh
0

Профайлинг NUnit-тестов .NET Framework 4

Reading time 5 min
Views 14K

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

Как показал мой личный опыт, чтобы успешно выполнить эту задачу для сборки, собранной под .NET Framework 4, требуется выполнить ряд действий, на поиск которых мне пришлось потратить определенное время. Поэтому я решил обобщить полученный опыт в единую компиляцию и сделать ее доступной для того, чтобы другие смогли избежать тех граблей, на которые пришлось наступить мне.

Задача стояла вполне реальная — необходимо было замерить расход памяти при подъеме большого количества тестов — для того, чтобы убрать утечки памяти.

В рабочем проекте в качестве тестовой среды использовался NUnit 2.4, а в качестве профайлера был выбран .NET Memory Profiler 4.0.

Тестовые данные


Сделаем сборку, содержащую пример теста.

Для этого в Visual Studio 2010 создадим новый проект WPF User Control Library, добавим в референсы nunit.framework.dll и создадим файл TestClasses для тестов.


Рис.1 Референсы и состав проекта


Рис.2 Окно опций проекта

Напишем один единственный тест, код которого приведен ниже:

using System;
using NUnit.Framework;
using System.Windows.Controls;
using System.Windows;
 
namespace WpfClassLibrary {
    [TestFixture]
    public class MyTests {
        [Test]
        public void TestForProfiling() {
            TextBox textBlock = new TextBox();
            textBlock.Text = "UIElement Test";
            Assert.IsTrue(textBlock.Text.Length > 0);
            MessageBox.Show("Ready for Collect Snapshot...");
        }
    }
}


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

Настройка профайлера


C чего начать профайлинг теста?

Запустим профайлер и укажем в качестве запускаемого приложения консольную версию NUnit. Для этого указываем файл nunut-console.exe (в моем случае он был расположен по следующему пути: C:\Program Files (x86)\TestDriven.NET 3\NUnit\2.4\ ).

Теперь останется задать аргументы. Достаточно запустить nunut-console.exe с ключом "/?" чтобы получить интересующий нас список. Формат запуска файла выглядит так:

NUNIT-CONSOLE [inputfiles] [options]

Т.е необходимо указать путь до сборки с тестами, указав опции. Нас будет интересовать только одна:

/run=STR Name of the test to run

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

Приходим к тому, что пропишем следующую строку в качестве аргументов для профайлера:

C:\Sandbox\WpfClassLibrary\WpfClassLibrary\bin\Debug\WpfClassLibrary.dll /run=WpfClassLibrary.MyTests.TestForProfiling



Рис.3 Окно настроек профайлера для запуска теста

Вроде бы все готово, но в при данных условиях тест не выполняется. Пришло время разобраться, почему это так…


Как позволить NUnit запусть сборки собранные в VS2010 ?


Попробуем запустить наш тест, используя nunit-console.exe, а не профайлер.

В результате получаем ошибку System.BadImageFormatException:
Unhandled Exception:
System.BadImageFormatException: Could not load file or assembly 'C:\Sandbox\WpfClassLibrary\WpfClassLibrary\bin\Debug\WpfClassLibrary.dll' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.
File name: 'C:\Sandbox\WpfClassLibrary\WpfClassLibrary\bin\Debug\WpfClassLibrary.dll'


Т.к. что наша сборка собрана под Framework 4, она не может быть загружена для запуска теста.

Находим файл nunit-console.exe.config, расположенный рядом с консольной версией NUnit. На моей машине он расположен тут:
«C:\Program Files (x86)\TestDriven.NET 3\NUnit\2.4\nunit-console.exe.config»

Ищем секцию startup, в которой описание нам говорит:
<!--
   The startup section may be used to specify the runtime versions
   supported in the order that they will be used if more than one
   is present.
  -->

* This source code was highlighted with Source Code Highlighter.

Эта секция содержит информацию о поддержку .NET Framework версий 1 и 2, но никак не 4.
Создаем свою startup-секцию (или модифицируем существующую) и добавляем туда строку для поддержки запуска сборки 4-го фреймворка. Строка для добавления выглядит так:

<startup>
   <supportedRuntime version="v4.0.30319" />
</startup>

* This source code was highlighted with Source Code Highlighter.


Первая проблема решена — NUnit теперь умеет запускать тесты, собранные под .NET Framework 4.


Конфигурируем запуск сборки с тестами в STA


Вернемся к нашему тесту. Он прекрасно запускается и проходит, если запускать его непосредственно из Visual Studio 2010 с помощью интегрированных в нее средств TDD. Если же попытаться выполнить тест из nunit.exe или ее консольной версии (для консольной не забудем указать параметром тестовую сборку), то произойдет следующая ошибка:
MyTestLib.MyTests.TestForProfiling:
System.InvalidOperationException: The calling thread must be STA, because many UI components require this.



Рис.4 Ошибка при запуске теста в NUnit

Из сообщения и стека вызовов становится очевидно, что необходимо запускать тесты в потоковом апартменте STA. Интересующиеся могут почитать детали тут или тут.

Запуск тестов для WinForms или ASP.NET может отличаться от рассматриваемого WPF примера, но, тем не менее, существует ряд ограничений (таких как OLE drag-n-drop, работа с клипбордом и т.д), которые будут требовать запускать тесты в STA.

В более новой версии NUnit 2.5 даже существует специальный RequiresSTAAttribute атрибут для этих целей.

Для тех, кто ограничен использованием NUnit версии 2.4 можно воспользоваться конфигурационными файлами.

Для тестируемой сборки создадим такой файл с указанием необходимости запуска тестов в STA. Хочу заметить, что он должен иметь имя, совпадающее с именем сборки и постфиксом config, и должен быть расположен рядом сней.

Создадим файл с именем WpfClassLibrary.dll.config следующего содержания:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="NUnit">
      <section name="TestRunner" type="System.Configuration.NameValueSectionHandler" />
    </sectionGroup>
  </configSections>
  <NUnit>
    <TestRunner>
      <add key="ApartmentState" value="STA" />
    </TestRunner>
  </NUnit>
</configuration>


* This source code was highlighted with Source Code Highlighter.



Вторая проблема решена — файл тестовой сборки теперь имеет конфигурационный файл, определяющий правильный способ запуска из NUnit.



Профайлим тест


Пришло время проверить результат. Запустим профайлер и убедимся, что тест был вызван.


Рис.5 Профайлинг теста

Задача решена — сборка с тестом корректно подгружена и тест запущен.

В дополнение хочу сказать, что существует другой вариант запуска профайлера. Суть метода в том, что можно указать системе запускать определенный процесс (для TDD это ProcessInvocation86.exe) под отладчиком (профайлером). Это делается прописыванием в реестре ключа Image File Execution Options.
Но это уже тема для отдельной статьи…

На этом всё! Надеюсь, что эта информация окажется полезной и сэкономит время на выполнение подобной задачи.

Удачного тестирования и профайлинга!
Tags:
Hubs:
+19
Comments 12
Comments Comments 12

Articles

Information

Website
www.developersoft.ru
Registered
Founded
1998
Employees
201–500 employees
Location
Россия