单元测试不可测试那些类(无抽象、静态类、静态方法)-阿里云开发者社区

开发者社区> luminji> 正文

单元测试不可测试那些类(无抽象、静态类、静态方法)

简介: 实际上“单元测试不可测试那些类(无抽象、静态类、静态方法)”是个伪命题,因为事实是:无抽象、静态类、静态方法都是不可单元测试的。那么,如果我们要写出可测试的代码,又要用到这些静态类等,该怎么办,实际上我们需要两个步骤: 1:为它们写一个包装类,让这个包装类是抽象的(继承自接口,或者抽象类,或者方法本身是Virtual的); 2:通知客户端程序员,使用包装类来代替原先的静态类来写业务逻辑; 实际上,微软也是这么干的,我在上一篇博文《单元测试WebForm的UI逻辑及文件上传》写到,最典型的不可测试类,那就是WebForm架构的网站中,对Response等的模拟。
+关注继续查看

实际上“单元测试不可测试那些类(无抽象、静态类、静态方法)”是个伪命题,因为事实是:无抽象、静态类、静态方法都是不可单元测试的。那么,如果我们要写出可测试的代码,又要用到这些静态类等,该怎么办,实际上我们需要两个步骤:

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吧,所以,为了让客户端程序员找到我们的包装类,可以让包装类以及被包装的类使用同一个命名空间。 

Creative Commons License本文基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9497 0
《Java程序员面试秘笈》—— 面试题9 对于类的静态变量的理解
【面试题解析】类的静态变量a在modify()方法中并没有被改变,而是改变了modify()方法的参数。
1248 0
单元测试不可测试那些类(无抽象、静态类、静态方法)
实际上“单元测试不可测试那些类(无抽象、静态类、静态方法)”是个伪命题,因为事实是:无抽象、静态类、静态方法都是不可单元测试的。那么,如果我们要写出可测试的代码,又要用到这些静态类等,该怎么办,实际上我们需要两个步骤: 1:为它们写一个包装类,让这个包装类是抽象的(继承自接口,或者抽象类,或者方法本身是Virtual的); 2:通知客户端程序员,使用包装类来代替原先的静态类来写业务逻辑; 实际上,微软也是这么干的,我在上一篇博文《单元测试WebForm的UI逻辑及文件上传》写到,最典型的不可测试类,那就是WebForm架构的网站中,对Response等的模拟。
582 0
ECS训练营Day1—基于阿里云ECS、VuePress搭建静态网站---青癯
ECS训练营Day1—基于阿里云ECS、VuePress搭建静态网站
125 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13186 0
JSP-讲解(生成java类、静态导入与动态导入)
一、JSP技术简介 JSP是Java Server Page的缩写,它是Servlet的扩展,它的作用是简化网站的创建和维护。 JSP是HTML代码与Java代码的混合体。 JSP文件通常以JSP或JSPX的扩展名。
1138 0
C++MFC编程笔记day04 运行时类信息和窗口的动态、静态切分
运行时类信息 程序在运行时,获取对象类的信息及类的继承关系实现:1、定义类继承自CObject类。2、类内声明宏DECLARE_DYNAMIC(),类外实现宏IMPLEMENT_DYNAMIC()3、使用:BOOL IsKindOf(CRuntimeClass* pClass)//对象是否属于某个类CRuntimeClass* GetRuntimeClass( );//获取对象运行时类信息,经常使用RUNTIME_CLASS(类名)代替。
897 0
+关注
luminji
微软最有价值技术专家(MVP),著有《编写高质量代码:改善C#程序的157个建议》,有着十多年的软件从业资历。
291
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载