日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

不要仅仅依靠单元测试

發(fā)布時(shí)間:2023/12/3 编程问答 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 不要仅仅依靠单元测试 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

當(dāng)您構(gòu)建一個(gè)復(fù)雜的系統(tǒng)時(shí),僅僅測(cè)試組件是不夠的。 這很關(guān)鍵,但還不夠。 想象一下一家汽車廠生產(chǎn)并進(jìn)口最高質(zhì)量的零件,但組裝好之后再也不會(huì)啟動(dòng)發(fā)動(dòng)機(jī)了。 如果您的測(cè)試用例套件幾乎不包含單元測(cè)試,則您將永遠(yuǎn)無(wú)法確保系統(tǒng)整體正常運(yùn)行。 讓我們舉一個(gè)人為的例子:

public class UserDao {public List<User> findRecentUsers() {try {return //run some query} catch(EmptyResultDataAccessException ignored) {return null;}}//... }

我希望您已經(jīng)在catch塊中發(fā)現(xiàn)了一個(gè)反模式(而且我并不是想忽略異常,這似乎是可以預(yù)期的)。 作為好公民,我們決定通過(guò)以下方式解決問(wèn)題: 返回一個(gè)空集合,而不是null :

public class UserDao {public List<User> findRecentUsers() {try {return //run some query} catch(EmptyResultDataAccessException ignored) {return Collections.emptyList();}}//... }

修復(fù)非常簡(jiǎn)單,我們幾乎忘記了運(yùn)行單元測(cè)試,但是以防萬(wàn)一我們執(zhí)行它們并發(fā)現(xiàn)第一個(gè)失敗的測(cè)試用例:

public class UserDaoTest {private UserDao userDao;@Beforepublic void setUp() throws Exception {userDao = new UserDao();}@Testpublic void shouldReturnNullWhenNoRecentUsers() throws Exception {//given//whenfinal List<User> result = userDao.findRecentUsers();//thenassertThat(result).isNull();}@Testpublic void shouldReturnOneRecentUser() throws Exception {//givenfinal User lastUser = new User();userDao.storeLoginEvent(lastUser);//whenfinal List<User> result = userDao.findRecentUsers();//thenassertThat(result).containsExactly(lastUser);}@Testpublic void shouldReturnTwoRecentUsers() throws Exception {//givenfinal User lastUser = new User();final User oneButLastUser = new User();userDao.storeLoginEvent(oneButLastUser);userDao.storeLoginEvent(lastUser);//whenfinal List<User> result = userDao.findRecentUsers();//thenassertThat(result).containsExactly(lastUser, oneButLastUser);}}

顯然,不僅代碼被破壞了(通過(guò)返回null而不是像null的空集合),而且還進(jìn)行了一項(xiàng)測(cè)試來(lái)驗(yàn)證這種虛假行為。 我很確定測(cè)試是在實(shí)現(xiàn)之后編寫的,并且必須以某種方式處理現(xiàn)實(shí)。 在沒(méi)有實(shí)施特性的事先知識(shí)的情況下,沒(méi)有人會(huì)編寫這樣的測(cè)試。 因此,我們修復(fù)了測(cè)試,并樂(lè)意等待綠色CI的建立–最終來(lái)了。 幾天后,我們的應(yīng)用程序在生產(chǎn)中因NullPointerException中斷。 它打破了經(jīng)過(guò)徹底的單元測(cè)試的地方:

public class StatService {private final UserDao userDao;public StatService(UserDao userDao) {this.userDao = userDao;}public void welcomeMostRecentUser() {final List<User> recentUsers = userDao.findRecentUsers();if (recentUsers != null) {welcome(recentUsers.get(0));}}private void welcome(User user) {//...} }

我們很驚訝,因?yàn)榇祟愐驯粏卧獪y(cè)試完全覆蓋(為清楚起見(jiàn),省略了驗(yàn)證步驟):

@RunWith(MockitoJUnitRunner.class) public class WelcomeServiceTest {@Mockprivate UserDao userDaoMock;private WelcomeService welcomeService;@Beforepublic void setup() {welcomeService = new WelcomeService(userDaoMock);}@Testpublic void shouldNotSendWelcomeMessageIfNoRecentUsers() throws Exception {//givengiven(userDaoMock.findRecentUsers()).willReturn(null);//whenwelcomeService.welcomeMostRecentUser();//then//verify no message sent}@Testpublic void shouldSendWelcomeMessageToMostRecentUser() throws Exception {//givengiven(userDaoMock.findRecentUsers()).willReturn(asList(new User()));//whenwelcomeService.welcomeMostRecentUser();//then//verify user welcomed}//...}

您知道問(wèn)題出在哪里嗎? 我們更改了UserDao類的合同,同時(shí)使它在表面上“看起來(lái)”相同。 通過(guò)修復(fù)損壞的測(cè)試,我們認(rèn)為它仍然可以工作。 但是, WelcomeService仍然依賴UserDao的舊行為,該行為要么返回null ,要么返回具有至少一個(gè)元素的列表。 使用模擬框架記錄了此行為,因此我們能夠?qū)卧械腤elcomeService進(jìn)行單獨(dú)測(cè)試。 換句話說(shuō),我們無(wú)法確保這兩個(gè)組件仍然可以正常工作,我們僅對(duì)它們進(jìn)行了單獨(dú)測(cè)試。 回到我們的汽車隱喻–所有零件仍然可以放在一起(相同的合同),但是其中一個(gè)內(nèi)部的行為與以前不同。 那么,到底出了什么問(wèn)題? 這里至少存在四個(gè)問(wèn)題,如果緩解了任何一個(gè),這些都不會(huì)發(fā)生。

首先, UserDao的作者未能認(rèn)識(shí)到返回null而空列表似乎更加直觀。 這引出了一個(gè)問(wèn)題:有沒(méi)有之間的差異顯著null和空集? 如果是,也許您正在嘗試在單個(gè)返回值中“編碼”太多信息? 如果沒(méi)有,為什么還要增加API使用者的生活呢? 遍歷空集合不需要任何額外的工作; 對(duì)可能為null collection進(jìn)行迭代需要一個(gè)額外的條件。 WelcomeService作者也因假定null表示空集合而失敗。 他應(yīng)該解決丑陋的API,而不要依賴它。 在這種情況下,他本可以使用CollectionUtils.isNotEmpty()并更具防御性:

if (CollectionUtils.isNotEmpty(recentUsers)) {

對(duì)于更全面的解決方案,他還可以考慮裝飾 UserDao并將null替換為空collection。 甚至使用AOP在整個(gè)應(yīng)用程序中全局修復(fù)此類API。 順便說(shuō)一句,這也適用于String 。 在99%的情況下, null ,空字符串和很少有空格的字符串之間沒(méi)有“業(yè)務(wù)”差異。 除非您真的想?yún)^(qū)分它們,否則默認(rèn)情況下使用StringUtils.isBlank()或類似名稱。

最終,“修復(fù)” UserDao的人看不到大圖。 僅僅修復(fù)單元測(cè)試是不夠的。 當(dāng)您在不更改API的情況下更改類的行為時(shí)(這對(duì)于動(dòng)態(tài)語(yǔ)言尤為重要),您很可能會(huì)錯(cuò)過(guò)使用該API的地方,從而失去上下文。 但是最大的失敗是缺少組件/系統(tǒng)測(cè)試 。 如果僅使用一個(gè)同時(shí)運(yùn)行WelcomeService 和 UserDao ,就會(huì)發(fā)現(xiàn)此錯(cuò)誤。 僅有100%的代碼覆蓋率是不夠的。 您測(cè)試了拼圖的每一個(gè)部分,但從未看過(guò)完成的圖片。 至少進(jìn)行一些較大的煙霧測(cè)試。 否則,您將不再具有如此強(qiáng)大的信心,即當(dāng)測(cè)試呈綠色時(shí),代碼就可以使用了。

參考: 不要單靠我們的JCG合作伙伴 Tomasz Nurkiewicz的NoBlogDefFound博客進(jìn)行單元測(cè)試 。

翻譯自: https://www.javacodegeeks.com/2013/02/dont-rely-on-unit-tests-alone.html

總結(jié)

以上是生活随笔為你收集整理的不要仅仅依靠单元测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。