单元测试WebForm的UI逻辑及文件上传
BS系统中的UI部分的逻辑测试,最首要的就是要模拟请求(Request)和输出(Response),而WebForm又跟MVC不一样,后者的Response,Request等HTTP上下文对象均有接口支持,很容易模拟,而查看WebForm的对应对象,如Response,我们首先看到的声明就是:
public sealed class HttpResponse
无接口,并且是sealed,换句话说,我们要测试一个如下的的Code-Behind函数的逻辑正确性,该怎么测试:
protected void Page_Load(object sender, EvengArgs e) { this.Response.Write("test u"); }
好在FCL中有现成的包装类HttpResponseWrapper来解决我们的烦恼(不然得自己写包装类),我们将上面的代码改成如下的形式,它就可以变得可测试:
protected HttpResponseBase _response; protected override void OnPreLoad(EventArgs e) { base.OnPreLoad(e); _response = new HttpResponseWrapper(this.Response); } protected void Page_Load(object sender, EvengArgs e) { _response.Write("test u"); }
上面的重构,并不影响原有功能的实现,同时却将代码修改成可测试的,
1:首先,this.Response被替代成可以被模拟的HttpResponseBase;
2:其次,方法Page_Load内部不再负责生成被依赖的对象的生成;
即,我们将_response的生成放到方法的外部。这个外部,可以是构造器等,不过由于webform本身的特殊性,在构造器中,this.Response上下文还不可用,所以在这个例子中放到了OnPreLoad中。
接下来看看测试类的编写,先说点额外话,Page_Load是protected的,要让测试类可以访问到它,需改成public或者干脆让测试类继承我们的当前页面,这里采用的是后者:
[TestClass] public class _DefaultTest: _Default ////很明显,这里测试的是Default.aspx.cs这个类 { public Mock<HttpResponseBase> FakeResponse; [TestMethod] public void LoadOk() { FakeResponse = new Mock<HttpResponse>(); StringWriter sw = new StringWriter(); FakeResponse.SetupGet(x=>x.OutPut).Returns(sw); this._response = FakeResponse.Object; FakeResponse.Setup(x=>x.Write(It.IsAny<string>())).CallBack<string((x)=> { sw.Write(x); sw.Flush(); } this.Page_Load(null,null); Assert.AreEqual( “test u”, (FakeResponse.Object.Output as StringWriter).ToString(); } }
首先,这个测试方法要测试的是Page_Load方法的逻辑正确性,即:执行完毕,客户端输出“test u”,它所依赖的是Response.Write这个方法,这个方法是向客户端浏览器输出文本,显然我们的单元测试中是不允许网络传输的,所以我们只能将Response.Write这个行为模拟到内存中去,测试代码中很大一部分就是模拟这个行为。要深刻理会的是:
1:我们要验证的是Page_Load行为的准确性,而不是验证Response.Write的准确性;
2:Response.Write是Page_Load行为中的一种依赖,测试方法要对这种依赖进行模拟;
备注:Response.Write内部使用TextWriter,模拟的时候,我们使用了TextWriter的一个子类,StringWriter。
上面的例子很简单,进一步看,我们如何来写针对文件上传的单元测试。假设上传的逻辑是这样的:
Public void UploadFile() { Var file = _request.Files[0]; If(file.contentLength==0 || file.ContentLength > 5 * 1024 * 1024) { throw new ArgumentException(); } File.SaveAs(file.FileName); }
则,其中一个测试用例的代码应该象如下这样:
[TestMethod] public void ThrowExceptionIfFilelengthInvalid() { FakeRequest = new Mock<HttpRequestBase>(); FakeFiles = new Mock<HttpFileCollectionBase>(); FakeFile = new Mock<HttpPostedFileBase>(); FakeFile.SetupGet(x=> x.ContentLength).Returns(6 * 1024 * 1024); FakeFiles.SetupGet( x=> x[0]).Returns(FakeFile.Object); FakeRequest.SetupGet( x=>x.Files).Returns(FakeFiles.Object); this._request = FakeRequest.Object; try { this.UploadFile(); Assert.Fail(); } catch(ArugmentOutOfRangeException) { } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
2010-11-30 C#特性Attribute的实际应用之:为应用程序提供多个版本