(MVC) MVC (2016 год)

Unit-тести для ASP.NET MVC

На цієї сторінці я розповім як виконувалися тести в одному з моїх великих проєктів. Та про сім видів тестів, яки зазвичай виконуються у ASP.NET MVC. Це відповідає загальному погляду на програмування TDD - Test Driven Development.



1. Загальний опис проєкту ShelAuto.RU

На цієї сторінці спочатку я трошки розповім про один свій проєкт, який майже зовсім не описаний на моєму сайті - проект SHEL-AUTO.RU. Лише зараз, коли (з одного боку) цей проєкт вмер остаточно, а з другого боку у мене є вільний час, я могу розповісти про цей цікавий проект. Наскільки я розумію, на моєму сайті є лише невеличка ремарка щодо специфічного сортування, яку я використав у цьому сайті. Та ще існує сторінка зі скринами форм та описом однієї сторінки Электронный магазин запчастей SHEL-AUTO.RU на web-сервисах EMEX.RU. Тут я розповім трохи більше.

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



Тепер від загальної розповіді перейдемо до специфічного опису особливостей системи. Загально кажучи, це специфічній інтернет магазин, у якому продаються товари з локальної бази (локального складу цієї фірми) та з великого складу у Москві. Відносини з великим складом оформлені як дилерство, а у Москві існує велика фірма https://www.emex.ru/ (нажаль з недуже розумними програмістами), яка має SOAP-WSDL сервіси http://wsdoc.emex.ru/, яки показують наявність товарів на складі та стан заказу, наприклад товар вже відвантажений ділеру, чи ще ні, тільки пакується. Це звичайні логістичні бізнес-процесі, тобто якась автозапчастина замовляється кінцевим замовником у дилера, і відвантажується зі складу якогось великого імпортера, який має договора поставки запчастин з великими компаніями, BMW-Mersedes та іншими. Тобто, це надзвичайна торгова компанія, на сайті якої замовник може заказати необхідну запчастину, яку потім замовник отримує у своєму місті, яку для нього замовляє ділер на великому складі у Москві. Зрозуміла-звичайна та нецікава бізнес-схема. Але цікавості тут починаються безпосередньо у програмуванні.

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

Отже, друзі, ми все ближче і ближче до суті програмного коду. Нам потрібно якось налаштувати код нашого магазину, безліч коду, більш ніж 100 тисяч стрічок. Але як це робити? Невже потрібно заказувати якусь авто-запчастину в Москві, та виконувати отладку на реально-замовлених запчастинах, які до того ж, власник цього бізнесу повинен буде оплатити на складі у Москві. Є ще інша складність. Я казав вже, що у великих фірмах люди дуже загальмовані, величезна текучість кадрів, маленька зарплата, нікому ні до чого немає діла. Наприклад у тебе змінився айпішнік девелоперської машини - можно місяць чекати, поки у Москві внесуть твій айпишнік до списку дозволених. Тобто єдиний розумний підхід - це тести, яки по своєї логіці подібні до interface injection, але робляться самостійно, як-бито власними ручками.


Давайте спочатку подивимося, як працює цікавий для нас фрагмент коду. Серед тисячів і тисячів методів, є метод ADMIN-контролеру RefreshEmexBasketStatus - який визивається у деяких необхідних випадках у цьому контролері.



Десь серед тиcячів класів цього проекту знаходиться клас EMEX, у якому знаходиться цей метод RefreshEmexBasketStatus.



В свою чергу, цей метод розділює загальний запит на пакети по 100 запитів, робить між пакетами затримку і звертається до SOAP-WSDL cервіса EmExInmotion.InmConsumer



В процесі обробки пакета в сто заказів (RefreshEmexStatus100) є ще інші хандлери, наприклад потрібно парсити XML, та ще дещо, але це ми вже далі занурюємось в подробиці цього проєкту і цього коду.



Тут взагалі подробиць набагато більше, ніж я пояснюю у цієї загальної схемі взаємодії класів, наприклад від Global.asax періодично організується окремий процес, який також інколи звертається до аналогічних сервісів - але встановлює трошки інші флажки у базі, ніж ті, що встановлює ручками Админ проєкта, коли робить рефреш параметрів. Але щоб пояснити, як працює 100 тисяч стрічок коду звичайними словами, потрібно, мабуть, 10 мільйонів звичайних слів простої людини, яка не розуміє команд бейсіку.



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

2. Юніт-тести звернення до зовнішніх SOAP-WSDL сервісів.

