<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          單元測試填坑筆記,新技能Get!

          共 22955字,需瀏覽 46分鐘

           ·

          2021-07-18 13:34

          歷史遺留代碼不敢重構(gòu)?
          每次改代碼都要回歸所有邏輯?
          提測被打回?

          在近期的代碼重構(gòu)的過程中,遇到了各式各樣的問題。比如調(diào)整代碼順序?qū)е耣ug,取反操作邏輯丟失,參數(shù)校驗邏輯被誤改等。

          上線前需要花大量時間進(jìn)行測試和灰度驗證。在此過程最大的感受就是:一切沒有單測覆蓋的重構(gòu)都是裸奔。

          經(jīng)歷了沒有單測痛苦磨難,查閱很多資料和實戰(zhàn)之后,于是就有了這篇文章,希望能給你的單測提供一些參考。

          認(rèn)識單測

          What

          單元測試是針對程序模塊(軟件設(shè)計的最小單位)來進(jìn)行正確性檢驗的測試工作。程序單元是應(yīng)用的最小可測試部件。

          關(guān)于測試的名詞還有很多,如集成測試,系統(tǒng)測試,驗收測試。是在不同階段,不同角色來共同保證系統(tǒng)質(zhì)量的一種手段。

          筆者在工作中經(jīng)常遇到一些無效單測,通常是啟動Spring容器,連接數(shù)據(jù)庫,調(diào)用方法然后控制臺輸出結(jié)果。這些并不能算是單測。示例代碼如下:

          @RunWith(SpringRunner.class)
          @SpringBootTest(classes = ApplicationLoader.class)
          public class UserServiceTest {
              @Autowired
              private UserService userService;
              @Test
              public void testAddUser() {
                  AddUserRequest addUserRequest = new AddUserRequest("zhangsan""[email protected]");
                  ResultDTO<Long> addResult = userService.addUser(addUserRequest);
                  System.out.println(addResult);
              }
          }

          Why

          在工作中很多代碼是沒有單測的,這些項目也能正常得運行。那么為什么要編寫單測呢?

          好的單測在能夠提供我們代碼交付質(zhì)量的同時,減少bug發(fā)現(xiàn)和修復(fù)的成本,進(jìn)而提高工作效率。至于單測能夠讓QA開心,則只是錦上添花。

          提升工作效率,在工作中程序員的大多數(shù)時間都耗費在了測試階段,編碼往往可能只占一小部分。

          尤其是在修改已有代碼時候,不得不考慮增量代碼是否會對原有邏輯帶來沖擊,以及修復(fù)bug之后是否引入的新的bug。

          筆者就曾陷入如此困境,一下午時間都在重復(fù)著打包,部署,測試…,在改bug和寫bug之間無限循環(huán),有時也會因為一個低級bug抓心撓肝剛到后半夜。

          所以長遠(yuǎn)來看,單測是能夠有效提高工作效率的!

          提升代碼質(zhì)量,可測試通常與軟件的設(shè)計良好程序相關(guān),難以測試的代碼一般設(shè)計上都有問題。所以有效的單測會驅(qū)動開發(fā)者寫出更高質(zhì)量代碼。

          當(dāng)然,單測帶來最直接的收益就是能夠減少bug率,雖然單測不能捕獲所有bug,但是的確能夠暴露出大多數(shù)bug。

          節(jié)省成本,單測能夠確保程序的底層邏輯單元的正確性,讓問題能夠在RD自測階段暴露出來。bug越早發(fā)現(xiàn),修復(fù)成本往往更低,帶來的影響也會更小,所以bug應(yīng)該盡早暴露。

          如下圖紅色曲線所示,在不同階段修復(fù)bug的成本差別是巨大的。

          Who

          代碼的作者最了解代碼的目的、特點和實現(xiàn)的局限性。寫單測沒有比作者更適合的人選了,所以往往代碼作者往往是第一責(zé)任人。

          When

          編寫單測的時機,一般是 The sooner, the better(越早越好)。盡量不要將單測拖延到代碼編寫完之后,這樣帶來的收益可能不盡如人意。

          TDD(Test-Driven Development)測試驅(qū)動開發(fā),是一種軟件開發(fā)過程中的應(yīng)用方法,以其倡導(dǎo)先寫測試程序,然后編碼實現(xiàn)其功能得名。

          測試驅(qū)動著整個開發(fā)過程:首先,驅(qū)動代碼的設(shè)計和功能的實現(xiàn);其后,驅(qū)動代碼的再設(shè)計和重構(gòu)。

          當(dāng)然TDD是一種理想的狀態(tài),由于種種原因,想要完全遵守TDD原則,是有一定難度的,畢竟PM的需求往往是可變的。

          邊開發(fā)邊寫單測,先寫少量功能代碼,緊接著寫單測,重復(fù)這兩個過程,直到完成功能代碼開發(fā)。

          其實這種方案跟第一種已經(jīng)很接近,當(dāng)功能代碼開發(fā)完時,單測也差不多完成了。這種方案也是最常見和推薦的方式。

          開發(fā)后再補單測,效果往往是最差的。首先,要考慮的是代碼的可測性,已經(jīng)完成的代碼可能并不具備可測試性,畢竟寫代碼的時候可以任意發(fā)揮。

          其次,補單測時容易順著當(dāng)前實現(xiàn)去寫測試代碼,而忽略實際需求的邏輯是什么,導(dǎo)致我們的單測是無效的。

          Which

          究竟哪些方法需要進(jìn)行單測?這個困擾筆者很久的一個問題!如上文所說,單測覆蓋率當(dāng)然是越高越好,不過我們在考慮ROI時難免會做出一些妥協(xié)。

          接受不完美,對于歷史代碼,全覆蓋往往是不現(xiàn)實的。我們可以根據(jù)方法優(yōu)先級(如照成資損,影響業(yè)務(wù)主流程)針對性補全單測,保證現(xiàn)有邏輯能正常運行。

          對于增量代碼,筆者認(rèn)為沒有必要全部覆蓋,一般根據(jù)被測方法是否有處理(業(yè)務(wù))邏輯來決定。

          比如常見的JavaWeb項目代碼中,Controller層,DAO層以及其他僅涉及接口轉(zhuǎn)發(fā)相關(guān)的方法,往往不需要單測覆蓋。而業(yè)務(wù)邏輯層的各種Service則需要重點測試。

          對于自定義的工具類,正則表達(dá)式等固定邏輯,也是必須要測試的。因為這部分邏輯一般都是公共且通用的,一旦邏輯錯誤會產(chǎn)生比較嚴(yán)重的影響。

          How

          好的單測一定是能夠自動執(zhí)行并查執(zhí)行結(jié)果的,也不應(yīng)當(dāng)對外部有依賴,單測的執(zhí)行應(yīng)當(dāng)是完全自動化,并且無需部署,本地IDE就能運行。

          在寫單測前,不妨參考以下前人總結(jié)好的First原則。

          F—Fast:快速

          在開發(fā)過程中通常需要隨時執(zhí)行測試用例;在發(fā)布流水線中執(zhí)行也必須執(zhí)行,常見的就是push代碼后,或者打包時先執(zhí)行測試用例;況且一個項目中往往有成百上千個測試用例。

          所以為了保證開發(fā)和發(fā)布效率,快速執(zhí)行是單測的重要原則。這就要求我們不要像集成測試一樣依賴多個組件,確保單測在秒級甚至毫秒級執(zhí)行完畢。

          I—Isolated:隔離

          隔離性也可以理解為獨立性,好的單測是每個測試用例只關(guān)注一個邏輯單元或者代碼分支,保證單一職責(zé),這樣能更清晰的暴露問題和定位問題。

          每個單測之間不應(yīng)該產(chǎn)生依賴,為了保證單測穩(wěn)定可靠且便于維護,單測用例之間決不能互相調(diào)用,也不能依賴執(zhí)行的先后次序。

          數(shù)據(jù)資源隔離,測試時不要依賴和修改外部數(shù)據(jù)或文件等其他共享資源,做到測試前后共享資源數(shù)據(jù)一致。

          Fake,Stub和Mock

          我們的被測試代碼存在的外部依賴的行為往往是不可預(yù)測的,我們需要將這些"變化"變得可控,根據(jù)職責(zé)不同,可以分為Fake,Stubs,Mock三種。

          假數(shù)據(jù)(Fake), 一些針對當(dāng)前場景構(gòu)建的簡化版的對象,這些對象作為數(shù)據(jù)源供我們使用,職責(zé)就像內(nèi)存數(shù)據(jù)庫一樣。

          比如在常見的三層架構(gòu)中,業(yè)務(wù)邏輯層需要依賴數(shù)據(jù)訪問層,當(dāng)業(yè)務(wù)邏輯層開發(fā)完成后即使數(shù)據(jù)訪問層沒有開發(fā)完成,也能通過構(gòu)建Fake數(shù)據(jù)的方式完成業(yè)務(wù)邏輯層的測試。

          UserDO fakeUser = new UserDO("zhangsan""[email protected]");

          public UserVO getUser(Long userId) {
            // do something
            User user = fakeUser;  // 測試階段替換:User user = userDao.getById(userId);
            // do something
          }

          Fake數(shù)據(jù)雖然可以測試邏輯,但是當(dāng)數(shù)據(jù)訪問層開發(fā)完畢后可能需要修改代碼,將Fake數(shù)據(jù)替換為實際的方法調(diào)用來完成代碼集成,顯然這不是一種優(yōu)雅的實現(xiàn),于是便有了Stub。

          樁代碼(Stub)是用來代替真實代碼的臨時代碼,是在測試環(huán)境對依賴接口的一種專門實現(xiàn)。

          比如,UserService中調(diào)用了UseDao,為了對UserService中的函數(shù)進(jìn)行測試,這時候需要構(gòu)建一個UserDao接口的實現(xiàn)類UserDaoStub(返回Fake數(shù)據(jù)),這個臨時代碼就是所謂的樁代碼。

          public class UserDaoStub implements UserDao {
              UserDO fakeUser = new UserDO();
              {
                  fakeUser.setUserName("zhangsan");
                  fakeUser.setEmail("[email protected]");
                  LocalDateTime dateTime = LocalDateTime.of(20217112300);
                  fakeUser.setCreateTime(dateTime);
                  fakeUser.setUpdateTime(dateTime);
              }
              @Override
              public UserDO getById(Long id) {
                  if (Objects.isNull(id) || id <= 0) {
                      return new UserDO();
                  }
                  return fakeUser;
              }
          }

          這種面向接口編程,使得在不同場景下通過不同的實現(xiàn)類替換接口的編程設(shè)計原則就是我們常說的里氏替換原則。

          Mock 代碼和樁代碼非常類似,都是用來代替真實代碼的臨時代碼。不同的是在被調(diào)用時,會記錄被調(diào)用信息,執(zhí)行完畢后驗證執(zhí)行動作或結(jié)果是否符合預(yù)期。

          對于 Mock 代碼來說,我們的關(guān)注點是 Mock 方法有沒有被調(diào)用,以什么樣的參數(shù)被調(diào)用,被調(diào)用的次數(shù),以及多個 Mock 函數(shù)的先后調(diào)用順序。

              @Test
              public void testAddUser4SendEmail() {
                  // GIVEN:
                  AddUserRequest fakeAddUserRequest = new AddUserRequest("zhangsan""[email protected]");
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                  assertTrue(addResult.isSuccess());
                  // 驗證sendVerifyEmail的調(diào)用1次,并且調(diào)用參數(shù)為我們fake數(shù)據(jù)中指定的郵箱
                  verify(emailService, times(1)).sendVerifyEmail(any());
                  verify(emailService).sendVerifyEmail(fakeAddUserRequest.getEmail());
              }

          當(dāng)然,我們也可以通過修改Stub的實現(xiàn),達(dá)到和Mock一樣的效果。

          public class EmailServiceStub implements EmailService{
              public int invokeCount = 0;
              @Override
              public boolean sendVerifyEmail(String email) {
                  invokeCount ++;
                  // do something
                  return true;
              }
          }
          public class UserServiceImplTest {
              AddUserRequest fakeAddUserRequest;
              private UserServiceImpl userService;
              private EmailServiceStub emailServiceStub;
              @Before
              public void init() {
                  fakeAddUserRequest = new AddUserRequest("zhangsan""[email protected]");
                  emailServiceStub = new EmailServiceStub();
                  userService= new UserServiceImpl();
                  userService.setEmailService(emailServiceStub);
              }
              @Test
              public void testAddUser4SendEmail() {
                  // GIVEN: fakeAddUserRequest
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN:發(fā)送郵件接口被調(diào)用次數(shù)是否為1
                  Assert.assertEquals(emailServiceStub.invokeCount, 1);
              }
          }

          Stub和Mock的區(qū)別

          Stub和Mock的區(qū)別在于,Stub偏向于結(jié)果驗證,Mock則更加偏向于行為驗證。

          比如,測試addUser方法時,如果是Stub方式則關(guān)注方法返回結(jié)果,即用戶是否添加成功,郵件是否發(fā)送成功;而Mock方式則傾向于本次添加的行為驗證,比如sendEmail方法調(diào)用次數(shù)等。

          Mock替代Stub

          Mock和Stub本質(zhì)上是不同的,但是隨著各種Mock框架的引入,Stub和Mock的邊界越來越模糊,使得Mock不僅可以進(jìn)行行為驗證,同樣也具備Stub對接口的假實現(xiàn)的能力。

          目前大多數(shù)的mock工具都提供mock退化為stub的支持,以Mockito為例,我們可以通過anyObject(), any等方式對參數(shù)的進(jìn)行匹配;使用verify方法可以對方法的調(diào)用次數(shù)和參數(shù)進(jìn)行檢驗,這和stub就幾乎沒有本質(zhì)區(qū)別了。

          when(userDao.insert(any())).thenReturn(1L);
          when(emailService.sendVerifyEmail(anyString())).thenReturn(true);

          stub理論上也是可以向mock的方向做轉(zhuǎn)化,上文也提及stub是可以通過增加代碼來實現(xiàn)一些expectiation的特性,而從使得兩者的界限更加的模糊。

          所以,如果對于Stub和Mock的概念還是比較模糊,也不必過度糾結(jié),這并不影響寫出優(yōu)秀的單測。

          R—Repeatable:可重復(fù)執(zhí)行

          單測是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響。 同一測試用例,即使是在不同的機器,不同的環(huán)境中運行多次,每次運行都會產(chǎn)生相同的結(jié)果。

          避免隱式輸入(Hidden imput),比如測試代碼中不能依賴當(dāng)前日期,隨機數(shù)等,否則程序就會變得不可控從而變得不可重復(fù)執(zhí)行。

          S—Self-verifying:自我驗證

          單測需要通過斷言進(jìn)行結(jié)果驗證,即當(dāng)單測執(zhí)行完畢之后,用來判斷執(zhí)行結(jié)果是否和假設(shè)一致,無需人工檢查是否執(zhí)行成功。

          當(dāng)然,除了對執(zhí)行結(jié)果進(jìn)行檢查,也能對執(zhí)行過程進(jìn)行校驗,如方法調(diào)用次數(shù)等。下面是筆者在工作中經(jīng)常見到的寫法,這些都是無效的單測。

          // 直接打印結(jié)果
          public void testAddUser4DbError() {
              // GIVEN
              fakeAddUserRequest.setUserName("badcase");
              // WHEN
              ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
              // THEN
              System.out.println(addResult);
          }
          // 吞沒異常失敗case
          public void testAddUser4DbError() {
              // GIVEN
              fakeAddUserRequest.setUserName("badcase");
              // WHEN
                try {
                ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                Assert.assertTrue(addResult.isSuccess());
              } catch(Exception e) {
                  System.out.println("測試執(zhí)行失敗");
              }
          }

          正解如下:

          @Test
          public void testAddUser4DbError() {
            // GIVEN
            fakeAddUserRequest.setUserName("badcase");
            // WHEN
            ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
            // THEN
            Assert.assertEquals(addResult.getMsg(), "添加用戶失敗,請稍后重試");
          }

          T—Timely&Thorough:及時,全面

          理想狀態(tài)當(dāng)然是TDD模式開發(fā),即測試驅(qū)動開發(fā)。如前面提到的,編寫代碼邏輯之前寫最佳,邊開發(fā)邊寫次之,等代碼穩(wěn)定運行再來補單測收益可能是最低的。

          除了及時性,筆者認(rèn)為T應(yīng)當(dāng)有另一層含義,即全面性(Thorough)。理想情況下每行代碼都要被覆蓋到,每一個邏輯分支都必須有一個測試用例。

          不過想要100%的測試覆蓋率是非常耗費精力的,甚至?xí)臀覀冏畛跆岣咝实某踔韵嚆!K?strong style="font-size: inherit;line-height: inherit;color: rgb(51, 51, 51);">花合理的時間抓出大多數(shù)bug,要好過窮盡一生抓出所有bug。

          通常情況下我們要至少考慮到參數(shù)的邊界,特殊值,正常場景(與設(shè)計文檔結(jié)合)以及異常場景,保證我們的核心流程是正確的。

          Mock框架簡介

          工欲善其事必先利其器,選擇一個合適的Mock框架與手動實現(xiàn)Stub比,往往能夠讓我們的單測事半功倍。

          需要說明的是,Mock框架并不是必須的。正如上文所說,我們可以實現(xiàn)Stub代碼來隔離依賴,當(dāng)需要使用到Mock對象時,我們只需要對Stub的實現(xiàn)稍作修改即可。

          市面上有許多Mock框架可供選擇,如常見的Mockito,PowerMock,Spock,EasyMock,JMock等。如何選擇合適的框架呢?

          如果你想半個小時就能上手,不妨試試Mockito,絕對如絲般順滑!當(dāng)然,如果有時間并且對Groovy語言感興趣,不妨花半天時間了解下Spock,能讓測試代碼更加精簡。

          以下是幾種常用的Mock框架對比,不知道怎么選時,不妨根據(jù)現(xiàn)狀,需要注意的是,大部分Mock框架都不支持Mock靜態(tài)方法。

          單測實戰(zhàn)

          寫單測一般包括3個部分,即Given(Mock外部依賴&準(zhǔn)備Fake數(shù)據(jù)),When(調(diào)用被測方法)以及Then(斷言執(zhí)行結(jié)果),這種寫法和Spock的語法結(jié)構(gòu)也是一致的。

          為了更好的理解單元測試,筆者將針對如下代碼,分別使用Mockito和Spock寫一個簡單的示例,讓大家感受一下兩者的各自的特點和不同。

          @Service
          @AllArgsConstructor
          @NoArgsConstructor
          public class UserServiceImpl implements UserService {

              @Autowired
              private UserDao userDao;

              @Autowired
              private EmailService emailService;

              public ResultDTO<Long> addUser(AddUserRequest request) {
                  // 1. 校驗參數(shù)
                  ResultDTO<Void> validateResult = validateAddUserParam(request);
                  if (!validateResult.isSuccess()) {
                      return ResultDTO.paramError(validateResult.getMsg());
                  }

                  // 2. 添加用戶
                  UserDO userDO = request.buildUserDO();
                  long id = userDao.insert(userDO);

                  // 3. 添加成功,返回驗證激活郵件
                  if (id > 0) {
                      emailService.sendVerifyEmail(request.getEmail());
                      return ResultDTO.success(id);
                  }
                  return ResultDTO.internalError("添加用戶失敗,請稍后重試");
              }

              /**
               * 校驗添加用戶參數(shù)
               */

              private ResultDTO<Void> validateAddUserParam(AddUserRequest request) {
                  if (Objects.isNull(request)) {
                      return ResultDTO.paramError("添加用戶參數(shù)不能為空");
                  }
                  if (StringUtils.isBlank(request.getUserName())) {
                      return ResultDTO.paramError("用戶名不能為空");
                  }
                  if (!EmailValidator.validate(request.getEmail())) {
                      return ResultDTO.paramError("郵箱格式錯誤");
                  }
                  return ResultDTO.success();
              }
          }

          基于Mockito的單測示例如下,需要注意的下面是純java代碼,沒有對象顯示調(diào)用的方法都是已經(jīng)靜態(tài)導(dǎo)入過的。

          @RunWith(MockitoJUnitRunner.class)
          public class UserServiceImplTest {
              // Fake:需要提前構(gòu)造的假數(shù)據(jù)
              AddUserRequest fakeAddUserRequest;

              // Mock: mock外部依賴
              @InjectMocks
              private UserServiceImpl userService;
              @Mock
              private UserDao userDao;
              @Mock
              private EmailService emailService;

              @Before
              public void init() {
                  fakeAddUserRequest = new AddUserRequest("zhangsan""[email protected]");
                  when(userDao.insert(any())).thenReturn(1L);
                  when(emailService.sendVerifyEmail(anyString())).thenReturn(true);
              }

              @Test
              public void testAddUser4NullParam() {
                  // GIVEN
                  fakeAddUserRequest = null;
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                  assertEquals(addResult.getMsg(), "添加用戶參數(shù)不能為空");
              }
              @Test
              public void testAddUser4BadEmail() {
                  // GIVEN
                  fakeAddUserRequest.setEmail(null);
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                  assertEquals(addResult.getMsg(), "郵箱格式錯誤");
              }
              @Test
              public void testAddUser4BadUserName() {
                  // GIVEN
                  fakeAddUserRequest.setUserName(null);
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                  assertEquals(addResult.getMsg(), "用戶名不能為空");
              }

              @Test
              public void testAddUser4DbError() {
                  // GIVEN
                  when(userDao.insert(any())).thenReturn(-1L);
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                  assertEquals(addResult.getMsg(), "添加用戶失敗,請善后重試");
              }

              @Test
              public void testAddUser4SendEmail() {
                  // GIVEN
                  // WHEN
                  ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
                  // THEN
                  assertTrue(addResult.isSuccess());
                  verify(emailService, times(1)).sendVerifyEmail(any());
                  verify(emailService).sendVerifyEmail(fakeAddUserRequest.getEmail());
              }

          }

          正如上文提到的,Spock能夠讓代碼更加精簡,尤其是在代碼邏輯分支比較多的場景下。下面是基于Spock的單測。

          class UserServiceImplSpec extends Specification {
              UserServiceImpl userService = new UserServiceImpl();
              AddUserRequest fakeAddUserRequest;
              def userDao = Mock(UserDao)
              def emailService = Mock(EmailService)

              def setup() {
                  // Fake數(shù)據(jù)創(chuàng)建
                  fakeAddUserRequest = new AddUserRequest(userName: "zhangsan", email: "[email protected]")
                  // 注入Mock對象
                  userService.userDao = userDao
                  userService.emailService = emailService
              }

              def "testAddUser4BadParam"() {
                  given:
                  if (Objects.isNull(userName) || Objects.is(email)) {
                      fakeAddUserRequest = null
                  } else {
                      fakeAddUserRequest.setUserName(userName)
                      fakeAddUserRequest.setEmail(email)
                  }
                  when:
                  def result = userService.addUser(fakeAddUserRequest)
                  then:
                  Objects.equals(result.getMsg(), resultMsg)
                  where:
                  userName   | email              | resultMsg
                  null       | null               | "添加用戶參數(shù)不能為空"
                  "Java填坑筆記" | null               | "郵箱格式錯誤"
                  null       | "[email protected]" | "用戶名不能為空"
              }

              def "testAddUser4DbError"() {
                  given:
                  _ * userDao.insert(_) >> -1L
                  when:
                  def result = userService.addUser(fakeAddUserRequest)
                  then:
                  Objects.equals(result.getMsg(), "添加用戶失敗,請稍后重試")
              }

              def "testAddUser4SendEmail"() {
                  given:
                  _ * userDao.insert() >> 1
                  when:
                  def result = userService.addUser(fakeAddUserRequest)
                  then:
                  result.isSuccess()
                  1 * emailService.sendVerifyEmail(fakeAddUserRequest.getEmail())
              }
          }

          思考總結(jié)

          在驗證商業(yè)模式之前,時刻要想考慮投入產(chǎn)出比。時間和商業(yè)成本太高不利于產(chǎn)品快速推向市場,所以什么時候推廣單測,需要更高階的人決策。

          測試不可能發(fā)現(xiàn)所有錯誤,單測也不例外。單測只測試程序單元自身的功能。因此,它不能發(fā)現(xiàn)集成錯誤、性能、或者其他系統(tǒng)級別的問題。

          單測能夠提高代碼質(zhì)量,驅(qū)動代碼設(shè)計,幫助我們更早發(fā)現(xiàn)問題,保障持續(xù)優(yōu)化和重構(gòu),是工程師的一項必備技能。

          < END >

          也許你還想看
            | Java領(lǐng)域的又一神書!周志明老師YYDS!
            | 我常用的20+個學(xué)習(xí)編程的網(wǎng)站!蕪湖起飛!
            | 去香港讀CS碩士了!有點迷茫......
            | 7年前,24歲,出版了一本 Redis 神書
            | 京東二面:為什么需要分布式ID?你項目中是怎么做的?

          我是 Guide哥,一個工作2年有余,接觸編程已經(jīng)6年有余的程序員。大三開源 JavaGuide,目前已經(jīng) 100k+ Star。未來幾年,希望持續(xù)完善 JavaGuide,爭取能夠幫助更多學(xué)習(xí) Java 的小伙伴!共勉!凎!點擊即可了解我的個人經(jīng)歷。

          歡迎點贊分享。咱們下期再會!

          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久免费精品一区二区三区 | 欧美成人猛片AAAAAAA | 骚虎官网在线观看 | 操逼视频图片 | 欧美成人A猛片 |