<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                上節小定義了接口規范: <hr> ``` POST /Teacher/login ``` #### 參數 Parameters | type | name | Description | Schema | | --- | --- | --- | --- | | **Body** | **用戶名密碼** <br> *requried* | 登錄教師 | {username: 用戶名, password: 密碼} | #### 返回值 Responses | HTTP Code | Description | Schema | | --- | --- | --- | | **200** | Ok | 用戶密碼是否正確:正確,true; 不正確, false | <hr> 定制時序圖如下: ![](https://img.kancloud.cn/74/59/7459962638bcca890e81a8ab562d3c17_733x267.png) 按由后至前的開發順序進行開發。 # Repository 在TeacherRepository中加入findByUsername方法: repository/TeacherRepository.java ```java public interface TeacherRepository extends CrudRepository<Teacher, Long> { /** * 查找用戶 * @param username 用戶名 * @return */ Teacher findByUsername(String username); } ``` spring可以按照一些特定的方法名來自動組裝常用的查詢語句。spring見到`findByUsername`便會我們自動完成通過`username`來查找某個教師的功能。上述語句在返回值中使用了`Teacher`,則spring將通過傳入的`username`進行查找時:找到對應的教師,返回該教師實體;未找到對應的教師,則返回null。此外spring還支持以下寫法: repository/TeacherRepository.java ``` public interface TeacherRepository extends CrudRepository<Teacher, Long> { /** * 查找用戶 * @param username 用戶名 * @return */ Option<Teacher>? findByUsername(String username); } ``` 即使用`Option<Teacher>`來代替`Teacher`做為返回值,此寫法意在告知調用者:調用本方法時,可能最終獲取不到數據(比如用戶輸入了錯誤的用戶名,便不會通過該用戶在數據庫中找到對應的數據)。此處我們暫時使用第一種方式,直接將返回值類型聲明為Teacher,意在展示一種**過時**的使用方法以便我們在某些生產環境下處理一些歷史項目時也能夠得心應手。 repository/TeacherRepository.java ```java public interface TeacherRepository extends CrudRepository<Teacher, Long> { /** * 查找用戶 * @param username 用戶名 * @return */ Teacher findByUsername(String username); } ``` ## 測試 新建測試類TeacherRepositoryTest并初始化如下: ```java @SpringBootTest @RunWith(SpringRunner.class) public class TeacherRepositoryTest { @Test public void findByUsername() { } } ``` 補充測試代碼: ```java @Test public void findByUsername() { // 準備測試數據并持久化 Teacher teacher = new Teacher(); teacher.setUsername(RandomString.make(6)); this.teacherRepository.save(teacher); // 調用測試方法并斷言 Teacher teacher1 = this.teacherRepository.findByUsername(teacher.getUsername()); Assert.assertEquals(teacher, teacher1); } ``` 報錯如下: ``` java.lang.AssertionError: Expected :com.mengyunzhi.springbootstudy.entity.Teacher@608b35fa Actual :com.mengyunzhi.springbootstudy.entity.Teacher@965bcbc ``` 那么是否意味著findByUsername方法未失效呢?我們于相應的位置上加入斷言,并使用debug模式啟動項目進行查看: ![](https://img.kancloud.cn/f6/ed/f6ed4a27f9b59a2cccd27c8409d6a502_428x67.png) ![](https://img.kancloud.cn/43/1f/431fd6348914b1576252167e432e139a_331x52.png) 查看變量: ![](https://img.kancloud.cn/72/24/7224fb85fd4829ec5502f6c4ec4b1609_256x253.png) 這是由于:雖然使用findByUsername方法查詢出的教師也是數據表中ID為1的**教師數據**,但是此時該**教師數據**卻被裝入了一個全新的對象。這就像雖然兩個人的名字都叫李剛,但是他們終究是兩個人。而`Assert.assertEquals`能夠分辨出這兩個不同的李剛。 幸運的在建立數據表的時候,為每個數據都分配了一個具有唯一標識作用的`id`,在此完成可以通過該標識來判斷兩個teacher對象是否是基于同一條數據創建的。 ``` Assert.assertEquals(teacher, teacher1); ? Assert.assertEquals(teacher.getId(), teacher1.getId()); ``` 單元測試通過 # Service 服務層的初始化需要創建一個接口并同時創建該接口的實現類: service/TeacherService.java ```java package com.mengyunzhi.springbootstudy.service; import com.mengyunzhi.springbootstudy.entity.Teacher; /** * 教師 * @author 夢云智 */ public interface TeacherService { /** * 用戶登錄 * @param username 用戶名 * @param password 密碼 * @return 成功 true */ boolean login(String username, String password); /** * 驗證密碼的有效性 * @param teacher 教師 * @param password 密碼 * @return 有效 true */ boolean validatePassword(Teacher teacher, String password); } ``` 實現類:service/TeacherServiceImpl.java ```java package com.mengyunzhi.springbootstudy.service; import com.mengyunzhi.springbootstudy.entity.Teacher; import org.springframework.stereotype.Service; @Service public class TeacherServiceImpl implements TeacherService { @Override public boolean login(String username, String password) { return false; } @Override public boolean validatePassword(Teacher teacher, String password) { return false; } } ``` ## login 此方法先調用倉庫層獲取相關的教師,接著將數據轉發給validatePassword。 service/TeacherServiceImpl.java ``` public class TeacherServiceImpl implements TeacherService { private TeacherRepository teacherRepository; ? @Autowired ? public TeacherServiceImpl(TeacherRepository teacherRepository) { this.teacherRepository = teacherRepository; } ``` * ? 聲明私有變量 * ? 使用Autowired對構造函數進行注解,spring將自動裝入構造函數中聲明的全部類型。 >[success] 這種將Autowired添加在構造函數上的方法是推薦使用的方式 補充功能代碼: ```java @Override public boolean login(String username, String password) { Teacher teacher = this.teacherRepository.findByUsername(username); return this.validatePassword(teacher, password); } ``` ### 單元測試 初始化如下: service/TeacherServiceImplTest.java ```java package com.mengyunzhi.springbootstudy.service; import org.junit.Test; import static org.junit.Assert.*; public class TeacherServiceImplTest { @Test public void login() { } @Test public void validatePassword() { } } ``` 在每次測試用例執行完先創建一個供測試的`TeacherServiceImpl` service/TeacherServiceImplTest.java ```java public class TeacherServiceImplTest { private TeacherServiceImpl teacherService; ? private TeacherRepository teacherRepository; ? @Before public void before() { this.teacherRepository = Mockito.mock(TeacherRepository.class); ? TeacherServiceImpl teacherService = new TeacherServiceImpl(this.teacherRepository); ? this.teacherService = Mockito.spy(teacherService); ? } ``` * ? 定義兩個在測試中可能被使用的私有屬性 * ? 獲取一個TeacherRepository替身 * ? 創建一個真實的TeacherServiceImpl * ? clone一個與TeacherServiceImpl具有相同功能的替身。 * ? 在該替身上可以使用Mockito對替身上的部分方法進行自主替換 >[success] 此測試方法的最大優點在于:由于單元測試未依賴spring,所以將大幅度減小單元用例的啟動時間。這也將是教程在此后的單元測試中優先使用的測試方法。 補充代碼: service/TeacherServiceImplTest.java ```java @Test public void login() { // 請求及模擬返回數據準備 String username = RandomString.make(6); String password = RandomString.make(6); Teacher teacher = new Teacher(); Mockito.when(this.teacherRepository.findByUsername(username)).thenReturn(teacher); ? Mockito.doReturn(true).when(this.teacherService).validatePassword(teacher, password); ? // 調用 boolean result = this.teacherService.login(username, password); // 斷言 Assert.assertTrue(result); ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class); Mockito.verify(this.teacherRepository).findByUsername(stringArgumentCaptor.capture()); Assert.assertEquals(stringArgumentCaptor.getValue(), username); } ``` * ? this.teacherRepository是由Mockito.mock初始化而來,是一個徹頭徹尾的替身,推薦使用Mockito.when對其進行設置。 * ? this.teacherService是由Mockito.spy根據真實的對象clone而言,該替身擁有著與真身相同的功能,只能夠使用Mockito.doReturn對其進行設置 ## validatePassword 初始化如下: service/TeacherServiceImpl.java ``` @Override public boolean validatePassword(Teacher teacher, String password) { if (teacher == null || teacher.getPassword() == null || password == null) { return false; } return teacher.getPassword().equals(password); } ``` 此時發現在教師實體中未設置password字段,進行相應的增加,這說明在系統規劃時并沒有考慮充分。雖然我們要盡力減少諸如此類的變更,但事實告訴我們此類變更不可避免。這當然也是軟件工程存在意義,因為軟件工程的目標是:致力于打造易維護、易修改的代碼。 entity/Teacher.java ```java private String password = "yunzhi"; ? public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } ``` * ? 設置個默認密碼 ### 單元測試 service/TeacherServiceImplTest.java ```java @Test public void validatePassword() { // 教師中有密碼,且密碼與傳入的密碼相同,返回true ? // 教師為null返回false // 傳入的密碼為null返回false // 未設置教師的密碼,返回false // 教師中的密碼與傳入的密碼不相同返回false } ``` * ? 先測試成功的,這很重要 測試返回true service/TeacherServiceImplTest.java ```java @Test public void validatePassword() { // 教師中有密碼,且密碼與傳入的密碼相同,返回true Teacher teacher = new Teacher(); String password = RandomString.make(6); teacher.setPassword(password); Assert.assertTrue(this.teacherService.validatePassword(teacher, password)); ``` 測試其它 service/TeacherServiceImplTest.java ```java @Test public void validatePassword() { // 教師中有密碼,且密碼與傳入的密碼相同,返回true Teacher teacher = new Teacher(); String password = RandomString.make(6); teacher.setPassword(password); Assert.assertTrue(this.teacherService.validatePassword(teacher, password)); // 教師為null返回false Assert.assertFalse( this.teacherService.validatePassword( null, password)); // 傳入的密碼為null返回false Assert.assertFalse( this.teacherService.validatePassword( teacher, null)); // 未設置教師的密碼,返回false teacher.setPassword(null); Assert.assertFalse( this.teacherService.validatePassword( teacher, password)); // 教師中的密碼與傳入的密碼不相同返回false teacher.setPassword(RandomString.make(6)); Assert.assertFalse( this.teacherService.validatePassword( teacher, password)); } ``` 單元測試通過 # C層 C層的開發主要參數接口文檔 controller/TeacherController.java ```java @Autowired TeacherService teacherService; ? ... @PostMapping("login") public boolean login(@RequestBody Teacher teacher) { return this.teacherService.login(teacher.getUsername(), teacher.getPassword()); } ``` ## 單元測試 初始化如下: controller/TeacherControllerTest.java ```java package com.mengyunzhi.springbootstudy.controller; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.junit.Assert.*; @SpringBootTest ★ @RunWith(SpringRunner.class) ★ @AutoConfigureMockMvc ★ public class TeacherControllerTest { @Autowired MockMvc mockMvc; ★ @Test public void login() { } } ``` * ★ C層的單元測試需要借助于spring的MockMvc,必須依賴于spring。 完善代碼: controller/TeacherControllerTest.java ``` @Test public void login() throws Exception { // 準備數據 String url = "/Teacher/login"; String username = RandomString.make(6); String password = RandomString.make(6); JSONObject jsonObject = new JSONObject(); jsonObject.put("username", username); jsonObject.put("password", password); // 當以參數username, password調用teacherService.login方法時,返回true Mockito.when(this.teacherService.login(username, password)).thenReturn(true); // 觸發C層并斷言返回值 this.mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON_UTF8) .content(jsonObject.toJSONString())) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("true")?); // 斷言獲取的參數與傳入值相同 ArgumentCaptor<String> usernameArgumentCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> passwordArgumentCaptor = ArgumentCaptor.forClass(String.class); Mockito.verify(this.teacherService).login( usernameArgumentCaptor.capture(), passwordArgumentCaptor.capture()); Assert.assertEquals(username, usernameArgumentCaptor.getValue()); Assert.assertEquals(password, passwordArgumentCaptor.getValue()); } ``` * ? 斷言返回的內容為true 單元測試通過。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.1.3](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.1.3) | - |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看