Найбільш цікаві тести для цього проєкту - якось вприснути результати звернення до функції EmEx_InMotion.InMotion_Consumer_ByGlobalIdAdv, нібито з'ємуліровати звернення на реальну базу в Москві, НІБИТО зробити там замовлення, НІБИТО отримати там нові статуси деталі - наприклад деталь запакована та відправлена на адресу замовника. При чому цікаво було б зробити це не змінюючи коду проєкту і, зрозуміло, без реального звернення до сервісу. Бо кожне реальне звернення потребує оплати на центральний склад у Москві, це реальний заказ, реальний бізнес-процесс, замовлена запчастина запаковується і відвантажується замовнику. А як можна налаштувати всі ці сотні тисяч стрічок коду? Тільки на різноманітних ємуляторах!

По-перше, простого засоба ємуліровати web-сервіси не існує. Тобто що таке взагалі клент SOAP-WSDL-cервісу? Це не більше, як набор деяких функцій:



Але всі ці функції наслідуються від величезної ієрархії класів



Тому, нажаль, просто зробити свій клас, як потомок класу, що описує зовнішній WEB-сервіс - неможливо. Навіть якщо використовувати усі могутні можливості бейсіку, наприклад Shadows замість звичайного Overrides - ніяк модифікувати NEW у базових класах ви не зможете, а ці базові класи будуть звертатися по адресу сервіса, а не на ваші локальні програмні ємулятори WEB-сервісів. Тобто, ось таким чином ємуліровати зовнішні WEB-сервіси неможливо. Якщо хтось знає шлях, як це можливо - напишіть мені, будь ласка, в каментах до цієї сторінки. Я бачу лише якісь напів-хакерські шляхи, якось ось так CLR Injection: Runtime Method Replacer, або ось так MethodRental.SwapMethodBody Method - але це лише принцип дії. Можливо, можно вирокистовувати ось цю систему - Rebel.NET, .NET Internals and Code Injection Для реальних тестів цім шляхом потрібно мати надійній фрейворк, власній чи зовнішній - я такого не знаю і не маю. Тому цей шлях у мене і залишається лише як принцип дії ємулятора.



Тому залишаеться лише всім добрий знайомий шлях сервісу, який ємулірує зовнішній сервіс. Мабуть, сістем ємуляторів існує багато, но я користуюсь вже багато років своєю улюбленою системою https://www.soapui.org/. У цієї фірми, взагалі чудові Freware-інструменти, не тільки SoapUI - https://smartbear.com/product/free/ - мені вони подобаються.



Цю систему я вже багато разів описував на своєму сайті, я постійно нею користуюся (SOAP/WSDL vs XML data exchange.), тому повторюватися нецікаво. Глобально, ця OpenSource-прога дозволяє зробити як тестовий кліент, так і тестовий сервіс. На скрині вище ви як раз бачите роботу тестового сервіса.


Взагалі для цього конкретно проєкту, SOAPUI-тести - це були головні типи моїх тестів. Але далі я хочу трошки описати більш прості тести, яки звичайно використовуються у тих випадках, коли проєкт розрізаних на багато груп програмістів, і наприклад одні програмісти пишуть лише WEB-форми (Front-end), інші - тільки базу, інші - тільки контролери, які підготовлюють дані для відображення.

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



Це найбільш швидкий та простий засіб програмування, мабуть у тисячі разів швидче, ніж застосування EntityFramework, Dependency Injection та інших технологій, за допомогою яких з ось такого коду можна зробити тисячі стрічок коду. Розмазати ці тисячі стрічок по десяткам програмістів. А потім для кожного окремого фрагменту кода зробити Unit-Teсти.


   1:  <body>
   2:      <div>
   3:          <center><span class="pagetitle">Состояние заказа <%= ViewData("Details_ID").ToString.Substring(0, 6)%>/<%= ViewData("EmexNum")%></span></center>
   4:      </div>
   5:      
   6:      <% Dim AllState As System.Collections.Generic.List(Of State) = Application("AllState")%>
   7:   
   8:      <table id="myTable" class="sortable" width="100%" cellspacing="1" cellpadding="3">
   9:      <thead>
  10:          <tr>
  11:              <th>
  12:                  Дата
  13:              </th>
  14:              <th>
  15:                  Статус
  16:              </th>
  17:          </tr>
  18:          </thead>
  19:          <%If ViewData("StatusLog") IsNot Nothing Then%>
  20:          <% Dim StatusLog As System.Collections.Generic.List(Of StateLog) = ViewData("StatusLog")%>
  21:          <tbody>
  22:          <% For i As Integer = StatusLog.Count - 1 To 0 Step -1%>
  23:          <tr>
  24:              <td>
  25:               <%: String.Format("{0:dd.MM.yy hh:mm}", StatusLog(i).CrDate)%>
  26:              </td>
  27:              <td>
  28:              <b><%: AllState(StatusLog(i).NewState).State_Name%></b>
  29:              </td>
  30:          </tr>
  31:          <% Next%>
  32:          </tbody>
  33:          <% End If%>
  34:      </table>
  35:   
  36:  </body>
  37:  </html>

