(MVC) MVC (2016 год)

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

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

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

Це була чудова сістема, ось моє резюме з цієї роботи, вона чудово працювала десь п'ять годин приносила своєму власнику десь приблизно десять мільонів доларів на місяць (згідно записів у моєї базі оброблених заказів), для цієї сістеми я написав понад 100 тісяч стрічок коду (на ASP.NET MVC). Але потім з незрозумілих для мене обставин ця сістема перестала бути потрібним свому власнику. Хм, це для мене залишилося загадкою. Взагалі ми з ним посварилися щодо Криму, відносини погіршились, але сістема працювала чудово і гроші білли власнику рікою.

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



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

Це дуже цікавий код, у якому кожна буква взриває мозок:



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 ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17
Link to this page: http://www.vb-net.com/ExtensionFunction/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <MAIL ME>  <ABOUT ME>  < THANKS ME>