2018-03-16 14:16:26 3226瀏覽
一、模型狀態(tài)-ModelState
我理解的ModelState是微軟在ASP.NETMVC中提出的一種新機(jī)制,它主要實(shí)現(xiàn)以下幾個(gè)功能:
1.保存客戶端傳過(guò)來(lái)的數(shù)據(jù),如果驗(yàn)證不通過(guò),把數(shù)據(jù)返回到客戶端,這樣可以保存用戶輸入,不需要重新輸入。
2.驗(yàn)證數(shù)據(jù),以及保存數(shù)據(jù)對(duì)應(yīng)的錯(cuò)誤信息。
3.微軟的一種DRY(Don'tRepeatYourself)設(shè)計(jì),通過(guò)ModelState可以做服務(wù)端驗(yàn)證,同時(shí)可以配合jqueryvalidation生成前端數(shù)據(jù)驗(yàn)證。
但是在WebAPI里面,ModelState的主要功能就只剩下第2點(diǎn)了。
需要注意的是,ModelState一般只做輸入驗(yàn)證,一些其他的業(yè)務(wù)驗(yàn)證還有要在特定的地方進(jìn)行處理。
二、數(shù)據(jù)注解-DataAnnotations
數(shù)據(jù)注解可以理解為驗(yàn)證數(shù)據(jù)的邏輯或方法,微軟本身有提供一批數(shù)據(jù)注解,當(dāng)然我們也可以自定義數(shù)據(jù)注解,以下是微軟提供的常見(jiàn)的數(shù)據(jù)注解:
1.Required-非空驗(yàn)證。
當(dāng)一個(gè)輸入是null時(shí)會(huì)引發(fā)一個(gè)驗(yàn)證錯(cuò)誤。
當(dāng)屬性類型是string的時(shí)候,如果設(shè)置了AllowEmptyStrings=false(默認(rèn)為false),那么輸入空字符串或者空格,也會(huì)引發(fā)一個(gè)驗(yàn)證錯(cuò)誤。
[Required]
publicstringName{get;set;}
[Required(AllowEmptyStrings=true)]
publicstringExchange{get;set;}
2.StringLength-長(zhǎng)度驗(yàn)證。
當(dāng)輸入大于指定最大長(zhǎng)度,或者小于最大指定長(zhǎng)度時(shí),會(huì)引發(fā)一個(gè)驗(yàn)證錯(cuò)誤。
[StringLength(100)]
publicstringSymbol{get;set;}
[StringLength(100,MinimumLength=10)]
publicstringName{get;set;}
3.RegularExpression-正則表達(dá)式驗(yàn)證。
當(dāng)輸入內(nèi)容不滿足指定的正則表達(dá)式時(shí),會(huì)引發(fā)一個(gè)驗(yàn)證錯(cuò)誤。
注:在.NETFramework4.6.1添加了一個(gè)MatchTimeoutInMilliseconds屬性,用來(lái)設(shè)定正則表達(dá)時(shí)驗(yàn)證時(shí)長(zhǎng)。如超時(shí),則拋出RegexMatchTimeoutException異常。
[RegularExpression("yourexpression")]
publicstringSymbol{get;set;}
4.Range-值范圍驗(yàn)證
當(dāng)輸入的值小于最小值或者大于最大值時(shí),會(huì)引發(fā)一個(gè)驗(yàn)證錯(cuò)誤,這里要求驗(yàn)證字段的類型需要實(shí)現(xiàn)IComparable接口。
[Range(10,100)]
publicdoubleOpenPrice{get;set;}
[Range(typeof(double),"10","100")]
publicdoubleClosePrice{get;set;}
5.Compare-對(duì)比驗(yàn)證
確保對(duì)象兩個(gè)屬性擁有相同的值。如果兩個(gè)值不同,會(huì)引發(fā)一個(gè)驗(yàn)證錯(cuò)誤。
publicstringName{get;set;}
[Compare("Name")]
publicstringConfirmName{get;set;}
6.Remote-遠(yuǎn)程調(diào)用驗(yàn)證
Remote可以利用服務(wù)端回調(diào)函數(shù)執(zhí)行客戶端的驗(yàn)證邏輯。
注:該數(shù)據(jù)注解是ASP.NETMVC特有的注解,在WebApi中無(wú)此注解。
[Remote("CheckName","Account"]
publicstringUserName{get;set;}
publicclassAccountController:Controller
{
publicJsonResultCheckName(stringname)
{
returnJson(true);
}
}
三、自定義數(shù)據(jù)注解
如果覺(jué)得微軟提供的數(shù)據(jù)注解不夠用,也可以自己寫數(shù)據(jù)注解,只需要繼承ValidationAttribute,并復(fù)寫IsValid方法。
下面是一個(gè)來(lái)自《ASP.NETMVC5高級(jí)編程》的一個(gè)例子MaxWordsAttribute,用于限制屬性的單詞個(gè)數(shù)。
publicclassMaxWordsAttribute:ValidationAttribute
{
privatereadonlyint_maxWords;
publicMaxWordsAttribute(intmaxWords)
{
_maxWords=maxWords;
}
protectedoverrideValidationResultIsValid(objectvalue,ValidationContextvalidationContext)
{
if(value!=null)
{
varvalueAsString=value.ToString();
if(valueAsString.Split('').Length>_maxWords)
{
returnnewValidationResult("Toomanywords!");
}
}
returnValidationResult.Success;
}
}
[Required]
[MaxWords(2)]
publicstringName{get;set;}
[HttpPost]
publicIHttpActionResultCreate(Stockstock)
{
if(!ModelState.IsValid)
{
returnBadRequest(ModelState);
}
returnCreatedAtRoute("Get",new{symbol=stock.Symbol},stock);
}
SwashbuckleHelpPage測(cè)試效果如下:
四、全局?jǐn)?shù)據(jù)驗(yàn)證
我們?cè)谑褂脭?shù)據(jù)驗(yàn)證的時(shí)候,往往會(huì)出現(xiàn)許多重復(fù)的代碼,如下圖:
有沒(méi)有辦法減少這些重復(fù)的代碼呢?我從“ModelValidationinASP.NETWebAPI”這篇文章中找到了方法。
首先,我們需要寫一個(gè)GlobalActionFilterAttribute。
publicclassGlobalActionFilterAttribute:ActionFilterAttribute
{
publicoverridevoidOnActionExecuting(HttpActionContextactionContext)
{
if(actionContext.ModelState.IsValid==false)
{
actionContext.Response=actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,actionContext.ModelState);
}
}
}
然后,在WebApiConfig里注冊(cè)一下這個(gè)Attribute。
publicstaticvoidRegister(HttpConfigurationconfig)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi","api/{controller}/{id}",new{id=RouteParameter.Optional});
//registerthecustomactionfilter
config.Filters.Add(newGlobalActionFilterAttribute());
}
那么,我們把Controller中的數(shù)據(jù)驗(yàn)證注釋掉,依舊會(huì)得到相同的效果。
如果想只對(duì)Post請(qǐng)求進(jìn)行驗(yàn)證,可以在GlobalActionFilterAttribute加對(duì)請(qǐng)求方式的判斷:
publicclassGlobalActionFilterAttribute:ActionFilterAttribute
{
publicoverridevoidOnActionExecuting(HttpActionContextactionContext)
{
//Ifyouonlywanttovalidatethepostrequest.
if(actionContext.Request.Method!=HttpMethod.Post)
{
return;
}
if(actionContext.ModelState.IsValid==false)
{
actionContext.Response=actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,actionContext.ModelState);
}
}
}
如果某些Controller或Action需要繞過(guò)數(shù)據(jù)驗(yàn)證,那么可以這么實(shí)現(xiàn):
1.定義一個(gè)BypassModelStateValidationAttribute
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,Inherited=false)]
publicsealedclassBypassModelStateValidationAttribute:Attribute
{
}
2.在不需要驗(yàn)證的Controller或者Action上加這個(gè)Attribute
[HttpPut]
[BypassModelStateValidation]
publicIHttpActionResultUpdate(Stockstock)
{
//if(!ModelState.IsValid)
//{
//returnBadRequest(ModelState);
//}
returnStatusCode(HttpStatusCode.NoContent);
}
3.在GlobalActionFilterAttribute加對(duì)BypassModelStateValidationAttribute的判斷:
publicclassGlobalActionFilterAttribute:ActionFilterAttribute
{
publicoverridevoidOnActionExecuting(HttpActionContextactionContext)
{
//Ifyouonlywanttovalidatethepostrequest.
if(actionContext.Request.Method!=HttpMethod.Post)
{
return;
}
varpassby=actionContext.ActionDescriptor.GetCustomAttributes<BypassModelStateValidationAttribute>().Any()||
actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<BypassModelStateValidationAttribute>().Any();
if(passby)
{
return;
}
if(actionContext.ModelState.IsValid==false)
{
actionContext.Response=actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,actionContext.ModelState);
}
}
}
五、單元測(cè)試
我使用BDD的風(fēng)格編寫單元測(cè)試,關(guān)于BDD的詳細(xì)信息,可查看我之前的文章《行為驅(qū)動(dòng)開(kāi)發(fā)(BDD)實(shí)踐示例》(http://www.cnblogs.com/Erik_Xu/p/5297981.html)。
對(duì)于全局?jǐn)?shù)據(jù)驗(yàn)證,我設(shè)計(jì)了3個(gè)測(cè)試用例。
1.非Post請(qǐng)求不做驗(yàn)證-HttpMethodNotMatched
feature描述:
測(cè)試代碼:
[Binding]
[Scope(Scenario=@"HttpMethodNotMatched")]
publicclassHttpMethodNotMatchedTest:GlobalActionFilterAttributeTests
{
[Given(@"非Post方式的請(qǐng)求")]
publicvoidGiven()
{
HttpActionContext.Request.Method=HttpMethod.Get;
}
[When(@"執(zhí)行OnActionExecuting方法")]
publicvoidWhen()
{
GlobalActionFilterAttribute.OnActionExecuting(HttpActionContext);
}
[Then(@"Response為空")]
publicvoidThen()
{
Assert.IsNull(HttpActionContext.Response);
}
}
2.設(shè)置了跳過(guò)驗(yàn)證-BypassModelStateValidation
feature描述:
測(cè)試代碼:
[Binding]
[Scope(Scenario=@"BypassModelStateValidation")]
publicclassBypassModelStateValidationTest:GlobalActionFilterAttributeTests
{
[Given(@"BypassModelStateValidationAttribute")]
publicvoidGiven()
{
HttpActionContext.Request.Method=HttpMethod.Post;
HttpActionContext.ActionDescriptor=ActionDescriptorMock.Object;
ActionDescriptorMock.Setup(m=>m.GetCustomAttributes<BypassModelStateValidationAttribute>()).Returns(newCollection<BypassModelStateValidationAttribute>(new[]{newBypassModelStateValidationAttribute()}));
HttpActionContext.ControllerContext.ControllerDescriptor=ControllerDescriptorMock.Object;
ControllerDescriptorMock.Setup(m=>m.GetCustomAttributes<BypassModelStateValidationAttribute>()).Returns(newCollection<BypassModelStateValidationAttribute>());
}
[When(@"執(zhí)行OnActionExecuting方法")]
publicvoidWhen()
{
GlobalActionFilterAttribute.OnActionExecuting(HttpActionContext);
}
[Then(@"Response為空")]
publicvoidThen()
{
Assert.IsNull(HttpActionContext.Response);
}
}
3.驗(yàn)證不通過(guò)-ModelStateInvalid
feature描述:
測(cè)試代碼:
[Binding]
[Scope(Scenario=@"ModelStateInvalid")]
publicclassModelStateInvalidTest:GlobalActionFilterAttributeTests
{
[Given(@"ModelState錯(cuò)誤信息")]
publicvoidGiven()
{
HttpActionContext.Request.Method=HttpMethod.Post;
HttpActionContext.ActionDescriptor=ActionDescriptorMock.Object;
ActionDescriptorMock.Setup(m=>m.GetCustomAttributes<BypassModelStateValidationAttribute>()).Returns(newCollection<BypassModelStateValidationAttribute>());
HttpActionContext.ControllerContext.ControllerDescriptor=ControllerDescriptorMock.Object;
ControllerDescriptorMock.Setup(m=>m.GetCustomAttributes<BypassModelStateValidationAttribute>()).Returns(newCollection<BypassModelStateValidationAttribute>());
HttpActionContext.ModelState.AddModelError("stock.Name","TheNamefieldisrequired.");
}
[When(@"執(zhí)行OnActionExecuting方法")]
publicvoidWhen()
{
GlobalActionFilterAttribute.OnActionExecuting(HttpActionContext);
}
[Then(@"返回BadRequest")]
publicvoidThen()
{
Assert.AreEqual(HttpStatusCode.BadRequest,HttpActionContext.Response.StatusCode);
}
}
單元測(cè)試結(jié)果:
說(shuō)明:
GlobalActionFilterAttributeTests是單元測(cè)試的父類,公共的部分可以抽取到這里。其中ContextUtil是微軟源碼中的測(cè)試輔助類。
publicclassGlobalActionFilterAttributeTests
{
protectedreadonlyMock<HttpActionDescriptor>ActionDescriptorMock=newMock<HttpActionDescriptor>();
protectedreadonlyMock<HttpControllerDescriptor>ControllerDescriptorMock=newMock<HttpControllerDescriptor>();
protectedHttpActionContextHttpActionContext;
protectedGlobalActionFilterAttributeGlobalActionFilterAttribute;
publicGlobalActionFilterAttributeTests()
{
HttpActionContext=ContextUtil.CreateActionContext();
GlobalActionFilterAttribute=newGlobalActionFilterAttribute();
}
}
以上就是關(guān)于軟件測(cè)試培訓(xùn)之Web API數(shù)據(jù)驗(yàn)證與單元測(cè)試的詳細(xì)介紹,最后想要了解更多關(guān)于軟件測(cè)試培訓(xùn)發(fā)展前景趨勢(shì),請(qǐng)關(guān)注扣丁學(xué)堂官網(wǎng)、微信等平臺(tái),扣丁學(xué)堂IT職業(yè)在線學(xué)習(xí)教育平臺(tái)為您提供權(quán)威的軟件測(cè)試視頻教程系統(tǒng),通過(guò)千鋒扣丁學(xué)堂金牌講師在線錄制的軟件測(cè)試在線視頻教程,讓你快速掌握軟件測(cè)試從入門到精通開(kāi)發(fā)實(shí)戰(zhàn)技能。
【關(guān)注微信公眾號(hào)獲取更多學(xué)習(xí)資料】
查看更多關(guān)于“軟件測(cè)試技術(shù)資訊”的相關(guān)文章>>