ASP.NET MVC Урок 1-F / ASP.NET MVC Урок E
.pdf,WHPV
YDULWHPV QHZ/LVW'LFWLRQDU\
WKLV6HWXS S ! S ,WHPV 5HWXUQV LWHPV
`
`
Кроме этого создаем еще такие классы:
MockHttpCachePolicy
MockHttpBrowserCapabilities
MockHttpSessionState
MockHttpServerUtility
MockHttpResponse
MockHttpRequest
Все эти mockобъекты весьма тривиальны, кроме MockSessionState, где и хранится session storage (/Mock/Http/MockHttpSessionState.cs):
SXEOLF FODVV 0RFN+WWS6HVVLRQ6WDWH 0RFN +WWS6HVVLRQ6WDWH%DVH!
^
'LFWLRQDU\VWULQJ REMHFW! VHVVLRQ6WRUDJH
SXEOLF 0RFN+WWS6HVVLRQ6WDWH0RFN%HKDYLRU PRFN%HKDYLRU 0RFN%HKDYLRU 6WULFW
EDVHPRFN%HKDYLRU
^
VHVVLRQ6WRUDJH QHZ'LFWLRQDU\VWULQJ REMHFW!
WKLV6HWXS S ! S>,W ,V$Q\VWULQJ! @ 5HWXUQVVWULQJLQGH[ ! VHVVLRQ6WRUDJ H>LQGH[@
WKLV6HWXS S ! S$GG ,W ,V$Q\VWULQJ! ,W ,V$Q\REMHFW! &DOOEDFNVWULQJ REMHFW! QDPH REM !
^
LIVHVVLRQ6WRUDJH &RQWDLQV.H\ QDPH
^
VHVVLRQ6WRUDJH$GG QDPH REM
`
HOVH
^
VHVVLRQ6WRUDJH>QDPH@ REM
`
`
`
`
Создаем FakeAuthCookieProvider.cs (/Fake/FakeAuthCookieProvider.cs):
SXEOLF FODVV )DNH$XWK&RRNLH3URYLGHU ,$XWK&RRNLH3URYLGHU
^
>,QMHFW@
SXEOLF+WWS&RRNLH&ROOHFWLRQ &RRNLHV ^JHW VHW`
SXEOLF+WWS&RRNLH*HW&RRNLH VWULQJFRRNLH1DPH
^
UHWXUQ&RRNLHV *HW FRRNLH1DPH
`
SXEOLF YRLG 6HW&RRNLH+WWS&RRNLH FRRNLH
^
LI&RRNLHV *HW FRRNLH 1DPH QXOO
^
&RRNLHV 5HPRYH FRRNLH 1DPH
`
&RRNLHV$GG FRRNLH
`
`
Фух! Инициализируем это в UnitTestSetupFixture.cs (/Setup/UnitTestSetupFixture.cs):
SURWHFWHG YLUWXDO YRLG ,QLW$XWK6WDQGDUG.HUQHO NHUQHO
^
NHUQHO %LQG +WWS&RRNLH&ROOHFWLRQ! 7R +WWS&RRNLH&ROOHFWLRQ!
NHUQHO %LQG ,$XWK&RRNLH3URYLGHU! 7R )DNH$XWK&RRNLH3URYLGHU! ,Q6LQJOHWRQ6FRS H
NHUQHO %LQG ,$XWKHQWLFDWLRQ! 7R0HWKRG &XVWRP$XWKHQWLFDWLRQ! F !
^
YDUDXWK QHZ&XVWRP$XWKHQWLFDWLRQ
DXWK$XWK&RRNLH3URYLGHU NHUQHO *HW ,$XWK&RRNLH3URYLGHU!
UHWXUQDXWK
`
`
Заметим, что Bind происходит на SingletonScope(), т.е. единожды авторизовавшись в каком то тесте, мы в последующих тестах будем использовать эту же авторизацию.
Компилим и пытаемся с этим всем взлететь. Сейчас начнется магия…
Проверка валидации
Если мы просто вызовем чтото типа:
YDUUHJLVWHU8VHU QHZ8VHU9LHZ
^
(PDLO XVHU#VDPSOH FRP
3DVVZRUG
&RQILUP3DVVZRUG
$YDWDU3DWK ILOH QR LPDJH MSJ
%LUWKGDWH'D\
%LUWKGDWH0RQWK
%LUWKGDWH<HDU
&DSWFKD
`
YDUUHVXOW FRQWUROOHU 5HJLVWHU UHJLVWHU8VHU
То, вопервых, никакая неявная валидация не выполнится, а вовторых, у нас там есть session и мы ее не проинициализировали, она null и всё – ошибка. Так что проверку валидации (та, что в атрибутах) будем устраивать через отдельный класс. Назовем его Валидатор Валидаторович (/Tools/Validator.cs):
SXEOLF FODVV 9DOLGDWRU([FHSWLRQ ([FHSWLRQ
^
SXEOLF9DOLGDWLRQ$WWULEXWH$WWULEXWH ^JHW SULYDWH VHW`
SXEOLF 9DOLGDWRU([FHSWLRQ9DOLGDWLRQ([FHSWLRQ H[ 9DOLGDWLRQ$WWULEXWH DWWULEXWH
EDVHDWWULEXWH *HW7\SH 1DPH H[
^
$WWULEXWH DWWULEXWH
`
`
SXEOLF FODVV 9DOLGDWRU
^
SXEOLF VWDWLF YRLG9DOLGDWH2EMHFW 7! 7 REM
^
YDUW\SH W\SHRI7
YDUPHWD W\SH *HW&XVWRP$WWULEXWHVIDOVH2I7\SH 0HWDGDWD7\SH$WWULEXWH! )LUV W2U'HIDXOW
LIPHWD QXOO
^
W\SH PHWD 0HWDGDWD&ODVV7\SH
`
YDUW\SH$WWULEXWHV W\SH *HW&XVWRP$WWULEXWHVW\SHRI9DOLGDWLRQ$WWULEXWHWUX H2I7\SH 9DOLGDWLRQ$WWULEXWH!
YDUYDOLGDWLRQ&RQWH[W QHZ9DOLGDWLRQ&RQWH[W REM
IRUHDFK YDUDWWULEXWHLQW\SH$WWULEXWHV
^
WU\
^
DWWULEXWH 9DOLGDWH REM YDOLGDWLRQ&RQWH[W
`
FDWFK9DOLGDWLRQ([FHSWLRQ H[
^
WKURZ QHZ9DOLGDWRU([FHSWLRQ H[ DWWULEXWH
`
`
YDUSURSHUW\,QIR W\SH *HW3URSHUWLHV
IRUHDFK YDULQIRLQSURSHUW\,QIR
^
YDUDWWULEXWHV LQIR *HW&XVWRP$WWULEXWHVW\SHRI9DOLGDWLRQ$WWULEXWHWUX H2I7\SH 9DOLGDWLRQ$WWULEXWH!
IRUHDFK YDUDWWULEXWHLQDWWULEXWHV
^
YDUREM3URS,QIR REM *HW7\SH *HW3URSHUW\ LQIR 1DPH
WU\
^
DWWULEXWH 9DOLGDWH REM3URS,QIR *HW9DOXH REMQXOOYDOLGDWLRQ&RQWH [W
`
FDWFK9DOLGDWLRQ([FHSWLRQ H[
^
WKURZ QHZ9DOLGDWRU([FHSWLRQ H[ DWWULEXWH
`
`
`
`
`
Итак, что тут у нас происходит. Вначале мы получаем все атрибуты класса T, которые относятся к типу ValidationAttribute:
YDUW\SH$WWULEXWHV W\SH *HW&XVWRP$WWULEXWHVW\SHRI9DOLGDWLRQ$WWULEXWHWUXH2I7\SH 9DO LGDWLRQ$WWULEXWH!
YDUYDOLGDWLRQ&RQWH[W QHZ9DOLGDWLRQ&RQWH[W REM
IRUHDFK YDUDWWULEXWHLQW\SH$WWULEXWHV
^
WU\
^
DWWULEXWH 9DOLGDWH REM YDOLGDWLRQ&RQWH[W
`
FDWFK9DOLGDWLRQ([FHSWLRQ H[
^
WKURZ QHZ9DOLGDWRU([FHSWLRQ H[ DWWULEXWH
`
`
Потом аналогично для каждого свойства:
YDUSURSHUW\,QIR W\SH *HW3URSHUWLHV
IRUHDFK YDULQIRLQSURSHUW\,QIR
^
YDUDWWULEXWHV LQIR *HW&XVWRP$WWULEXWHVW\SHRI9DOLGDWLRQ$WWULEXWHWUX H2I7\SH 9DOLGDWLRQ$WWULEXWH!
IRUHDFK YDUDWWULEXWHLQDWWULEXWHV
^
YDUREM3URS,QIR REM *HW7\SH *HW3URSHUW\ LQIR 1DPH
WU\
^
DWWULEXWH 9DOLGDWH REM3URS,QIR *HW9DOXH REMQXOOYDOLGDWLRQ&RQWH [W
`
FDWFK9DOLGDWLRQ([FHSWLRQ H[
^
WKURZ QHZ9DOLGDWRU([FHSWLRQ H[ DWWULEXWH
`
`
`
Если валидация не проходит, то происходит исключение, и мы оборачиваем его в ValidatorException, передавая еще и атрибут, по которому произошло исключение. Теперь по поводу капчи и Session. Мы должны контроллеру передать контекст
(MockHttpContext):
YDUFRQWUROOHU 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH$UHDV 'HIDXOW &RQWUROOHUV 8VHU&RQWUR OOHU!
YDUKWWS&RQWH[W QHZ0RFN+WWS&RQWH[W 2EMHFW
&RQWUROOHU&RQWH[W FRQWH[W QHZ&RQWUROOHU&RQWH[WQHZ5HTXHVW&RQWH[W KWWS&RQWH[ WQHZ5RXWH'DWD FRQWUROOHU
FRQWUROOHU &RQWUROOHU&RQWH[W FRQWH[W
FRQWUROOHU 6HVVLRQ$GG &DSWFKD,PDJH &DSWFKD9DOXH.H\
И теперь всё вместе:
>7HVW@
SXEOLF YRLG ,QGH[B5HJLVWHU8VHU:LWK'LIIHUHQW3DVVZRUGB([FHSWLRQ&RPSDUH
^
LQLW
YDUFRQWUROOHU 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH$UHDV 'HIDXOW &RQWUROOHU V 8VHU&RQWUROOHU!
YDUKWWS&RQWH[W QHZ0RFN+WWS&RQWH[W 2EMHFW
&RQWUROOHU&RQWH[W FRQWH[W QHZ&RQWUROOHU&RQWH[WQHZ5HTXHVW&RQWH[W KWWS&RQWH[ WQHZ5RXWH'DWD FRQWUROOHU
FRQWUROOHU &RQWUROOHU&RQWH[W FRQWH[W
DFW
YDUUHJLVWHU8VHU9LHZ QHZ8VHU9LHZ
^
(PDLO XVHU#VDPSOH FRP
3DVVZRUG
&RQILUP3DVVZRUG
$YDWDU3DWK ILOH QR LPDJH MSJ
%LUWKGDWH'D\
%LUWKGDWH0RQWK
%LUWKGDWH<HDU
&DSWFKD
`
WU\
^
9DOLGDWRU 9DOLGDWH2EMHFW 8VHU9LHZ! UHJLVWHU8VHU9LHZ
`
FDWFK([FHSWLRQ H[
^
$VVHUW ,V,QVWDQFH2I 9DOLGDWRU([FHSWLRQ! H[
$VVHUW ,V,QVWDQFH2I 6\VWHP &RPSRQHQW0RGHO 'DWD$QQRWDWLRQV &RPSDUH$WWULEXWH!
9DOLGDWRU([FHSWLRQ H[$WWULEXWH
`
`
Запускаем, и всё получилось. Но капча проверяется непосредственно в методе контроллера. Специально для капчи:
>7HVW@
SXEOLF YRLG ,QGH[B5HJLVWHU8VHU:LWK:URQJ&DSWFKDB0RGHO6WDWH:LWK(UURU
^
LQLW
YDUFRQWUROOHU 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH$UHDV 'HIDXOW &RQWUROOHU V 8VHU&RQWUROOHU!
YDUKWWS&RQWH[W QHZ0RFN+WWS&RQWH[W 2EMHFW
&RQWUROOHU&RQWH[W FRQWH[W QHZ&RQWUROOHU&RQWH[WQHZ5HTXHVW&RQWH[W KWWS&RQWH[ WQHZ5RXWH'DWD FRQWUROOHU
FRQWUROOHU &RQWUROOHU&RQWH[W FRQWH[W
FRQWUROOHU 6HVVLRQ$GG &DSWFKD,PDJH &DSWFKD9DOXH.H\
DFW
YDUUHJLVWHU8VHU9LHZ QHZ8VHU9LHZ
^
(PDLO XVHU#VDPSOH FRP
3DVVZRUG
&RQILUP3DVVZRUG
$YDWDU3DWK ILOH QR LPDJH MSJ
%LUWKGDWH'D\
%LUWKGDWH0RQWK
%LUWKGDWH<HDU
&DSWFKD
`
YDUUHVXOW FRQWUROOHU 5HJLVWHU UHJLVWHU8VHU9LHZ
$VVHUW$UH(TXDOɌɟɤɫɬ ɫ ɤɚɪɬɢɧɤɢ ɜɜɟɞɟɧ ɧɟɜɟɪɧɨFRQWUROOHU 0RGHO6WDWH>&DSWF KD@ (UURUV> @ (UURU0HVVDJH
`
Круто!
Проверка авторизации
Например, мы должны проверить, что, если я захожу не под админом, то в авторизованную часть (в контроллер, помеченный атрибутом [Authorize(Roles=“admin”)]) – обычному польвателю не дадут войти. Есть отличный способ это проверить. Обратим внимание на класс ControllerActionInvoker и отнаследуем его для вызовов
(/Fake/FakeControllerActionInvoker.cs + FakeValueProvider.cs):
SXEOLF FODVV )DNH9DOXH3URYLGHU
^
SURWHFWHG'LFWLRQDU\VWULQJ REMHFW! 9DOXHV ^JHW VHW`
SXEOLF )DNH9DOXH3URYLGHU
^
9DOXHV QHZ'LFWLRQDU\VWULQJ REMHFW!
`
SXEOLF REMHFW WKLV>VWULQJLQGH[@
^
JHW
^
LI9DOXHV &RQWDLQV.H\ LQGH[
^
UHWXUQ9DOXHV>LQGH[@
`
UHWXUQ QXOO
`
VHW
^
LI9DOXHV &RQWDLQV.H\ LQGH[
^
9DOXHV>LQGH[@ YDOXH
`
HOVH
^
9DOXHV$GG LQGH[YDOXH
`
`
`
`
SXEOLF FODVV )DNH&RQWUROOHU$FWLRQ,QYRNHU 7([SHFWHG5HVXOW!&RQWUROOHU$FWLRQ,QYRNHU ZKHUH 7([SHFWHG5HVXOW $FWLRQ5HVXOW
^
SURWHFWHG)DNH9DOXH3URYLGHU )DNH9DOXH3URYLGHU ^JHW VHW`
SXEOLF )DNH&RQWUROOHU$FWLRQ,QYRNHU
^
)DNH9DOXH3URYLGHU QHZ)DNH9DOXH3URYLGHU
`
SXEOLF )DNH&RQWUROOHU$FWLRQ,QYRNHU)DNH9DOXH3URYLGHU IDNH9DOXH3URYLGHU
^
)DNH9DOXH3URYLGHU IDNH9DOXH3URYLGHU
`
SURWHFWHG RYHUULGH $FWLRQ([HFXWHG&RQWH[W,QYRNH$FWLRQ0HWKRG:LWK)LOWHUV&RQWUROOHU&R QWH[W FRQWUROOHU&RQWH[W ,/LVW ,$FWLRQ)LOWHU! ILOWHUV$FWLRQ'HVFULSWRU DFWLRQ'HVFULSWRU , 'LFWLRQDU\VWULQJ REMHFW! SDUDPHWHUV
^
UHWXUQ EDVH,QYRNH$FWLRQ0HWKRG:LWK)LOWHUV FRQWUROOHU&RQWH[W ILOWHUV DFWLRQ'HV FULSWRU SDUDPHWHUV
`
SURWHFWHG RYHUULGH REMHFW *HW3DUDPHWHU9DOXH&RQWUROOHU&RQWH[W FRQWUROOHU&RQWH[W 3D UDPHWHU'HVFULSWRU SDUDPHWHU'HVFULSWRU
^
YDUREM )DNH9DOXH3URYLGHU>SDUDPHWHU'HVFULSWRU 3DUDPHWHU1DPH@
LIREM QXOO
^
UHWXUQREM
`
UHWXUQSDUDPHWHU'HVFULSWRU 'HIDXOW9DOXH
`
SURWHFWHG RYHUULGH YRLG ,QYRNH$FWLRQ5HVXOW&RQWUROOHU&RQWH[W FRQWUROOHU&RQWH[W$FW LRQ5HVXOW DFWLRQ5HVXOW
^
$VVHUW ,V,QVWDQFH2I 7([SHFWHG5HVXOW! DFWLRQ5HVXOW
`
`
По сути это «вызывальщик» actionметодов контроллеров, где Generic класс – это ожидаемый класс результата. В случае неавторизации это будет HttpUnauthorizedResult.
Сделаем тест (/Test/Admin/HomeControllerTest.cs):
>7HVW)L[WXUH@
SXEOLF FODVV $GPLQ+RPH&RQWUROOHU7HVW
^
>7HVW@
SXEOLF YRLG ,QGH[B1RW$XWKRUL]H*HW'HIDXOW9LHZB5HGLUHFW7R/RJLQ3DJH
^
YDUDXWK 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH ,$XWKHQWLFDWLRQ!
DXWK /RJLQFKHUQLNRY#JPDLO FRP SDVVZRUG IDOVH
YDUKWWS&RQWH[W QHZ0RFN+WWS&RQWH[W DXWK 2EMHFW
YDUFRQWUROOHU 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH$UHDV$GPLQ &RQWUROOHU V +RPH&RQWUROOHU!
YDUURXWH QHZ5RXWH'DWD
URXWH 9DOXHV$GGFRQWUROOHU +RPH
URXWH 9DOXHV$GGDFWLRQ ,QGH[
URXWH 9DOXHV$GGDUHD $GPLQ
&RQWUROOHU&RQWH[W FRQWH[W QHZ&RQWUROOHU&RQWH[WQHZ5HTXHVW&RQWH[W KWWS&RQWH[ W URXWH FRQWUROOHU
FRQWUROOHU &RQWUROOHU&RQWH[W FRQWH[W
YDUFRQWUROOHU$FWLRQ,QYRNHU QHZ)DNH&RQWUROOHU$FWLRQ,QYRNHU +WWS8QDXWKRUL]HG5 HVXOW!
YDUUHVXOW FRQWUROOHU$FWLRQ,QYRNHU ,QYRNH$FWLRQ FRQWUROOHU &RQWUROOHU&RQWH[W ,QGH[
`
`
Запускаем тест, он проходит. Сделаем, чтобы авторизация была под пользователем admin и будем ожидать получение ViewResult:
>7HVW@
SXEOLF YRLG ,QGH[B$GPLQ$XWKRUL]HB*HW9LHZ5HVXOW
^
YDUDXWK 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH ,$XWKHQWLFDWLRQ!
DXWK /RJLQDGPLQ SDVVZRUG IDOVH
YDUKWWS&RQWH[W QHZ0RFN+WWS&RQWH[W DXWK 2EMHFW
YDUFRQWUROOHU 'HSHQGHQF\5HVROYHU &XUUHQW *HW6HUYLFH$UHDV$GPLQ &RQWUROOHU V +RPH&RQWUROOHU!
YDUURXWH QHZ5RXWH'DWD
URXWH 9DOXHV$GGFRQWUROOHU +RPH
URXWH 9DOXHV$GGDFWLRQ ,QGH[
URXWH 9DOXHV$GGDUHD $GPLQ
&RQWUROOHU&RQWH[W FRQWH[W QHZ&RQWUROOHU&RQWH[WQHZ5HTXHVW&RQWH[W KWWS&RQWH[ W URXWH FRQWUROOHU
FRQWUROOHU &RQWUROOHU&RQWH[W FRQWH[W
YDUFRQWUROOHU$FWLRQ,QYRNHU QHZ)DNH&RQWUROOHU$FWLRQ,QYRNHU 9LHZ5HVXOW!
YDUUHVXOW FRQWUROOHU$FWLRQ,QYRNHU ,QYRNH$FWLRQ FRQWUROOHU &RQWUROOHU&RQWH[W ,QGH[
`
Так же прошли. Молодцом.
На этом давайте остановимся и подумаем, чего мы достигли. Мы можем оттестировать любой контроллер, проверить правильность любой валидации, проверку прав пользователя. Но это касается только контроллера. А как же работа с моделью? Да, мы можем проверить, что вызывается метод репозитория, но на этом всё. Да, мы можем написать Mockметоды для добавления, изменения, удаления, но как это поможет решить ту проблему, о которой я писал вначале главы? Как мы заметим, что чтото не так при упущении поля с тегом? В хрестоматийном примере NerdDinner тесты не покрывают эту область.
Есть IRepository, есть SqlRepository, есть MockRepository. И всё что находится в
SqlRepository – это не покрытая тестами область. А там может быть реализовано очень многое. Что же делать? К чему этот TDD?
Интегрированное тестирование
Идея будет совершенно безумной, мы будем использовать и проверять уже существующий