NAV 2009 SP1 testing. Модульное тестирование кода в NAV 2009 SP1.

Поговорим немного о возможностях тестирования в NAV 2009 SP1 и сравнении этих возможностей с тем, что мы имеем сейчас…
Известно, что в как более старых версиях, так и в версии 5.0, достаточно просто заниматься процессом «тестирования»: попробовал сам в ручном режиме несколько сценариев, отдал пользователю пресловутую форму с кнопками и ждешь, когда же он сообщит о найденных на ней ошибках. Понятно, что здесь я немного утрирую, но суть примерно такая, согласитесь?

Собственно, это даже не testing как таковой, поскольку процесс тестирования довольно многогранен:

  • тестирование производительности;
  • тестирование безопасности;
  • тестирование интерфейса, бизнес-логики и т.д…

Все-таки разработка в NAV – прикладная, поэтому нанимать в штат профессионального тестера для выполнения перечисленных вещей выглядит довольно абсурдно…

Вообще, на мой взгляд, тестирование в Навижине – это и не тестирование вовсе. Это – верификация. Т.е. мы проверяем алгоритмы на основании опытных данных, доказывая этим, что ошибки и дефекты отсутствуют с нашей точки зрения (с точки зрения изменяемых и предлагаемых нами методов решения проблемы). Но гарантировать отсутствие дефектов с учетом человеческого фактора (пользователя) не представляется возможным…

И все бы ничего, если бы не одно «Но», о котором я уже написал: конечный пользователь. Человек – не машина и предвидеть какие-то специфичные ошибки/слабости системы, подчас, не в состоянии.

К примеру, надо было ему в заказе продажи нажать кнопку, которая пересчитывала бы по определенному алгоритму все строки этого заказа и что-то там выдавала. И пользователь у нас настолько попался хороший, что обычно никогда не забывает ввести количество товара в строке… Но, разработку он принял и однажды, забыв ввести количество, нажал уже привычную для него кнопку. И выдалась ему ошибка, что на ноль делить нельзя…

 m2_pic-1.png

Можно было предвидеть такой поворот событий на этапе разработки/тестирования? Конечно. Но что нам предлагают в этом случае более старые версии системы, включая NAV 5.0 и последняя NAV 2009 SP1?

 m2_pic-2.png

* - перечисленные возможности доступны только в Service Pack 1для NAV 2009
** - http://msdn.microsoft.com/en-us/library/ee414224.aspx

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

Хотя, откровенно говоря, среда разработки NAV - не такая мудреная вещь, чтобы усердно что-то там тестировать и отлаживать… Но, подчас, бывают такие ситуации, когда хотелось бы иметь под рукой «красную кнопку», нажав на которую, можно было бы однозначно точно понять – «вот конкретно тут логика ведет себя неправильно или не совсем корректно»…

Под «конкретно тут» я понимаю принципы простого модульного тестирования, когда у нас нет цели оттестировать все и вся в нашей доработке/Add-on. И эти принципы, отчасти, реализованы в новых инструментах разработчика NAV 2009 SP1:

  • test codeunits;
  • test runner codeunits;
  • UI (User Interface) handlers;
  • ASSERTERROR statement.

Для тех, кто сразу хочет увидеть эти инструменты «в действии», приведу ссылку, по которой вы можете скачать «Application Test Toolset for Microsoft Dynamics NAV 2009 SP1».
Здесь приведен пример использования указанных выше новых инструментов разработки… А выглядит эта штука в классическом клиенте примерно так:

m2_pic-3.png

RTC-клиент:

m2_pic-4.png

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

Прежде всего, в систему были внесены общие изменения, которые касаются базовых настроек приложения:

m2_pic-5.png

Включение опции «Show C/AL Testability properties» открывает ряд других, скрытых возможностей настройки кодэюнитов. Теперь мы можем не просто создать кодэюнит, но и определить его тип:

m2_pic-6.png

Как видим, значение свойство кодэюнита Subtype может принимать 3 значения:

  1. Normal-codeunit – собственно, значение по-умолчанию. Кодэюнит является обычным исполняемым модулем.
  2. Test-codeunit. Центральное звено в процессе тестирования. По сути, содержит функции/процедуры, написанные для тестирования бизнес-логики и функционала системы. Кодэюнит с типом Test может содержать как стандартные (Normal) функции, так и новые виды функций (свойство FunctionType):
    m2_pic-7.png
  •  
    • Test-functions. Содержат логику для верификации Normal-кодэюнитов. Не могут содержать параметры и возвращать какие-либо значения, определенные разработчиком. В случае успешного выполнения возвращает SUCCESS. В противном случае, FAILURE.
    • Handler-functions. Содержат логику для перехвата «пользовательских взаимодействий» (UI interactions). К таковым относятся, к примеру, взаимодействия пользователя с окнами, формами, отчетами. Handler-функции (в более продвинутых средах и ЯП именуются как Event Handler - «перехватчик событий») достаточно полезны, поскольку позволяют подписываться на них Test-функциям, имеющим дело с окнами, формами и т.д… Подписка на конкретные хэндлеры задается в поле HandlerFunctions Test-функции через запятые. Настоятельно рекомендуется использовать перехватчики, дабы процесс тестирования был максимально автоматическим, без участия в нем пользователя.
  • TestRunner-codeunit. Собственно, является неким middle-tier, что ли, между Normal-кодэюнитами и Test-кодэюнитами… Помимо интеграции с обычными кодэюнитами, является инициатором взаимодействия с Test-кодэюнитами. По сути, является диспетчером, вызывающим и обрабатывающим функции Test-кодэюнитов.

