有人喜歡創造世界,他們作了開發者;有的人喜歡開發者,他們作了測試員。什麼是軟件測試?軟件測試就是一場本該在用戶面前發生的災難提早在本身面前發生了,這會讓他們生出一種救世主的感受,拯救了用戶,也就拯救者這個軟件,避免了他們被卸載的命運。css
我目前是 Pilot.com 的一位高級工程師,負責給創業公司提供自動記帳服務。在此以前,我曾是 Dropbox 的桌面客戶端組的成員,我今天將分享關於我當時工做的一些故事。更早以前,我是 Recurse Center 的導師,給身在紐約的程序員提供臨時的訓練環境。在成爲工程師以前,我在大學攻讀天體物理學並在金融界工做過幾年。python
但這些都不重要——關於我你惟一須要知道的是,我愛 bug。我愛 bug 由於它們有趣。它們富有戲劇性。調試一個好的 bug 的過程能夠很是迂迴曲折。一個好的 bug 像是一個有趣的笑話或者或者謎語——你指望看到某種結果,但卻事與願違。程序員
在這個演講中我會給大家講一些我曾經熱愛過的 bug,解釋爲何我如此愛 bug,而後說服大家也一樣去熱愛 bug。web
好,讓咱們直接來看第一個 bug。這是我在 Dropbox 工做時遇到的一個 bug。大家或許據說過,Dropbox 是一個將你的文件從一個電腦上同步到雲端和其餘電腦上的應用。面試
+--------------+ +---------------+
| | | | | 元數據服務器 | | 塊服務器 | | | | | +-+--+---------+ +---------+-----+ ^ | ^ | | | | | +----------+ | | +---> | | | | | 客戶端 +--------+ +--------+ | +----------+
這是個極度簡化的 Dropbox 架構圖。桌面客戶端在你的電腦本地運行,監聽文件系統的變更。當它檢測到文件改動時,它讀取改變的文件,並把它的內容 hash 成 4 MB 大小的文件塊。這些文件塊被存放在後端一個叫作塊服務器blockserver的巨大的鍵值對數據庫key-value store中。數據庫
固然,咱們想避免屢次上傳同一個文件塊。能夠想見,若是你在編寫一份文檔,你應該大部分時候都在改動文檔最底部——咱們不想一遍又一遍地上傳開頭部分。因此在上傳文件塊到塊服務器以前以前,客戶端會先和一個負責管理元數據和權限等等的服務器溝通。客戶端會詢問這個元數據服務器metaserver它是須要這個文件塊,仍是已經見過這個文件塊了。元數據服務器會返回每個文件塊是否須要上傳。編程
因此這些請求和響應看上去大概是這樣:客戶端說「我有一個改動過的文件,分爲這些文件塊,它們的 hash 是 'abcd,deef,efgh'
。服務器響應說「我有前兩塊,但須要你上傳第三塊」。而後客戶端會把那個文件塊上傳到塊服務器。後端
+--------------+ +---------------+
| | | | | 元數據服務器 | | 塊服務器 | | | | | +-+--+---------+ +---------+-----+ ^ | ^ | | '有, 有, 無' | 'abcd,deef,efgh' | | +----------+ | efgh: [內容] | +---> | | | | | 客戶端 +--------+ +--------+ | +----------+
這是問題的背景。下面是 bug。ruby
+--------------+
| | | 塊服務器 | | | +-+--+---------+ ^ | | | '???' 'abcdldeef,efgh' | | +----------+ ^ | +---> | | ^ | | 客戶端 + +--------+ | +----------+
有時候客戶端會提交一個奇怪的請求:每一個 hash 值應該包含 16 個字母,但它卻發送了 33 個字母——所需數量的兩倍加一。服務器不知道該怎麼處理它,因而會拋出一個異常。咱們收到這個異常的報告,因而去查看客戶端的記錄文件,而後會看到很是奇怪的事情——客戶端的本地數據庫損壞了,或者 python 拋出 MemoryError,沒有一個合乎情理的。bash
若是你之前沒見過這個問題,可能會以爲毫無頭緒。但當你見過一次以後,你之後每次看到都能輕鬆地認出它來。給你一個提示:在那些 33 個字母的字符串中,l
常常會代替逗號出現。其餘常常出現的字符是:
l \x0c < $ ( . -
英文逗號的 ASCII 碼是 44。l
的 ASCII 碼是 108。它們的二進制表示以下:
bin(ord(',')): 0101100 bin(ord('l')): 1101100
你會注意到 l
和逗號只差了一位。問題就出在這裏:發生了位反轉。桌面客戶端使用的內存中的一位發生了錯誤,因而客戶端開始向服務器發送錯誤的請求。
這是其餘常常代替逗號出現的字符的 ASCII 碼:
, : 0101100 l : 1101100 \x0c : 0001100 < : 0111100 $ : 0100100 ( : 0101000 . : 0101110 - : 0101101
我愛這個 bug 由於它證實了位反轉是可能真實發生的事情,而不僅是一個理論上的問題。實際上,它在某些狀況下會比平時更容易發生。其中一種狀況是用戶使用的是低配或者老舊的硬件,而運行 Dropbox 的電腦不少都是這樣。另一種會形成不少位反轉的地方是外太空——在太空中沒有大氣層來保護你的內存不受高能粒子和輻射的影響,因此位反轉會十分常見。
你大概很是在意在宇宙中運行的程序的正確性——你的代碼或許事關國際空間站中宇航員的性命,但即便沒有那麼重要,也還要考慮到在宇宙中很難進行軟件更新。若是你的確須要讓你的程序可以處理位反轉,有不少硬件和軟件措施可供你選擇,Katie Betchold 還關於這個問題作過一個很是有意思的講座。
在剛纔那種狀況下,Dropbox 並不須要處理位反轉。出現內存損壞的是用戶的電腦,因此即便咱們能夠檢測到逗號字符的位反轉,但若是這發生在其餘字符上咱們就不必定能檢測到了,並且若是從硬盤中讀取的文件自己發生了位反轉,那咱們根本無從得知。咱們能改進的地方不多,因而咱們決定無視這個異常並繼續程序的運行。這種 bug 通常都會在客戶端重啓以後自動解決。
這是我最喜歡的 bug 之一,有幾個緣由。第一,它提醒我注意不常見和不可能之間的區別。當規模足夠大的時候,不常見的現象會以值得注意的頻率發生。
這個 bug 第二個讓我喜歡的地方是它覆蓋面很是廣。每當桌面客戶端和服務器交流的時候,這個 bug 均可能悄然出現,而這可能會發生在系統裏不少不一樣的端點和組件當中。這意味着許多不一樣的 Dropbox 工程師會看到這個 bug 的各類版本。你第一次看到它的時候,你 真的 會滿頭霧水,但在那以後診斷這個 bug 就變得很容易了,而調查過程也很是簡短:你只需找到中間的字母,看它是否是個 l
。
這個 bug 的一個有趣的反作用是它展現了服務器組和客戶端組之間的文化差別。有時候這個 bug 會被服務器組的成員發現並展開調查。若是你的 服務器 上發生了位反轉,那應該不是個偶然——這極可能是內存損壞,你須要找到受影響的主機並儘快把它從集羣中移除,否則就會有損壞大量用戶數據的風險。這是個事故,而你必須迅速作出反應。但若是是用戶的電腦在破壞數據,你並無什麼能夠作的。
若是你在調試一個難搞的 bug,特別是在大型系統中,不要忘記跟別人討論。也許你的同事之前就遇到過相似的 bug。如果如此,你可能會節省不少時間。就算他們沒有見過,也不要忘記在你解決了問題以後告訴他們解決方法——寫下來或者在組會中分享。這樣下次大家組遇到相似的問題時,大家都會早有準備。
在加入 Dropbox 以前,我曾在 Recurse Center 工做。它的理念是創建一個社區讓正在自學的程序員們聚到一塊兒來提升能力。這就是 Recurse Center 的所有了:咱們沒有大綱、做業、截止日期等等。惟一的前提條件是咱們都想要成爲更好的程序員。參與者中有的人有計算機學位但對本身的實際編程能力不夠自信,有的人已經寫了十年 Java 但想學 Clojure 或者 Haskell,還有各式各樣有着其餘的背景的參與者。
我在那裏是一位導師,幫助人們更好地利用這個自由的環境,並參考咱們從之前的參與者那裏學到的東西來提供指導。因此個人同事們和我本人都很是熱衷於尋找對成年自學者最有幫助的學習方法。
在學習方法這個領域有不少不一樣的研究,其中我以爲最有意思的研究之一是刻意練習的概念。刻意練習理論意在解釋專業人士和業餘愛好者的表現的差距。它的基本思想是若是你只看內在的特徵——不論先天與否——它們都沒法很是好地解釋這種差距。因而研究者們,包括最初的 Ericsson、Krampe 和 Tesch-Romer,開始尋找可以解釋這種差距的理論。他們最終的答案是在刻意練習上所花的時間。
他們給刻意練習的定義很是精確:不是爲了收入而工做,也不是爲了樂趣而玩耍。你必須盡本身能力的極限,去作一個和你的水平相稱的任務(不能太簡單致使你學不到東西,也不能太難致使你沒法取得任何進展)。你還須要得到即時的反饋,知道本身是否作得正確。
這很是使人興奮,由於這是一套可以用來創建專業技能的系統。但難點在於對於程序員來講這些建議很是難以實施。你很難知道你是否處在本身能力的極限。也不多有即時的反饋幫助你改進——有時候你能獲得任何反饋都已經算是很幸運了,還有時候你須要等幾個月才能獲得反饋。對於在 REPL 中作的簡單的事情你能夠很快地獲得反饋,但若是你在作一個設計上的決定或者技術上的選擇,你在很長一段時間裏都沒法獲得反饋。
可是在有一類編程工做中刻意練習是很是有用的,它就是 debug。若是你寫了一份代碼,那麼當時你是理解這份代碼是如何工做的。但你的代碼有 bug,因此你的理解並不徹底正確。根據定義來講,你正處在你理解能力的極限上——這很好!你立刻要學到新東西了。若是你能夠重現這個 bug,那麼這是個寶貴的機會,你能夠得到即時的反饋,知道本身的修改是否正確。
像這樣的 bug 也許能讓你學到關於你的程序的一些小知識,但你也可能會學到一些關於運行你的代碼的系統的一些更復雜的知識。我接下來要講一個關於這種 bug 的故事。
這也是我在 Dropbox 工做時遇到的 bug。當時我正在調查爲何有些桌面客戶端沒有像咱們預期的那樣持續發送日誌。我開始調查客戶端的日誌系統而且發現了不少有意思的 bug。我會挑一些跟這個故事有關的 bug 來說。
和以前同樣,這是一個很是簡化的系統架構。
+--------------+
| | +---+ +----------> | 日誌服務器 | |日誌| | | | +---+ | +------+-------+ | | +-----+----+ | 200 ok | | | | 客戶端 | <-----------+ | | +-----+----+ ^ +--------+--------+--------+ | ^ ^ | +--+--+ +--+--+ +--+--+ +--+--+ | 日誌 | | 日誌 | | 日誌 | | 日誌 | | | | | | | | | | | | | | | | | +-----+ +-----+ +-----+ +-----+
桌面客戶端會生成日誌。這些日誌會被壓縮、加密並寫入硬盤。而後客戶端會間歇性地把它們發送給服務器。客戶端從硬盤讀取日誌併發送給日誌服務器。服務器會將它解碼並存儲,而後返回 200。
若是客戶端沒法鏈接到日誌服務器,它不會讓日誌目錄無限地增加。超過必定大小以後,它會開始刪除日誌來讓目錄大小不超過一個最大值。
最初的兩個 bug 自己並不嚴重。第一個 bug 是桌面客戶端向服務器發送日誌時會從最先的日誌而不是最新的日誌開始。這並非很好——好比服務器會在客戶端報告異常的時候讓客戶端發送日誌,因此你可能最在意的是剛剛生成的日誌而不是在硬盤上的最先的日誌。
第二個 bug 和第一個類似:若是日誌目錄的大小達到了上限,客戶端會從最新的日誌而不是最先的日誌開始刪除。同理,你老是會丟失一些日誌文件,但你大概更不在意那些較早的日誌。
第三個 bug 和加密有關。有時服務器會沒法對一個日誌文件解碼(咱們通常不知道爲何——也許發生了位反轉)。咱們在後端沒有正確地處理這個錯誤,而服務器會返回 500。客戶端看到 500 以後會作合理的反應:它會認爲服務器停機了。因此它會中止發送日誌文件而且再也不嘗試發送其餘的日誌。
對於一個損壞的日誌文件返回 500 顯然不是正確的行爲。你能夠考慮返回 400,由於問題出在客戶端的請求上。但客戶端一樣沒法修復這個問題——若是日誌文件如今沒法解碼,咱們後也永遠沒法將它解碼。客戶端正確的作法是直接刪除日誌文件而後繼續運行。實際上,這正是客戶端在成功上傳日誌文件並從服務器收到 200 的響應時的默認行爲。因此咱們說,好——若是日誌文件沒法解碼,就返回 200。
全部這些 bug 都很容易修復。前兩個 bug 出在客戶端上,因此咱們在 alpha 版本修復了它們,但大部分的客戶端尚未得到這些改動。咱們在服務器代碼中修復了第三個 bug 並部署了新版的服務器。
忽然日誌服務器集羣的流量開始激增。客服團隊找到咱們並問咱們是否知道緣由。我花了點時間把全部的部分拼到一塊兒。
在修復以前,這四件事情會發生:
日誌文件從最先的開始發送
日誌文件從最新的開始刪除
若是服務器沒法解碼日誌文件,它會返回 500
若是客戶端收到 500,它會中止發送日誌
一個存有損壞的日誌文件的客戶端會試着發送這個文件,服務器會返回 500,客戶端會放棄發送日誌。在下一次運行時,它會嘗試再次發送一樣的文件,再次失敗,並再次放棄。最終日誌目錄會被填滿,而後客戶端會開始刪除最新的日誌文件,而把損壞的文件繼續保留在硬盤上。
這三個 bug 致使的結果是:若是客戶端在任什麼時候候生成了損壞的日誌文件,咱們就不再會收到那個客戶端的日誌了。
問題是,處於這種狀態的客戶端比咱們想象的要多不少。任何有一個損壞文件的客戶端都會像被關在堤壩裏同樣,沒法再發送日誌。如今這個堤壩被清除了,全部這些客戶端都開始發送它們的日誌目錄的剩餘內容。
好的,如今文件從世界各地的電腦如洪水般涌來。咱們能作什麼?(當你在一個有 Dropbox 這種規模,尤爲是這種桌面客戶端的規模的公司工做時,會遇到這種有趣的事情:你能夠很是輕易地對本身形成 DDoS 攻擊)。
當你部署的新版本發生問題時,第一個選項是回滾。這是很是合理的選擇,但對於這個問題,它沒法幫助咱們。咱們改變的不是服務器的狀態而是客戶端的——咱們刪除了那些出錯文件。將服務器回滾能夠防止更多客戶端進入這種狀態,但它並不能解決根本問題。
那擴大日誌集羣的規模呢?咱們試過了——而後由於處理能力增長了,咱們開始收到更多的請求。咱們又擴大了一次,但你不可能一直這麼下去。爲何不能?由於這個集羣並非獨立的。它會向另外一個集羣發送請求,在這裏是爲了處理異常。若是你的一個集羣正在被 DDoS,而你持續擴大那個集羣,你最終會把它依賴的集羣也弄壞,而後你就有兩個問題了。
咱們考慮過的另外一個選擇是減低負載——你不須要每個日誌文件,因此咱們能夠直接無視一些請求。一個難點是咱們並無一個很好的方法來區分好的請求和壞的請求。咱們沒法快速地判斷哪些日誌文件是舊的,哪些是新的。
咱們最終使用的是一個 Dropbox 裏許多不一樣場合都用過的一個解決方法:咱們有一個自定義的頭字段,chillout
,全世界全部的客戶端都遵照它。若是客戶端收到一個有這個頭字段的響應,它將在字段所標註的時間內再也不發送任何請求。很早之前一個英明的程序員把它加到了 Dropbox 客戶端裏,在以後這些年中它已經不止一次地起了做用。
這個 bug 的第一個教訓是要了解你的系統。我對於客戶端和服務器之間的交互有不錯的理解,但我並無考慮到當服務器和全部這些客戶端同時交互的時候會發生什麼。這是一個我沒有徹底搞懂的層面。
第二個教訓是要了解你的工具。若是出了差錯,你有哪些選項?你能撤銷你作的遷移嗎?你如何知道事情出了差錯,你又如何發現更多信息?全部這些事情都應該在危機發生以前就瞭解好——但若是你沒有,你會在危機發生時學到它們並不會再忘記。
第三個教訓是專門針對移動端和桌面應用開發者的:你須要服務器端功能控制和功能開關。當你發現一個問題時若是你沒有服務器端的功能控制,你可能須要幾天或幾星期來推送新版本或者提交新版本到應用商店中,而後問題才能獲得解決。這是個很糟糕的處境。Dropbox 桌面客戶端不須要通過應用商店的審查過程,但光是把一個版本推送給上千萬的用戶就已經要花不少時間。相比之下,若是你能在新功能遇到問題的時候在服務器上翻轉一個開關:十分鐘以後你的問題就已經解決了。
這個策略也有它的代價。加入不少的功能開關會大幅提升你的代碼的複雜度。而你的測試代碼更是會成指數地複雜化:要考慮 A 功能和 B 功能都開啓,或者僅開啓一個,或者都不開啓的狀況——而後每一個功能都要相乘一遍。讓工程師們在過後清理他們的功能開關是一件很難的事情(我本身也有這個毛病)。另外,桌面客戶端會同時有好幾個版本有人使用,也會加大思考難度。
可是它的好處——啊,當你須要它的時候,你真的是很須要它。
我講了幾個我愛的 bug,也講了爲何要愛 bug。如今我想告訴你如何去愛 bug。若是你如今還不愛 bug,我知道惟一一種改變的方法,那就是要有成長型心態。
社會學家 Carol Dweck 作了不少關於人們如何看待智力的研究。她找到兩種不一樣的看待智力的心態。第一種,她叫作固定型心態,認爲智力是一個固定的特徵,人類沒法改變本身智力的多寡。另外一種心態叫作成長型心態。在成長型心態下,人們相信智力是可變的並且能夠經過努力來加強。
Dweck 發現一我的看待智力的方式——固定型仍是成長型心態——能夠很大程度地影響他們選擇任務的方式、面對挑戰的反應、認知能力、甚至是他們的誠信度。
【我在新西蘭 Kiwi Pycon 會議所作的主題演講中也討論過成長型心態,因此在此只摘錄一部份內容。你能夠在這裏找到完整版的演講稿】
關於誠信的發現:
在這以後,他們讓學生們給筆友寫信講這個實驗,信中說「咱們在學校作了這個實驗,這是我得的分數」。他們發現 因智力而受到表揚的學生中幾乎一半人謊報了本身的分數 ,而因努力而受表揚的學生則幾乎沒有人不誠實。
關於努力:
數個研究發現有着固定型心態的人會不肯真正去努力,由於他們認爲這意味着他們不擅長作他們正努力去作的這件事情。Dweck 寫道,「若是每當一個任務須要努力的時候你就會懷疑本身的智力,那麼你會很難對本身的能力保持自信。」
關於面對困惑:
他們發現有成長型心態的學生大約能理解 70% 的內容,不論裏面是否有難懂的段落。在有固定型心態的學生中,那些被分配沒有難懂段落的手冊的學生一樣能夠理解大約 70%。但那些看到了難懂段落的持固定型心態的學生的記憶則降到了 30%。有着固定型心態的學生很是不擅長從困惑中恢復。
這些發現代表成長型心態對 debug 相當重要。咱們必須從從困惑中重整旗鼓,誠實地面對咱們理解上的不足,並時不時地在尋找答案的路上努力奮鬥——成長型心態會讓這些都變得更簡單並且不那麼痛苦。
我在 Recurse Center 工做時會直白地歡迎挑戰,我就是這樣學會熱愛個人 bug 的。有時參與者會坐到我身邊說「唉,我以爲我遇到了個奇怪的 Python bug」,而後我會說「太棒了,我 愛 奇怪的 Python bug!」 首先,這百分之百是真的,但更重要的是,我這樣是在對參與者強調,找到讓本身以爲困難的事情是一種成就,而他們作到了這一點,這是件好事。
像我以前說過的,在 Recurse Center 沒有截止日期也沒有做業,因此這種態度沒有任何成本。我會說,「你如今能夠花一成天去在 Flask 裏找出這個奇怪的 bug 了,多使人興奮啊!」在 Dropbox 和以後的 Pilot,咱們有產品須要發佈,有截止日期,還有用戶,因而我並不老是對在奇怪的 bug 上花一成天而感到興奮。因此我對有截止日期的現實也是感同身受。可是若是我有 bug 須要解決,我就必須得去解決它,而抱怨它的存在並不會幫助我以後更快地解決它。我以爲就算在截止日期臨近的時候,你也依然能夠保持這樣的心態。
若是你熱愛你的 bug,你能夠在解決困難問題時得到更多樂趣。你能夠擔憂得更少而更加專一,而且從中學到更多。最後,你能夠和你的朋友和同事分享你的 bug,這將會同時幫助你本身和你的隊友們。
軟件測試學習交流羣:273462828,羣裏有分享的視頻,面試指導,測試資料,還有思惟導圖、羣裏有視頻,都是乾貨的,你能夠下載來看。主要分享測試基礎、接口測試、性能測試、自動化測試、TestOps架構、Jmeter、LoadRunner、Fiddler、MySql、Linux、簡歷優化、面試技巧以及大型測試項目實戰視頻資料。合理利用本身的時間來學習提高本身,不要在焦慮中舉步維艱,迷失在焦急慌亂之間。
獲取往期資深測開工程師精講資料、精講視頻、Jmeter、自動化測試、腳本編寫技巧、Fiddler進階抓包、接口自動化測試實戰等等技術,可加入軟件測試學習交流羣:273462828