<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
        My Github

      .NET單元測試的藝術-3.測試代碼

      開篇:上一篇我們學習單元測試和核心技術:存根、模擬對象和隔離框架,它們是我們進行高質量單元測試的技術基礎。本篇會集中在管理和組織單元測試的技術,以及如何確保在真實項目中進行高質量的單元測試。

      系列目錄:

      1.入門

      2.核心技術

      3.測試代碼

      一、測試層次和組織

      1.1 測試項目的兩種目錄結構

       ?。?)集成測試和單元測試在同一個項目里,但放在不同的目錄和命名空間里。基礎類放在單獨的文件夾里。

       ?。?)集成測試和單元測試位于不同的項目中,有不同的命名空間。

      實踐中推薦使用第二種目錄結構,因為如果我們不把這兩種測試分開,人們可能就不會經常地運行這些測試。既然測試都寫好了,為什么人們不愿意按照需要運行它們呢?一個原因是:開發人員有可能懶得運行測試,或者沒有實踐運行測試。

      1.2 構建綠色安全區

        將集成測試和單元測試分開放置,其實就給團隊的開發人員構建了綠色安全區,這個區只包含單元測試。

        因為集成測試的本質決定了它運行時間較長,開發人員很有可能每天運行多次單元測試,較少運行集成測試。

      單元測試全部通過至少可以使開發人員對代碼質量比較有信心,專注于提高編碼效率。而且我們應該將測試自動化,編寫每日構建腳本,并借助持續集成工具幫助我們自動執行這些腳本。

      1.3 將測試類映射到被測試代碼

        (1)將測試映射到項目

        創建一個測試項目,用被測試項目的名字加上后綴.UnitTests來命名。

        例如:Manulife.MyLibrary → Manulife.MyLibrary.UnitTests 和 Manulife.MyLibrary.IntegrationTests,這種方法看起來簡單直觀,開發人員能夠從項目名稱找到對應的所有測試。

       ?。?)將測試映射到類

        ① 每個被測試類或者被測試工作單元對應一個測試類:LogAnalyzer → LogAnalyzer.UnitTests

       ?、?每個功能對應一個測試類:有一個LoginManager類,測試方法為ChangePassword(這個方法測試用例特別多,需要單獨放在一個測試類里邊) → 創建兩個類 LoginManagerTests 和 LoginManagerTests-ChangePassword,前者只包含對ChangePassword方法的測試,后者包含該類其他所有測試。

        (3)將測試映射到具體的工作單元入口

        測試方法的命名應該有意義,這樣人們可以很容易地找到所有相關的測試方法。

        這里,回歸一下第一篇中提到的測試方法名稱的規范,一般包含三個部分:[UnitOfWorkName]_[ScenarioUnderTest]_[ExpectedBehavior]

        • UnitOfWorkName  被測試的方法、一組方法或者一組類
        • Scenario  測試進行的假設條件,例如“登入失敗”,“無效用戶”或“密碼正確”等
        • ExpectedBehavior  在測試場景指定的條件下,你對被測試方法行為的預期  

        示例:IsValidFileName_BadExtension_ReturnsFalse,IsValidFileName_EmptyName_Throws 等

      1.4 注入橫切關注點

        當需要處理類似時間管理、異常或日志的橫切關注點時,使用它們的地方會非常多,如果把它們實現成可注入的,產生的代碼會很容易測試,但卻很難閱讀和理解。這里我們來看一個例子,假設應用程序使用當前時間進行寫日志,相關代碼如下:

          public static class TimeLogger
          {
              public static string CreateMessage(string info)
              {
                  return DateTime.Now.ToShortDateString() + " " + info;
              }
          }

        為了使這段代碼容易測試,如果使用之前的依賴注入技術,那么我們需要創建一個ITimeProvider接口,還必須在每個用到DateTime的地方使用到這個接口。這樣做非常耗時,實際上,還有更直接的方法解決這個問題。

        Step1.創建一個名為SystemTime的定制類,在所有的產品代碼里邊使用這個定制類,而非標準的內建類DateTime。

          public class SystemTime
          {
              private static DateTime _date;
      
              public static void Set(DateTime custom)
              {
                  _date = custom;
              }
      
              public static void Reset()
              {
                  _date = DateTime.MinValue;
              }
      
              public static DateTime Now
              {
                  get
                  {
                      // 如果設置了時間,SystemTime就返回假時間,否則返回真時間
                      if (_date != DateTime.MinValue)
                      {
                          return _date;
                      }
                      return DateTime.Now;
                  }
              }
          }
      View Code

        閱讀這段代碼,其中有一個小技巧:SystemTime類提供一個特殊方法Set,它會修改系統中的當前時間,也就是說,每個使用這個SystemTime類的人看到的都是你指定的日期和時間。有了這樣的代碼,每個使用這個SystemTime類的人看到的都會是你指定的日期和時間。

        Step2.在測試項目中使用SystemTime進行測試。

          [TestFixture]
          public class TimeLoggerTests
          {
              [Test]
              public void SettingSystemTime_Always_ChangesTime()
              {
                  SystemTime.Set(new DateTime(2000, 1, 1));
                  string output = TimeLogger.CreateMessage("a");
      
                  StringAssert.Contains("2000/1/1", output);
              }
      
              /// <summary>
              /// 在每個測試結束時重置日期
              /// </summary>
              [TearDown]
              public void AfterEachTest()
              {
                  SystemTime.Reset();
              }
          }
      View Code

        在測試中,我們首先假定設置一個日期,然后進行斷言。并且借助TearDown方法,確保當前測試不會改變其他測試的值。

      Note : 這樣做的好處就在于不用注入一大堆接口,我們所付出的代價僅僅在于在測試類中加入一個簡單的[TearDown]方法,確保當前測試不會改變其他測試的值。

      1.5 使用繼承使測試代碼可重用

        推薦大家在測試代碼中使用繼承機制,通過實現基類,可以較好地展現面向對象的魔力。在實踐中,一般有三種模式會被使用到:

        (1)抽象測試基礎結構類模式

          /// <summary>
          /// 測試類集成模式
          /// </summary>
          [TestFixture]
          public class BaseTestsClass
          {
              /// <summary>
              /// 重構為通用可讀的工具方法,由派生類使用
              /// </summary>
              /// <returns>FakeLogger</returns>
              public ILogger FakeTheLogger()
              {
                  LoggingFacility.Logger = Substitute.For<ILogger>();
                  return LoggingFacility.Logger;
              }
      
              [TearDown]
              public void ClearLogger()
              {
                  // 測試之間要重置靜態資源
                  LoggingFacility.Logger = null;
              }
          }
      
          [TestFixture]
          public class LogAnalyzerTests : BaseTestsClass
          {
              [Test]
              public void Analyze_EmptyFile_ThrowsException()
              {
                  // 調用基類的輔助方法
                  FakeTheLogger();
      
                  LogAnalyzer analyzer = new LogAnalyzer();
                  analyzer.Analyze("myemptyfile.txt");
      
                  // 測試方法的其余部分
              }
          }
      View Code

        使用此模式要注意繼承最好不要超過一層,如果繼承層數過多,不僅可讀性急劇下降,編譯也很容易出錯。

       ?。?)測試類類模板模式

          /// <summary>
          /// 測試模板類模式
          /// </summary>
          [TestFixture]
          public abstract class TemplateStringParserTests
          {
              [Test]
              public abstract void TestGetStringVersionFromHeader_SingleDigit_Found();
              [Test]
              public abstract void TestGetStringVersionFromHeader_WithMinorVersion_Found();
              [Test]
              public abstract void TestGetStringVersionFromHeader_WithRevision_Found();
          }
      
          [TestFixture]
          public class XMLStrignParserTests : TemplateStringParserTests
          {
              protected IStringParser GetParser(string input)
              {
                  return new XMLStringParser(input);
              }
      
              [Test]
              public override void TestGetStringVersionFromHeader_SingleDigit_Found()
              {
                  IStringParser parser = GetParser("<Header>1</Header>");
      
                  string versionFromHeader = parser.GetTextVersionFromHeader();
                  Assert.AreEqual("1", versionFromHeader);
              }
      
              [Test]
              public override void TestGetStringVersionFromHeader_WithMinorVersion_Found()
              {
                  IStringParser parser = GetParser("<Header>1.1</Header>");
      
                  string versionFromHeader = parser.GetTextVersionFromHeader();
                  Assert.AreEqual("1.1", versionFromHeader);
              }
      
              [Test]
              public override void TestGetStringVersionFromHeader_WithRevision_Found()
              {
                  IStringParser parser = GetParser("<Header>1.1.1</Header>");
      
                  string versionFromHeader = parser.GetTextVersionFromHeader();
                  Assert.AreEqual("1.1", versionFromHeader);
              }
          }
      View Code

        使用此模式可以確保開發者不會遺忘重要的測試,基類包含了抽象的測試方法,派生類必須實現這些抽象方法。

       ?。?)抽象測試驅動類模式

          /// <summary>
          /// 抽象“填空”測試驅動類模式
          /// </summary>
          public abstract class FillInTheBlankStringParserTests
          {
              // 返回接口的抽象方法
              protected abstract IStringParser GetParser(string input);
              // 抽象輸入方法(屬性),為派生類提供特定格式的數據
              protected abstract string HeaderVersion_SingleDigit { get; }
              protected abstract string HeaderVersion_WithMinorVersion { get; }
              protected abstract string HeaderVersion_WithRevision { get; }
              // 如果需要,預先為派生類定義預期的輸出
              public const string EXPECTED_SINGLE_DIGIT = "1";
              public const string EXPECTED_WITH_MINORVERSION = "1.1";
              public const string EXPECTED_WITH_REVISION = "1.1.1";
      
              [Test]
              public void TestGetStringVersionFromHeader_SingleDigit_Found()
              {
                  string input = HeaderVersion_SingleDigit;
                  IStringParser parser = GetParser(input);
      
                  string versionFromHeader = parser.GetTextVersionFromHeader();
                  Assert.AreEqual(EXPECTED_SINGLE_DIGIT, versionFromHeader);
              }
      
              [Test]
              public void TestGetStringVersionFromHeader_WithMinorVersion_Found()
              {
                  string input = HeaderVersion_WithMinorVersion;
                  IStringParser parser = GetParser(input);
      
                  string versionFromHeader = parser.GetTextVersionFromHeader();
                  Assert.AreEqual(EXPECTED_WITH_MINORVERSION, versionFromHeader);
              }
      
              [Test]
              public void TestGetStringVersionFromHeader_WithRevision_Found()
              {
                  string input = HeaderVersion_WithRevision;
                  IStringParser parser = GetParser(input);
      
                  string versionFromHeader = parser.GetTextVersionFromHeader();
                  Assert.AreEqual(EXPECTED_WITH_REVISION, versionFromHeader);
              }
          }
      
          public class DBLogStringParserTests : GenericParserTests<DBLogStringParser>
          {
              protected override string GetInputHeaderSingleDigit()
              {
                  return "Header;1";
              }
      
              protected override string GetInputHeaderWithMinorVersion()
              {
                  return "Header;1.1";
              }
      
              protected override string GetInputHeaderWithRevision()
              {
                  return "Header;1.1.1";
              }
          }
      View Code

        此模式在基類中實現測試方法,并提供派生類可以實現的抽象方法鉤子。當然,只是大部分的測試代碼在基類中,派生類也可以加入自己的特殊測試。

        此模式的要點在于:你不是具體地測試一個類,而是測試產品代碼中的一個接口或者基類。

        當然,在.NET中我們也可以通過泛型來實現此模式,例如下面的代碼:

          public abstract class GenericParserTests<T> where T : IStringParser // 01.定義參數的泛型約束
          {
              protected abstract string GetInputHeaderSingleDigit();
              protected abstract string GetInputHeaderWithMinorVersion();
              protected abstract string GetInputHeaderWithRevision();
      
              // 02.返回泛型變量而非接口
              protected T GetParser(string input)
              {
                  // 03.返回泛型
                  return (T)Activator.CreateInstance(typeof(T), input);
              }
      
              [Test]
              public void TestGetStringVersionFromHeader_SingleDigit_Found()
              {
                  string input = GetInputHeaderSingleDigit();
                  T parser = GetParser(input);
      
                  bool result = parser.HasCorrectHeader();
                  Assert.AreEqual(false, result);
              }
      
              [Test]
              public void TestGetStringVersionFromHeader_WithMinorVersion_Found()
              {
                  string input = GetInputHeaderWithMinorVersion();
                  T parser = GetParser(input);
      
                  bool result = parser.HasCorrectHeader();
                  Assert.AreEqual(false, result);
              }
      
              [Test]
              public void TestGetStringVersionFromHeader_WithRevision_Found()
              {
                  string input = GetInputHeaderWithRevision();
                  T parser = GetParser(input);
      
                  bool result = parser.HasCorrectHeader();
                  Assert.AreEqual(false, result);
              }
          }
      
          public class DBLogStringParserTests : GenericParserTests<DBLogStringParser>
          {
              protected override string GetInputHeaderSingleDigit()
              {
                  return "Header;1";
              }
      
              protected override string GetInputHeaderWithMinorVersion()
              {
                  return "Header;1.1";
              }
      
              protected override string GetInputHeaderWithRevision()
              {
                  return "Header;1.1.1";
              }
          }
      View Code

      二、優秀單元測試的支柱

        要編寫優秀的單元測試,它們應該同時具有 可靠性、可維護性可讀性。

      2.1 編寫可靠的測試

        一個可靠的測試能讓你覺得自己對事態了如指掌,能夠從容應對。以下是一些指導原則和技術:

       ?。?)決定何時刪除或修改測試

        一旦測試寫好并通過,通常我們不應該修改或刪除這些測試,因為它們是我們得綠色保護網。但是,有時候我們還是需要修改或者刪除測試,所以需要理解什么情況下修改或刪除測試會帶來問題,什么情況下又是合理的。一般來說,如果有產品缺陷、測試缺陷、語義或者API更改或者是由于沖突或無效測試,我們需要修改和刪除測試代碼。

        (2)避免測試中的邏輯

        隨著測試中邏輯的增多,出現測試缺陷的幾率就會呈現指數倍的增長。如果單元測試中包含了下列語句就是包含了不應該有的邏輯:

      • switch、if或else語句;
      • foreach、for或while循環;

        這種做法不值得推薦,因為這樣的測試可讀性較差,也比較脆弱。通常來說,一個單元測試應該是一系列方法的調用和斷言,但是不包含控制流程語句,甚至不應該將斷言語句放在try-catch中

       ?。?)只測試一個關注點

        如果我們的單元測試對多個對象進行了斷言,那么這個測試有可能測試了多個關注點。在一個單元測試中驗證多個關注點會使得事情變得復雜,卻沒有什么價值。你應該在分開的、獨立的單元測試中驗證多余的關注點,這樣才能發現真正失敗的地方。

       ?。?)把單元測試和集成測試分開

        掐面討論了測試的綠色安全區,我們需要的就是準備一個單獨的單元測試項目,項目中僅包含那些在內存中運行,結果穩定,可重復執行的測試。

       ?。?)用代碼審查確保代碼覆蓋率

        如果覆蓋率低于20%,說明我們缺少很多測試,我們不會知道下一個開發人員將怎么修改我們得代碼。如果沒有回失敗的測試,可能就不會發現這些錯誤。

      2.2 編寫可維護性的測試

        可維護性是大多數開發者在編寫單元測試時面對的核心問題之一。為此我們需要:

       ?。?)只測試公共契約

       ?。?)刪除重復測試(去除重復代碼)

       ?。?)實施測試隔離

        測試隔離的基本概念是:一個測試應該總是在它自己的小世界中運行,與其他類似或不同的工作的測試隔離,甚至不知道其他測試的存在。

      2.3 編寫可讀性的測試

        不可讀的測試幾乎沒有任何意義,它是我們向項目的下一代開發者講述的故事,幫助開發者理解一個應用程序的組成及其開端。

       ?。?)單元測試命名

        這個前面我們討論過,應該包括三部分:被測試方法名_測試場景_預期行為,如果開發人員都是用這種規范,其他的開發人員就能很容易進入項目,理解測試。

       ?。?)變量命名

        通過合理命名變量,你可以確保閱讀測試的人可以盡快地理解你要驗證什么(相對于理解產品代碼中你想要實現什么)。請看下面的一個例子:

          [Test]
          public void BadlyNameTest()
          {
              LogAnalyzer log = new LogAnalyzer();
              int result = log.GetLineCount("abc.txt");
      
              Assert.AreEqual(-100, result);
          }
      
          [Test]
          public void GoodNameTest()
          {
              LogAnalyzer log = new LogAnalyzer();
              int result = log.GetLineCount("abc.txt");
              const int COULD_NOT_READ_FILE = -100;
      
              Assert.AreEqual(-COULD_NOT_READ_FILE, result);
          }
      View Code

        經過改進后,我們會很容易理解這個返回值的意義。

       ?。?)有意義的斷言

        只有當測試確實需要,并且找不到別的辦法使測試更清晰時,你才應該編寫定制的斷言信息。編寫好的斷言信息就像編寫好的異常信息,一不小心就會犯錯,使讀者產生誤解,浪費他們的時間。

       ?。?)斷言和操作分離

        為了可讀性,請不要把斷言和方法調用寫在同一行。

          // 斷言和操作寫在了同一行
          Assert.AreEqual(-COULD_NOT_READ_FILE, log.GetLineCount("abc.txt"));

      三、小結

        這一篇我們學習了:

      • 盡量將測試自動化,盡可能多次地運行測試,盡可能持續地進行產品交付;
      • 把集成測試和單元測試分開,為整個團隊構建一個綠色安全區,該區域中所有的測試都必須通過;
      • 按照項目和類型組織測試,把測試分別放在不同的目錄、文件夾或者命名空間中;
      • 使用測試類層次,對一個層次中相關的幾個類進行同一組測試,或者對共享一個通用接口或者基類的類型進行同一組測試;
      • 優秀單元測試具有三大支柱:可讀性、可維護性與可靠性,它們相輔相成。
      • 如果人們能讀懂你的測試,就能理解和維護測試,如果測試能夠通過,它們也會信任測試。一旦實現這個目標,你就能知道系統是否正常工作,具有了處理變更和在需要時修改代碼的能力;

      附件下載

        本系列文章的示例代碼:點此下載

      參考資料

            The Art of Unit Testing

       ?。?)Roy Osherove 著,金迎 譯,《單元測試的藝術(第2版)》

       

      posted @ 2016-05-15 00:53  EdisonZhou  閱讀(3579)  評論(4)    收藏  舉報
      主站蜘蛛池模板: 四虎影视4hu4虎成人| 久久精品中文字幕少妇| 亚洲欧美在线观看品| 精品人妻一区二区三区蜜臀| 国产一区二区三区色噜噜| 欧美一级黄色影院| 国产亚洲国产精品二区| 中日韩黄色基地一二三区| 福利一区二区不卡国产| 成人免费区一区二区三区| 人妻护士在线波多野结衣| 久久久久香蕉国产线看观看伊| 毛片av中文字幕一区二区| 亚洲午夜无码久久久久蜜臀av| 国产精品疯狂输出jk草莓视频| 国产999久久高清免费观看| 日韩精品一区二区三区在线观看 | 国产成年码av片在线观看| 老熟妇仑乱换频一区二区| 天堂网国产| 日本一区二区三区专线| 999国产精品一区二区| 高清国产一区二区无遮挡| 韩国午夜福利片在线观看| 中文字幕日韩有码第一页| 影音先锋大黄瓜视频| 久久亚洲欧美日本精品| 日韩成人午夜精品久久高潮 | 久久亚洲熟女cc98cm| 真实国产老熟女无套内射| 东方四虎在线观看av| 瓮安县| 日韩精品中文字幕一线不卡| 丰满少妇高潮无套内谢| 国产精品久久久久久人妻精品| 国产天美传媒性色av高清| 亚洲人妻精品中文字幕| 动漫av纯肉无码av在线播放| 乱人伦人妻精品一区二区| 国产成人午夜精品影院| 欧美和黑人xxxx猛交视频|