Помимо перечисленных изменений на уровне ядра системы, было сделано еще одно изменение, касающееся таблицы AllObjWithCaption. Теперь поле «Object Subtype» работает не только для объектов с типом Page, но также и для кодэюнитов. Собственно, именно это поле и отвечает за свойства кодэюнитов, которые мы описывали выше…

На этом краткий курс теории закончен. Перейдем к практике…

В целом, рекомендуется создать некую таблицу, в которой будут храниться все кодэюниты с типом Test. Этакое «хранилище» Use Cases, которые в дальнейшем можно использовать для верификации самых разнообразных алгоритмов. Создав такое «хранилище», мы:

  • во-первых, накапливаем реальную базу алгоритмов для тестирования;
  • во-вторых, можем обрабатывать их в цикле, последовательно запуская механизм верификации для разных функциональных областей;
  • в-третьих, можем разделить Test-кодэюниты по функциональным областям.

В нашем тестовом примере мы не будем особо мудрить и создадим следующие кодэюниты (для удобства восприятия, я занес в Version List типы кодэюнитов):

m2_pic-8.png

Для начала, создадим обычный стандартный кодэюнит 50100 «NAV Testing Management», а также TestRunner-кодэюнит для запуска тестовых сценариев:

m2_pic-9.png

Идея в следующем: «NAV Testing Management» кодэюнит с типом Normal запускает «Test Runner Example» кодэюнит с типом TestRunner. Последний, в свою очередь, запускает все кодэюниты с типом Test. Помимо стандартных триггеров, вы можете увидеть 2 процедуры: OnBeforeTestRun() и OnAfterTestRun(). И вот здесь – самое интересное:

  • во-первых, это «родные» навижиновские триггера, которые, почему-то, надо создавать руками через Globals/Functions. Странно, что так было задумано (или, вероятнее всего, криво сделано) архитекторами системы, но факт остается фактом…
  • во-вторых, попытка скомпилировать кодэюнит «Test Runner Example» теперь окажется неудачной;
  • в-третьих, если убрать триггера OnBeforeTestRun() и OnAfterTestRun(), компиляция пройдет успешно.

Сначала разберем, для чего необходимы эти триггера:

  • OnBeforeTestRun-триггер срабатывает каждый раз перед вызовом очередной процедуры/функции/OnRun-триггера кодэюнита с типом Test;
  • OnAfterTestRun-триггер отрабатывает каждый раз после окончания работы очередной процедуры/функции/OnRun-триггера кодэюнита с типом Test.

Довольно полезные триггера.

Вот, что выдает C/SIDE при попытке скомпилировать кодэюнит «Test Runner Example»:

 m2_pic-10.png

Дело в том, что мы не определили параметры, необходимые для функционирования триггера OnBeforeTestRun:

  • CodeUnitId : Integer – номер тестирующего кодэюнита;
  • CodeUnitName : Text[30] – название тестирующего кодэюнита;
  • FunctionName : Text[30] – имя Test-функции;
  • возвращаемый Boolean-параметр влияет на то, будет ли Test-функция исполняться или нет. Если возвращаем FALSE – функция скипуется, если TRUE – выполняется. Это довольно полезно, поскольку позволяет на основании задаваемых нами условий определить – стоит вообще выполнять конкретный тестовый метод или стоит его пропустить.

После того, как мы определили параметры для триггера OnBeforeTestRun, определим их для триггера OnAfterTestRun:

m2_pic-11.png

Здесь добавляется один параметр, который передается по ссылке из Test-метода Test-кодэюнита. А именно – параметр Success Boolean-типа. Если Test-метод вернул ошибку, возникшую при тестировании какой-либо функциональности, параметр Success содержит значение FALSE, а ошибка, которая возникла в Test-кодэюните, может быть получена посредством вызова функции GETLASTERRORTEXT.

Снимем галочку Default Nos. в настройке серии номеров для клиента. Это означает, что мы не сможем создавать клиента с серией номеров по-умолчанию. Теперь напишем пару строк кода в триггере OnRun тестового кодэюнита «1st Test Codeunit»:

Customer.INIT;
Customer.Name := ‘:)’;
Customer.INSERT(TRUE);

Внесем изменения в кодэюнит «Test Runner Example»:

m2_pic-12.png

Система последовательно выведет на экран несколько сообщений, последним из которых является следующее:

m2_pic-13.png

Таким образом, мы перехватили ошибку, возникшую в кодэюните с типом Test.

Теперь зададим пару функций в кодэюните «2nd Test Codeunit»:

m2_pic-14.png

Свойства этих функций приведены ниже:

m2_pic-15.png

Тестовый сценарий довольно прост: мы хотим создать заказ покупки с одной строкой. А далее мы хотим сделать отгрузку одного единственного товара, который есть в нашем заказе. Поскольку в момент учета заказа продажи вызывается 91-й кодэюнит, нам необходимо обработать вызов STRMENU данного кодэюнита:

m2_pic-16.png

Вызов STRMENU отлавливаем функцией TestHandler, предварительно подписываясь на это событие в процедуре TestFunction. Инициализация параметра Choice функции TestHandler значением 1 говорит нам о том, что будет произведена только поставка товара, без счета. Если бы мы хотели получить счет, параметр был бы инициализирован значением 2.

Изучим стек вызова функций:

m2_pic-17.png

m2_pic-18.png

m2_pic-19.png

 m2_pic-20.png

Товары по заказу покупки получены.

Теперь попробуем присвоить параметру Choice значение 2. Как не трудно догадаться, сценарий вернет ошибку, поскольку будет создан новый заказ покупки, по которому еще не было произведено товарного движения! Тестовая транзакция будет откатана.

m2_pic-21.png

Метки: ,



Оставьте свой отзыв!