3. Юніт-тести звернення до бази.

Це теж дуже цікаві тести, наприклад підкладати в базу деякі запроси до сервісів та подивитися, як буде реагувати сервіс. З одного боку. А з іншого боку як будуть відображатися на формах різноманітні статути замовлень, як будуть впливати наприклад курси валют, на відображення на формах у кінцевого кліента-замовника. Але у цьому проєкти я не проводив таких тестів, бо:

Здається, що ідея TDD занадто революційна. Усе існуюче програмне забезпечення .NET платформи, починаючи з клиета SOAP-WSDL сервісу і закінчуючи LINQ-To-SQL ніяк не пристосовано працювати в рамках цієї теорії. Але все це дуже непогані компоненти .NET, мабуть кращі що взагалі є у Мікрософта. Хм...

4. Юніт-тести MVC-контролерів з HttpContext'ом

Ще раз повторюю, що якщо програміст має доступ до усіх компонентів проєкту, то юніт-тести часто взагалі не мають будь-якого сенсу. Але яко проєкт розрізан на багато часток, і людина не має доступу до інших частин проєкту - то іншого виходу, як UNIT-tests - не існує взагалі. І ще раз повторюю, що наприклад для цього проекту, звичайні юніт-тести MVC-контролерів - це було другорядне маловажливе питання (наприклад порівняно з тестами, коли сторонній сервіс надсилає ДЕЩО незрозуміле щодо стану заказу деталі, а це потрібно корректно обробити. Тобто ми наприклад надіслали заказ на центральний склад у Москву. А він у кращому випадку надсилає "помилка заказу", або "заказ принятий, але не на 10 запчастин, а на одну, та ще й по іншій ціні", чи наприклад, замість "товар відвантажений" надсилає "пошкоджена упаковка, гроші і товар повертатися не будуть". Таких ситуацій було за п'ять років роботи просто безліч. І потрібно було зміньвати поведінку системи залежно від нових і нових чудес, яки надсилав зовнішній сервіс. На такому фоні, юніт-тести окремого метода у окремому контролері віглядають якоюсь дурницею, але і іх було деколи потрібно робити.

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


Якщо заглиблюватися у цю тему, то по-перше - існує ось така сторінка Microsoft Chapter 13: Unit Testing Web Applications, яка вводить вас взагалі у курс цієї справи. Є багато людей, які взагалі пишуть на ці теми, ось наприклад Built-in Unit Test for ASP.NET MVC 3 in Visual Studio 2010 Part 2. Але для мене там щось дуже багато букв, мені подобається більш конкретно, наприклад ось тут непоганий опис http://www.danylkoweb.com/Blog/how-to-successfully-mock-httpcontext-BT. До речі, ця людина стверджує, що RhinoMock-тести вже ніхто не використовує. Взагалі існує багато і специфічних тестових фреймворків для ASP.NET, наприклад всі дуже хвалять MvcContrib. Можливо все це й так. Але я - старомодна людина и покажу як я робив тести для цього проекту саме на Rhino Mocks.

Для того, щоб розпочати Unit-tests - по-перше потрібно зробити окремий проєкт ClassLibrary (у тому ж самому фреймворку, що і Система, яку ви збираєтесь потестити). По-друге, потрібно додати до проєкту лінки на DLL з MVC-контролерами, що ви плануєте тестити, та додати до проекта дві обов'язкові DLL: RhinoMocks та NUnit. Звичайно, потрібно додати DLL - System.Web.DLL та System.Web.Mvc.DLL.

Також важливо, щоб у вашій студії був присутній extension - Visual Nunit 2010 Після цього вже можна починати робити код тесту.




Цей проект не був зроблений з урахуванням теорії TDD - Test Drive Development. Це, мабуть, у деяких випадках, і непоганий принцип, але це учбовий принцип, на яких навчаються програмувати діти в школі. Справжні проєкти, як ось цей, які потрібно зробити у короткий час, які приносять мільйони доларів доходу - виглядають складніше. Я якщо б я дотримувався усіх теорій (ось тут у мене перераховано пару сотен різноманітних теорій, при чому кожна теорія програмування має протиріччя з усіма іншими теоріями та руйнує їх) - якщо б я дотримувався багатьох теорій, і особливо теорії TDD - я б ніколи не зробив би цього проєкта.

Цей проєет постійно змінювався, незрозуміло було що буде давати сервіс EMEX (до того ж, сама версія і набор функцій сервіса EMEX за п'ять років декілька разів змінився. Тому я вирішив, що буду передавати з контролера до форми через ViewData лише юзера зі свої бази, щоб не змінювати постійно код - бо моя база - це єдине що було біль-менш постійне у цьому проєкті. А важливі дані буду передавати через Session. Ті дані, що повинні зберігатися від сторінки до сторінки. Це мабуть не дуже сучасний шлях, бо рекомендується використовувати TempData, але принципово між нім і Session різниці немає.

Тому далі я покажу тести лише самого простого методу BASKET з контролеру CAB.



Зверніть увагу, що в цьому контролері використовується HttpContext у вигляді HttpContext.Current.Session та у вигляді HttpContext.User.Identity.Name - це дуже важливо для наших тестів. Взагалі я так дуже рідко роблю, звичайний мій шлях - загальний код аутентифікації, який використовується у цілому проєкті (Додаток про загальний код у проектах MVC.), але в цьому проєкті зроблено саме так.

Тому самий перший клас, який нам потрібно додати до тестів - це емулятор контексту. Без цього емулятора можливо написати лише отмазку для начальника, а не реальні тести, бо тести без HttpContext, щонайменше, не зможуть визвати MVC-методи з аутентіфіцірованим юзером. Тобто без цього класу щонайбільше, що може бути протестованим - це вхідна сторінка сайта, тобто метод Index контролера Home. Всі інші тести вже потребують наявності цього класу.


   1:  Public NotInheritable Class MockHelpers
   2:      Public Shared Function FakeHttpContext() As Web.HttpContext
   3:          Dim httpRequest = New Web.HttpRequest(", "http://localhost/", ")
   4:          Dim stringWriter = New IO.StringWriter()
   5:          Dim httpResponce = New Web.HttpResponse(stringWriter)
   6:          Dim httpContext = New Web.HttpContext(httpRequest, httpResponce)
   7:   
   8:          Dim sessionContainer = New Web.SessionState.HttpSessionStateContainer("id",
   9:                                                                                New Web.SessionState.SessionStateItemCollection(),
  10:                                                                                New Web.HttpStaticObjectsCollection(),
  11:                                                                                10,
  12:                                                                                True,
  13:                                                                                Web.HttpCookieMode.AutoDetect,
  14:                                                                                Web.SessionState.SessionStateMode.InProc,
  15:                                                                                False)
  16:   
  17:          Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer)
  18:          Return httpContext
  19:      End Function
  20:  End Class

Для того, щоб проходити тести у пошаговому режимі, потрібно "підчепити" дебагер до потрібного процессу, і потім нажати RUN на плагіні NUNIT-тестів.



Ninit-плагін побачить свої атрибути SetUp та Test, і виконає потрібні фрагменти коду. Перевіркою того, що Nunit без помилок вичитує свої теги служить те, що комбобокси на формі плагіну відкриваються правильно.



Результат тестів можна записати в базу звичайним засобом, можна вивести на форму плагіна по NUnit.Framework.Assert.Pass, можна вівести як я зробив - за допомогою System.Diagnostics.Trace.WriteLine у вікно дебагера. Можна навіть у якісь модальні вікна Windows.Forms.



Але головне, для чого взагалі зроблені тести - щоб їх міг побачити менеджер процессу (QA-Менеджер). Засоби колективної розробки программ від Міскрософту загружать результати своїх тестів на Test dashboard (Agile and CMMI). Тому, коли ви працюєте вільно, без менеджеру, який контролює кожний ваш шаг, процедури тестів взагалі можна пропустити, чи робити їх як саме вам потрібно, а не як вимагає менеджер.

Я працюю, як правило, без жорсткого контролю зі сторони менеджера, тому, якщо у мене щось не виході, я знаходжу інший шлях (особливо, якщо ви маете доступ до всього коду і просто можете дещо закоментити). Ось наприклад, у мене в цьому проєкті був дуже важливий клас з параметрами роботи проекту. Зрозуміло, що параметри роботи вичитуються один раз при старті проєкту, тобто кодом від GLOBAL.ASAX. Я витратив годину чи більше і не знайшов засобу протестити ось цей код. І тому облишив цей тест і пишов далі. Але якщо ви будете працювати з Менеджером, такої халяви не буде.



Якщо ви знаєте, як емулюювати Application в таких тестах, напишіть мені, будь ласка, у каментах.


5. Код тесту MVC-контролеру та його особливості (Extension-функції та Lambda Expressions)

Отже код мого найпростішого теста виглядає ось так. Цей код має дві дуже-дуже цікаві особливості - вони підкреслені і я далі скажу про них окремо.


   1:  <NUnit.Framework.TestFixture()> _
   2:  Public Class SimplestTest
   3:   
   4:      Dim TestPrincipal As aSystem.Security.Principal.GenericPrincipal
   5:   
   6:      <NUnit.Framework.SetUp()> _
   7:      Public Sub Setup()
   8:          Dim TestIdentity = New Security.Principal.GenericIdentity("admin")
   9:          TestPrincipal = New Security.Principal.GenericPrincipal(TestIdentity, Nothing)
  10:          '
  11:          Web.HttpContext.Current = MockHelpers.FakeHttpContext()
  12:      End Sub
  13:   
  14:      <NUnit.Framework.Test()> _
  15:      Public Sub Basket_Index()
  16:   
  17:          Dim TestHttpContext = MockRepository.GenerateMock(Of System.Web.HttpContextBase)()
  18:          TestHttpContext.Stub(Function(x) x.User).[Return](TestPrincipal)
  19:   
  20:          Dim TestControllerContext = New System.Web.Mvc.ControllerContext
  21:          TestControllerContext.HttpContext = TestHttpContext
  22:   
  23:          Dim Y As New OMSK1.Omsk.CabController
  24:          Y.ControllerContext = TestControllerContext
  25:   
  26:          Dim Result As System.Web.Mvc.ActionResult =
  27:              Y.Basket("7740984b-805a-45e6-b52f-0694c01862af")
  28:   
  29:          Dim Model As Global.OMSK1.AspnetUsers =
  30:              Result.GetModel(Of Global.OMSK1.AspnetUsers)()
  31:   
  32:          NUnit.Framework.Assert.AreEqual("7740984b-805a-45e6-b52f-0694c01862af", Model.id.ToString)
  33:   
  34:          For Each OneKey In Web.HttpContext.Current.Session.Keys
  35:              System.Diagnostics.Trace.WriteLine(OneKey & IIf(Web.HttpContext.Current.Session(OneKey) Is Nothing, "-Nothing", "-OK"))
  36:          Next
  37:      End Sub

Спочатку загальна ідея цього коду. По назві зрозуміло, що стрічки 7-12 - це ініціалізація. У цьому випадку тут організується залогінений контекст для тестів контролерів, та статичній контекст для зберігання Session.

Стрічка 17 утворює емулятори інтерфейсу HttpContextBase - це головне зовнішнє середовище, що потрібно для виконання цього тесту.



Далі у стрічці 24 воно задається як контекст виконання контролеру. І далі, у стрічці 27 викликається сам контролер, який згідно встановлених мною правил у цьому проєкті повертає лише ID поточного юзеру. Тобто Assert у цьому випадку досить фіктивний, набагато більше значення мають усі змінні Session.

Дуже важлива для порозуміння цього коду - стрічка 18. Коли ми у 17-ї стрічці утворили інтерфейси для виконання контролера, воні пусті, тобто не наповнені конкретними класами з об'єктами. Саме стрічка 18 наповнює інтерфейс Юзер реальним вмістом, тобто реальнім об'єктом USER (який ми зробили у процесі ініціалізації тесту у стрічках 8,9).

Але найбільш цікаво - ЯК це було зроблено! Це було зроблено за допомогою Lambda Expressions. Тобто, будь яке звернення до об'єкту User об'єкту HttpContextBase буде повертати об'єкт HttpContextBase. Саме це записано тут за допомогою expression-функції.



Ще більш цікавий фрагмент коду тут у стрічці 30. Пам'ятаю часи, коли Extension-функції.NET були для мене однією з самих загадкових особливостей NET. Тобто, у цілому нібито зрозуміло, що можливо написати якийсь свої власні функції, що будуть поширювати існуючий набор функцій, що маніпулюють даними безпосередньо у самому класі, тобто додати свої власні функції. Але навіщо це може буте потрібно? Існує безліч інших методів маніпуляції будь-якими даними, зовнішньої маніпуляції, навіщо може бути потрібно поширювати існуючий набор функцій безпосередньо всередині самого класу?

А справа тут ось в чому. Об'єкт ActionResult взагалі не має якихось корисних свойств чи методів, тільки ExecuteActionResult (все інше просто наслідується від Object). Це занадто загальний об'єкт, наприклад всі редіректи можно передати через нього. Лише ViewResultBase має корисні свойства, такі як Model, наприклад. І використовується цей об'єкт уже не для загальних цілій, таких як редіректи, а для об'єктів Model, які потрібно наприклад відобразити на WEB-формі.



Таким чином, об'єкт даних можливо побачити, якщо перетворити за допомогою DirectCast занадто загальний об'єкт ActionResult у більш конкретний об'єкт ViewResultBase - тоді у нового об'єкта з'являються додаткові корисні інтерфейси, через які вже можна побачити модель, наприклад.



Але і наявності самої моделі недостатньо. Потрібні ще й ще більш детальні інтерфейси, тобто потрібно знати конкретний клас модели, що повертає контролер (у данному випадку OMSK1.AspnetUsers).



Щоб не робити безкінечні ланцюжки CAST'ів, особливо коли моделі різні - можна доповнити System.Web.Mvc.ActionResult власною функцією GetModel.


   1:  Module MvcResultExtensionFunction
   2:      'Поширює класа першого параметру, у цьому випадку System.Web.Mvc.ActionResult
   3:      <System.Runtime.CompilerServices.Extension()> _
   4:      Public Function GetModel(Of T As Class)(Page As System.Web.Mvc.ActionResult) As T
   5:          Dim Model = DirectCast(Page, System.Web.Mvc.ViewResult).ViewData.Model
   6:          Return TryCast(Model, T)
   7:      End Function
   8:  End Module

Або, навіть ось так:


   1:  'Теж саме у більш короткому, але меньш зрозумілому сінтаксісі 
   2:  Module MvcResultExtensionFunction
   3:      <System.Runtime.CompilerServices.Extension()> _
   4:      Public Function GetModel(Of T As Class)(Page As System.Web.Mvc.ActionResult) As T
   5:          Return TryCast(DirectCast(Page, System.Web.Mvc.ViewResult).ViewData.Model, T) 
   6:      End Function
   7:  End Module

Тут використаний дуже цікавий Контрейнст As Class, тобто це вимога, щоб параметр T був саме класом, а не чимось іншім. Саме тоді буде можливо виконати останнє перетворення TryCast(Model, T). Про контрейнси почитайте, будь ласка у Мікрософта на цієї сторінці - останній приклад там просто вбивчий. Коли контрейнсти визначаються як коллекция

Public Class thisClass(Of t As {IComparable, IDisposable, Class, New})

More about extension function:



6. АJAX-тести.

У цьому проєкті у мене не було AJAX-функціионалу. Взагалі, я поки що роблю ці тесті за допомогою звичайних хандлерів, Network monitor'а браузера, Fiddler, Wireshark (особливо у власних проектах, де я маю доступ до всього коду, а не просто виконую вимоги Менеджеру), використовувати специфічні фреймворки для тестів AJAX-функціоналу чужих сайтів у мене поки що потреби не було. Але принципово це дуже важлива частки програмування сайтів і багато людей цим займається. Ось подивиться наприклад ось цю сторінку mock ajax mvc.

Колись у майбутньому, мабуть, мені теж попадеться у проєкт, де буде потреба у AJAX-тестах, які виконуються у емуляторі, без повного запуску проекта з тестовой DLL. Тоді я доповню цю сторінку.



7. Unit-тести HTML-відображення даних.

У описаному тут проекті ShelAuto у мене не було потреби робити тести HTML-верстки та взагалі відображення Model для юзера у браузері. Але у іншому моєму проекті це була настільки важлива тема, що я присвятив ій декілька сторінок на моему сайті - починаючи с сторінки Аналіз MVC-сайту за допомогою System.Reflection.

А потім я взагалі на базі цього функционалу утворив фрагмент коду, який має можливість закеширувати вхідну сторінку сайту - і видавати статичне відображення вхідної сторінки сайту зовнішнім незалогіненим юзерам - Кешування вхідної сторінки сайту за допомогою System.Web.Mvc.ViewEngines.






Comments ( )
Link to this page: //www.vb-net.com/UnitTest/index.htm
< THANKS ME >