单元测试不可测试那些类(无抽象、静态类、静态方法)
2013-11-27 14:03
495 查看
实际上“单元测试不可测试那些类(无抽象、静态类、静态方法)”是个伪命题,因为事实是:无抽象、静态类、静态方法都是不可单元测试的。那么,如果我们要写出可测试的代码,又要用到这些静态类等,该怎么办,实际上我们需要两个步骤:
1:为它们写一个包装类,让这个包装类是抽象的(继承自接口,或者抽象类,或者方法本身是Virtual的);
2:通知客户端程序员,使用包装类来代替原先的静态类来写业务逻辑;
实际上,微软也是这么干的,我在上一篇博文《单元测试WebForm的UI逻辑及文件上传》写到,最典型的不可测试类,那就是WebForm架构的网站中,对Response等的模拟。查看Response这个类:
很明显,如果我们在某个WebForm的后台方法中,直接使用它的话:
该后台代码逻辑就无法进行单元测试了,因为类似MOQ的框架所依赖的是代码本身具有可被重写行,如果某个类本身是静态的,就无法在运行时用模拟类替换掉实际类。
所以,写一个包装类吧,我们看到微软为Response写了一个包装类,为HttpResponseWrapper:
View Code
光从代码本身的角度来说,可以说这个类什么事情也没做,但是它为我们提供了一个抽象体系(从抽象类HttpResponseBase继承),这样的话,我们的客户端代码就可以改写为:
OK,可测试了,因为我们可以在某个地方注入依赖给_response了。
注意,包装类的撰写,还可以使用接口,或者干脆也仅仅只是一个类,只要让被包装的这个同名的方法是virtual的就可以了。
另外,不可测试的那些类,可能往往已经作为稳定版本提交给客户了,我们就不能修改源代码然后告诉客户说你替换下我们的DLL吧,所以,为了让客户端程序员找到我们的包装类,可以让包装类以及被包装的类使用同一个命名空间。
1:为它们写一个包装类,让这个包装类是抽象的(继承自接口,或者抽象类,或者方法本身是Virtual的);
2:通知客户端程序员,使用包装类来代替原先的静态类来写业务逻辑;
实际上,微软也是这么干的,我在上一篇博文《单元测试WebForm的UI逻辑及文件上传》写到,最典型的不可测试类,那就是WebForm架构的网站中,对Response等的模拟。查看Response这个类:
namespace System.Web { public sealed class HttpResponse { ... } }
很明显,如果我们在某个WebForm的后台方法中,直接使用它的话:
protected void Page_Load(object sender, EvengArgs e) { this.Response.Write("test u"); }
该后台代码逻辑就无法进行单元测试了,因为类似MOQ的框架所依赖的是代码本身具有可被重写行,如果某个类本身是静态的,就无法在运行时用模拟类替换掉实际类。
所以,写一个包装类吧,我们看到微软为Response写了一个包装类,为HttpResponseWrapper:
View Code
namespace System.Web { [TypeForwardedFrom("System.Web.Abstractions, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public class HttpResponseWrapper : HttpResponseBase { public override bool Buffer { get { } set { } } public override bool BufferOutput { get { } set { } } public override HttpCachePolicyBase Cache { get { } } public override string CacheControl { get { } set { } } public override string Charset { get { } set { } } public override CancellationToken ClientDisconnectedToken { get { } } public override Encoding ContentEncoding { get { } set { } } public override string ContentType { get { } set { } } public override HttpCookieCollection Cookies { get { } } public override int Expires { get { } set { } } public override DateTime ExpiresAbsolute { get { } set { } } public override Stream Filter { get { } set { } } public override NameValueCollection Headers { get { } } public override Encoding HeaderEncoding { get { } set { } } public override bool IsClientConnected { get { } } public override bool IsRequestBeingRedirected { get { } } public override TextWriter Output { get { } set { } } public override Stream OutputStream { get { } } public override string RedirectLocation { get { } set { } } public override string Status { get { } set { } } public override int StatusCode { get { } set { } } public override string StatusDescription { get { } set { } } public override int SubStatusCode { get { } set { } } public override bool SupportsAsyncFlush { get { } } public override bool SuppressContent { get { } set { } } public override bool SuppressFormsAuthenticationRedirect { get { } set { } } public override bool TrySkipIisCustomErrors { get { } set { } } public HttpResponseWrapper(HttpResponse httpResponse) { } public override void AddCacheItemDependency(string cacheKey) { } public override void AddCacheItemDependencies(ArrayList cacheKeys) { } public override void AddCacheItemDependencies(string[] cacheKeys) { } public override void AddCacheDependency(params CacheDependency[] dependencies) { } public override void AddFileDependency(string filename) { } public override void AddFileDependencies(ArrayList filenames) { } public override void AddFileDependencies(string[] filenames) { } public override void AddHeader(string name, string value) { } public override void AppendCookie(HttpCookie cookie) { } public override void AppendHeader(string name, string value) { } public override void AppendToLog(string param) { } public override string ApplyAppPathModifier(string virtualPath) { } public override IAsyncResult BeginFlush(AsyncCallback callback, object state) { } public override void BinaryWrite(byte[] buffer) { } public override void Clear() { } public override void ClearContent() { } public override void ClearHeaders() { } public override void Close() { } public override void DisableKernelCache() { } public override void DisableUserCache() { } public override void End() { } public override void EndFlush(IAsyncResult asyncResult) { } public override void Flush() { } public override void Pics(string value) { } public override void Redirect(string url) { } public override void Redirect(string url, bool endResponse) { } public override void RedirectPermanent(string url) { } public override void RedirectPermanent(string url, bool endResponse) { } public override void RedirectToRoute(object routeValues) { } public override void RedirectToRoute(string routeName) { } public override void RedirectToRoute(RouteValueDictionary routeValues) { } public override void RedirectToRoute(string routeName, object routeValues) { } public override void RedirectToRoute(string routeName, RouteValueDictionary routeValues) { } public override void RedirectToRoutePermanent(object routeValues) { } public override void RedirectToRoutePermanent(string routeName) { } public override void RedirectToRoutePermanent(RouteValueDictionary routeValues) { } public override void RedirectToRoutePermanent(string routeName, object routeValues) { } public override void RedirectToRoutePermanent(string routeName, RouteValueDictionary routeValues) { } public override void RemoveOutputCacheItem(string path) { } public override void RemoveOutputCacheItem(string path, string providerName) { } public override void SetCookie(HttpCookie cookie) { } public override void TransmitFile(string filename) { } public override void TransmitFile(string filename, long offset, long length) { } public override void Write(string s) { } public override void Write(char ch) { } public override void Write(char[] buffer, int index, int count) { } public override void Write(object obj) { } public override void WriteFile(string filename) { } public override void WriteFile(string filename, bool readIntoMemory) { } public override void WriteFile(string filename, long offset, long size) { } public override void WriteFile(IntPtr fileHandle, long offset, long size) { } public override void WriteSubstitution(HttpResponseSubstitutionCallback callback) { } } }
光从代码本身的角度来说,可以说这个类什么事情也没做,但是它为我们提供了一个抽象体系(从抽象类HttpResponseBase继承),这样的话,我们的客户端代码就可以改写为:
protected HttpResponseBase _response; protected void Page_Load(object sender, EvengArgs e) { _response.Write("test u"); }
OK,可测试了,因为我们可以在某个地方注入依赖给_response了。
注意,包装类的撰写,还可以使用接口,或者干脆也仅仅只是一个类,只要让被包装的这个同名的方法是virtual的就可以了。
另外,不可测试的那些类,可能往往已经作为稳定版本提交给客户了,我们就不能修改源代码然后告诉客户说你替换下我们的DLL吧,所以,为了让客户端程序员找到我们的包装类,可以让包装类以及被包装的类使用同一个命名空间。
相关文章推荐
- 单元测试不可测试那些类(无抽象、静态类、静态方法)
- 单元测试不可测试那些类(无抽象、静态类、静态方法)
- 单元测试笔记,静态方法的测试和Mock
- 静态方法中不可直接new内部类实例对象问题
- 如何使用powerMockito对静态方法测试(static)
- JAVA的静态方法,静态变量,静态类。
- 用单例模式还是用静态方法或静态类
- JAVA的静态变量、静态方法、静态类
- eclipse导入静态类,自动代码提示静态方法
- java基础篇(五)——静态变量、静态方法、静态类
- C#File类常用的文件操作方法(创建、移动、删除、复制等) File类,是一个静态类,主要是来提供一些函数库用的。静态实用类,提供了很多静态的方法,支持对文件的基本操作,包括创建,拷贝,移动,删除和
- java中静态方法不可发出对非static方法的调用
- (转)Java静态变量,静态类,静态方法
- Java抽象类不可创建对象,但可以调用静态方法将建一个对象
- 单元测试之白盒测试方法
- C#语言学习--基础部分(九) --类->静态字段,静态方法,静态类
- nginx+apache环境伪静态规则是否有效的测试方法
- 单元测试中测试用例的设计方法
- 静态类和 关于什么时候使用“静态方法”
- [单元测试]PowerMock+SpringMVC整合并测试Controller层方法(一)