單元測試不可測試那些類(無抽象、靜態類、靜態方法)


實際上“單元測試不可測試那些類(無抽象、靜態類、靜態方法)”是個偽命題,因為事實是:無抽象、靜態類、靜態方法都是不可單元測試的。那么,如果我們要寫出可測試的代碼,又要用到這些靜態類等,該怎么辦,實際上我們需要兩個步驟:

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吧,所以,為了讓客戶端程序員找到我們的包裝類,可以讓包裝類以及被包裝的類使用同一個命名空間。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM