stringboot TDD測(cè)試驅(qū)動(dòng)開(kāi)發(fā)

應(yīng)用程序在分發(fā)之前應(yīng)該經(jīng)過(guò)測(cè)試和驗(yàn)證。測(cè)試的目的是驗(yàn)證應(yīng)用程序是否符合功能和非功能要求利虫,并檢測(cè)應(yīng)用程序中的錯(cuò)誤俘陷。

TDD:測(cè)試驅(qū)動(dòng)開(kāi)發(fā)

一旦需求和規(guī)范得到驗(yàn)證,就可以開(kāi)始一個(gè)稱(chēng)為測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的過(guò)程愿待。您首先編寫(xiě)測(cè)試浩螺,然后開(kāi)發(fā)代碼。

將根據(jù)商定的要求和規(guī)范創(chuàng)建測(cè)試(測(cè)試評(píng)審方案)呼盆;最初測(cè)試會(huì)失敗年扩,我們將在應(yīng)用程序中編寫(xiě)代碼以確保測(cè)試通過(guò)。一旦測(cè)試通過(guò)访圃,我們可以重構(gòu)應(yīng)用程序中的代碼以改進(jìn)它并再次啟動(dòng)測(cè)試厨幻。

此類(lèi)測(cè)試應(yīng)由分析師設(shè)計(jì)并由開(kāi)發(fā)人員實(shí)施。如果我們注意到某個(gè)規(guī)范的測(cè)試很難開(kāi)發(fā),我們應(yīng)該考慮這樣一個(gè)事實(shí)况脆,即該規(guī)范可能不正確或至少是不明確的饭宾。

多虧了 TDD 技術(shù),我們可以在開(kāi)發(fā)的早期階段發(fā)現(xiàn)任何問(wèn)題格了】疵考慮到解決問(wèn)題的努力與找到問(wèn)題所需的時(shí)間成正比。

單元和集成測(cè)試

單元測(cè)試作為類(lèi)方法驗(yàn)證應(yīng)用程序的一小部分的功能盛末,并且獨(dú)立于應(yīng)用程序的其他單元并隔離弹惦。

好的公司,單元測(cè)試都是在開(kāi)發(fā)完畢后悄但,自己就進(jìn)行處理完成了棠隐。當(dāng)然測(cè)試人員不是不可以做,主要是側(cè)重點(diǎn)不同檐嚣。

我們可以將各個(gè)單元視為應(yīng)用程序的各個(gè)層助泽。

因此,一個(gè)好的單元測(cè)試獨(dú)立于整個(gè)應(yīng)用程序基礎(chǔ)設(shè)施嚎京,如數(shù)據(jù)庫(kù)類(lèi)型和其他層嗡贺。如果測(cè)試方法與其他單元有依賴(lài)關(guān)系,它們可以被模擬(可能使用像 Mockito 這樣的庫(kù))鞍帝。在我們將要做的示例中诫睬,我們將測(cè)試一個(gè) Service 方法,并且該測(cè)試將獨(dú)立于數(shù)據(jù)庫(kù)和 Spring 的上下文(因此該測(cè)試適用于任何用于依賴(lài)注入的框架)膜眠。

集成測(cè)試驗(yàn)證應(yīng)用程序的多個(gè)單元的操作岩臣。這里使用的框架的上下文也用于測(cè)試階段。

需求示例

根據(jù)需求宵膨,我們被要求實(shí)現(xiàn) findById 的 REST API 并創(chuàng)建一個(gè) User 模型架谎。要求產(chǎn)品經(jīng)理提供明確的需求,開(kāi)發(fā)去實(shí)現(xiàn)產(chǎn)品提供的需求辟躏。

成功返回:

特別是谷扣,在 findById 中,客戶(hù)端必須收到 200 并且在響應(yīng)正文中收到找到的用戶(hù)的 JSON捎琐。

失敗或者不存在返回:

如果沒(méi)有具有輸入 id 的用戶(hù)会涎,則客戶(hù)端必須收到帶有空正文的 404。

此外瑞凑,客戶(hù)端會(huì)看到用戶(hù)的 2 個(gè)字段:姓名和地址末秃;名稱(chēng)由姓氏、空格和名字組成籽御。

該項(xiàng)目將具有以下層:

實(shí)體將包含具有姓名练慕、姓氏和地址的實(shí)體用戶(hù)

dtos 將包含映射實(shí)體 User 的 DTO UserDTO惰匙,帶有字段名稱(chēng)(“姓氏 + 名字”)和地址

負(fù)責(zé)將 User 轉(zhuǎn)換為 UserDTO 的轉(zhuǎn)換器,反之亦然

存儲(chǔ)庫(kù)將包含實(shí)體用戶(hù)的 Spring JpaRepository

將包含 UserService 的服務(wù)铃将,該服務(wù)將從數(shù)據(jù)庫(kù)中輕松檢索用戶(hù)并使用轉(zhuǎn)換器將其轉(zhuǎn)換為 DTO

將包含 UserController 的控制器项鬼,它負(fù)責(zé)映射 REST 調(diào)用并使用服務(wù)的業(yè)務(wù)邏輯。

第一步:讓我們創(chuàng)建實(shí)體和 dto

@Entity

public class User implements Serializable {

? ? @Id

? ? @GeneratedValue(strategy = GenerationType.IDENTITY)

? ? private Long id;

? ? private String name;

? ? private String surname;

? ? private String address;

? ? public User() {}

? ? public User(String name, String surname, String address) {

? ? ? ? this.name = name;

? ? ? ? this.surname = surname;

? ? ? ? this.address = address;

? ? }

? ? //getter, setter, equals and hashcode

}

@Entity

public class UserDTO {

? ? //surname + name

? ? private String name;

? ? private String address;

? ? public UserDTO() {}

? ? public UserDTO(String name, String address) {

? ? ? ? this.name = name;

? ? ? ? this.address = address;

? ? }


? ? //getter, setter, equals and hashcode

}

第二步:讓我們創(chuàng)建轉(zhuǎn)換器


@Component

public class UserConverter {

? ? public UserDTO userToUserDTO(User user) {

? ? ? ? return new UserDTO(user.getSurname() + " " + user.getName(), user.getAddress());

? ? }

? ? public User userDTOToUser(UserDTO userDTO) {

? ? ? ? String[] surnameAndName = userDTO.getName().split(" ");

? ? ? ? return new User(surnameAndName[1], surnameAndName[0], userDTO.getAddress());

? ? }

}

第三步:讓我們?yōu)?User 實(shí)體創(chuàng)建一個(gè)存儲(chǔ)庫(kù)


public interface UserRepository extends JpaRepository<User,Long> {

}

第四步:讓我們用 findById 方法創(chuàng)建服務(wù)


@Service

public class UserServiceImpl implements UserService {

? ? private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

? ? private UserRepository userRepository;

? ? private UserConverter userConverter;

? ? public UserServiceImpl(UserRepository userRepository, UserConverter userConverter) {

? ? ? ? this.userRepository = userRepository;

? ? ? ? this.userConverter = userConverter;

? ? }

? ? @Override

? ? public UserDTO findById(Long id) {

? ? ? ? return null;

? ? }

}

正如我們所見(jiàn)劲阎,該方法目前返回 null绘盟。我們將在運(yùn)行測(cè)試后實(shí)現(xiàn)該功能。

第五步:我們創(chuàng)建測(cè)試類(lèi)來(lái)測(cè)試服務(wù)的findById方法

當(dāng)輸入中提供有效值時(shí)悯仙,甚至當(dāng)提供無(wú)效值時(shí)龄毡,良好的測(cè)試應(yīng)該驗(yàn)證方法的行為。

該服務(wù)依賴(lài)于存儲(chǔ)庫(kù)和轉(zhuǎn)換器锡垄。我們對(duì)存儲(chǔ)庫(kù)的實(shí)現(xiàn)不感興趣稚虎,因此模擬它是一個(gè)好主意。對(duì)于轉(zhuǎn)換器偎捎,我們可以考慮做同樣的事情,但由于它是一個(gè)非承蛉粒瑣碎的類(lèi)茴她,我們可以考慮在測(cè)試中使用真正的類(lèi),但不帶出Spring的上下文程奠。

建立一個(gè)測(cè)試類(lèi)

@ExtendWith(MockitoExtension.class)

public class UserServiceTest {

? ? @Mock

? ? private UserRepository userRepository;

? ? @Spy

? ? private UserConverter userConverter;

? ? private UserService userService;

? ? @BeforeEach

? ? public void init() {

? ? ? ? userService = new UserServiceImpl(userRepository, userConverter);

? ? }

? ? @Test

? ? public void findByIdSuccess() {

? ? ? ? User user = new User("Vincenzo", "Racca", "via Roma");

? ? ? ? user.setId(1L);

? ? ? ? when(userRepository.findById(anyLong())).thenReturn(Optional.of(user));

? ? ? ? UserDTO userDTO = userService.findById(1L);

? ? ? ? verify(userRepository, times(1)).findById(anyLong());


? ? ? ? assertNotNull(userDTO);

? ? ? ? String[] surnameAndName = userDTO.getName().split( " ");

? ? ? ? assertEquals(2, surnameAndName.length);

? ? ? ? assertEquals(user.getSurname(), surnameAndName[0]);

? ? ? ? assertEquals(user.getName(), surnameAndName[1]);

? ? ? ? assertEquals(user.getAddress(), userDTO.getAddress());

? ? }

}

我們來(lái)分析一下代碼:

@ExtendWith(MockitoExtension.class) 允許我們使用 Mockito 庫(kù)的 mockato 上下文丈牢。 @ExtendWith 也對(duì)應(yīng)于 JUnit 4 的 @RunWith。

我們使用 @Mock 注釋存儲(chǔ)庫(kù)瞄沙,因?yàn)槲覀兿M?Mockito 創(chuàng)建接口的 mockata 實(shí)現(xiàn)己沛。

我們使用@Spy 對(duì)轉(zhuǎn)換器進(jìn)行注釋?zhuān)韵?Mockito 表明我們想要使用真正的類(lèi)。

我們使用 @BeforeEach 注釋每次運(yùn)行測(cè)試方法時(shí)初始化 userService 的 init 方法距境。由于我們使用了構(gòu)造函數(shù)依賴(lài)注入而不是字段注入申尼,我們只需要在構(gòu)造函數(shù)中傳入 mockato 存儲(chǔ)庫(kù)即可創(chuàng)建服務(wù)。 @BeforeEach 對(duì)應(yīng)于 JUnit 4 的 @Before 注解垫桂。

當(dāng)然這里也可用junit5或者是testng

我們來(lái)分析一下測(cè)試:

我們期望對(duì)于任何輸入 id师幕,存儲(chǔ)庫(kù)將返回一個(gè)名為 Vincenzo、姓氏 Racca 和通過(guò) Roma 的地址的用戶(hù)诬滩。為此霹粥,我們將 Mockito 的靜態(tài)方法與 anyLong(指示任何 id)一起使用,然后使用 thenReturn 指示我們期望從該方法中獲得的返回值疼鸟。當(dāng)我們調(diào)用該服務(wù)時(shí)后控,我們期望返回一個(gè)名為 Racca Vincenzo 和地址 via Roma.\ 的 UserDTO。通過(guò)驗(yàn)證空镜,我們驗(yàn)證服務(wù)調(diào)用存儲(chǔ)庫(kù)的 findById 1 次浩淘。然后遵循各種瑣碎的斷言捌朴。

我們執(zhí)行測(cè)試:它在調(diào)用 verify 時(shí)已經(jīng)失敗,因?yàn)榉?wù)方法從未調(diào)用存儲(chǔ)庫(kù)馋袜;

第六步:讓我們修復(fù)方法


public class UserNotFoundException extends RuntimeException {

? ? public UserNotFoundException(Long id) {

? ? ? ? super("User with id " + id + " not found!");

? ? }

}

@Test

public void findByIdUnSuccess() {

? ? when(userRepository.findById(anyLong())).thenReturn(Optional.empty());

? ? UserNotFoundException exp = assertThrows(UserNotFoundException.class, () -> userService.findById(1L));

? ? assertEquals("User with id 1 not found!", exp.getMessage());

}

我們非常簡(jiǎn)單地告訴 Mockito男旗,對(duì)于任何 id,存儲(chǔ)庫(kù)都不會(huì)找到任何用戶(hù)欣鳖。使用 assertThrows 我們返回我們期望由服務(wù)啟動(dòng)的異常察皇,然后我們?cè)诋惓O⑸蠈?xiě)一個(gè)斷言。這種測(cè)試在 JUnit 5 中是可能的泽台。實(shí)際上什荣,在 JUnit 4 中,我們可以驗(yàn)證該方法是否啟動(dòng)了異常怀酷,但是一旦啟動(dòng)了錯(cuò)誤稻爬,您就無(wú)法繼續(xù)進(jìn)行斷言。

顯然測(cè)試失敗蜕依,所以讓我們修復(fù)方法桅锄。

現(xiàn)在測(cè)試通過(guò)了,我們可以評(píng)估以改進(jìn)代碼而不會(huì)忘記重新測(cè)試該方法样眠。

第七步:重構(gòu)方法

@Override

public UserDTO findById(Long id) {

? ? User user = userRepository.findById(id).orElseThrow(() -> {

? ? ? ? UserNotFoundException exp = new UserNotFoundException(id);

? ? ? ? LOGGER.error("Exception is UserServiceImpl.findById", exp);

? ? ? ? return exp;

? ? });

? ? return userConverter.userToUserDTO(user);

}

結(jié)論

我們通過(guò)使用 JUnit 5 開(kāi)發(fā)單元測(cè)試并在 Mockito 的幫助下模擬測(cè)試不感興趣的單元友瘤,簡(jiǎn)要了解了測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的工作原理。在下一篇文章中檐束,我們將通過(guò)創(chuàng)建控制器來(lái)繼續(xù)開(kāi)發(fā)需求辫秧,我們將使用 Spring Boot、JUnit 5 和 H2 作為內(nèi)存數(shù)據(jù)庫(kù)編寫(xiě)集成測(cè)試被丧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盟戏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子甥桂,更是在濱河造成了極大的恐慌柿究,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄选,死亡現(xiàn)場(chǎng)離奇詭異笛求,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)糕簿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)探入,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人懂诗,你說(shuō)我怎么就攤上這事蜂嗽。” “怎么了殃恒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵植旧,是天一觀(guān)的道長(zhǎng)辱揭。 經(jīng)常有香客問(wèn)我,道長(zhǎng)病附,這世上最難降的妖魔是什么问窃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮完沪,結(jié)果婚禮上域庇,老公的妹妹穿的比我還像新娘。我一直安慰自己覆积,他們只是感情好听皿,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著宽档,像睡著了一般尉姨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吗冤,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天又厉,我揣著相機(jī)與錄音,去河邊找鬼椎瘟。 笑死馋没,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的降传。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼勾怒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼婆排!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起笔链,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤段只,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鉴扫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赞枕,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年坪创,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炕婶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莱预,死狀恐怖柠掂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情依沮,我是刑警寧澤涯贞,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布枪狂,位于F島的核電站,受9級(jí)特大地震影響宋渔,放射性物質(zhì)發(fā)生泄漏州疾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一皇拣、第九天 我趴在偏房一處隱蔽的房頂上張望严蓖。 院中可真熱鬧,春花似錦审磁、人聲如沸谈飒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杭措。三九已至,卻和暖如春钾恢,著一層夾襖步出監(jiān)牢的瞬間手素,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工瘩蚪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泉懦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓疹瘦,卻偏偏與公主長(zhǎng)得像崩哩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子言沐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容