天天看點

《Google軟體測試之道》—第2章2.3節SET的招聘

本節書摘來自異步社群《google軟體測試之道》一書中的第2章2.3節set的招聘,作者【美】james whittaker , jason arbon , jeff carollo,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

2.3 set的招聘

優秀的set在各個方面都很出色:是一個編碼能力很強的程式員,可以寫功能代碼;也是一個能力很強的測試者,可以測試任何産品,有能力管理他們自己的工作和工具。優秀的set不僅可以看到樹木而且可以看到整個森林,在看到小段函數原型或者api的時候,就能想到各種使用這段代碼的方法以及怎樣破壞這段代碼。

在google,所有的代碼都存放在同一個代碼庫中,這意味着任何人可以在任何時間使用裡面的任何代碼,是以代碼本身一定要可靠且穩定。set不僅僅要發現功能開發人員遺漏的代碼缺陷,而且還要去關心其他的工程師是如何使用這些代碼子產品,并確定這種使用方式是沒有問題的,甚至還會去關心這些代碼未來适用的功能。由于google前進變化的速度非常快,是以代碼一定要保持幹淨、連貫一緻。在最初的代碼作者都不再關心這些代碼的時候,仍要保證這些代碼可以正常工作。

在面試的過程中我們如何考察這些技能和心态呢?這可不是一件容易的事但幸運的是,我們已經找到了上百個滿足條件的工程師。我們期望有這樣的混合型人才:對測試有強烈興趣和天資的開發人員。一個通用且有效的招募優秀set的方法是,給候選人和其他開發角色一樣的程式設計問題,并考察他們在處理品質與測試方面的方法。在面試過程中,set有兩次回答錯誤的機會。

常常通過一些簡單的問題就可以識别出哪些是優秀的set。在一些棘手的編碼問題或功能的正确性上浪費時間,不如考核他們是如何看待編碼和品質的。在set的一輪面試中會有一個swe或set來考察算法方面的問題。對于候選者,最好去考察如何思索問題的解決方案,而不是解決方案本身的實作上展現得多麼高雅。

注意

set的面試重點在考察候選人如何思索問題的解決方案,而不是解決方案本身的實作上有多麼高雅。

這裡有一個例子。假如這是你第一天上班,你被要求去實作一個函數acount(void* s),傳回一個字元串中大寫字母a出現的次數。

如果候選人上來就直接開始寫代碼,這無非在傳遞一個強烈的資訊:隻有一件事情需要去做而我正在做這個事情,這個事情就是寫代碼。set不會遵循這樣的世界觀。我們希望先把問題搞清楚。

這個函數是用來做什麼的?我們為什麼要建構它?這個函數的原型看起來正确嗎?我們期望候選人可以關心函數的正确性以及如何驗證期望的行為。一個問題值得更多的關注!候選人如果沒頭沒腦地就跳進來編碼,試圖解決問題,在對得測試問題上他同樣會沒頭沒腦。如果我們提出一個問題是給子產品增加測試場景,我們不希望候選人上來就直接開始羅列所有可能的測試用例,直到我們強迫他停下來。其實我們隻是希望他先執行最佳的測試用例。

set的時間是有限的。我們希望候選人能夠回過頭來尋找最有效的解決問題的方法,為先前的函數定義可以做一些改進。優秀的set在面對拙劣的api定義的情況下,在測試的過程中也可以把這個api定義變得更漂亮一些。

普通的候選人會花幾分鐘通過提問題和陳述的方式來了解需求文檔,例如以下幾點。

傳入的字元串編碼是什麼:ascii、utf-8或其他的編碼方式?

函數名字比較槽糕,應該是駝峰式(camelcased)的?需要更多說明描述,還是這裡應該遵循其他的什麼命名規範?

傳回值類型是什麼(或許面試官忘記了,是以我會增加一個int類型的傳回值在函數原型之前)?

void是危險的。我們應該考慮更合适的類型,如char。在一些編譯時刻類型檢查中可以為我們提供一些幫助。

如果隻有一個a的情況,計數結果是多少?它對小寫字母a也計數嗎?

在标準庫中不是已經有這樣的函數了嗎(為了面試的目的,假裝你是第一個實作這個函數功能的人)?

更好的候選人則會考慮的更多一些。

考慮一下擴充性:或許傳回值的類型應該是一個64位的整形,因為google經常涉及海量資料。

考慮一下複用性:為什麼這個函數是針對大寫字母a進行計數的?一個好的辦法是參數化,使得任意的字元都可以被計數,而不是使用不同的函數來實作。

考慮一下安全性:這些指針都是來自于可信任的位址嗎?

最佳的候選人會這樣考慮。

考慮擴充性。

這個函數會在shared data(譯注:資料分區,是資料庫存儲分割(partition)的一種方式。水準分割是一個資料庫的設計準則,資料以記錄行的方式存儲在不同的實體位置,而不是通過不同列的方式存儲。(database_architecture))上被作為mapreduce(注:mapreduce是分布式計算程式設計模型)的一部分運作嗎?或許這才是調用這個函數最有用的形式。在這個場景需要考慮一些什麼問題嗎?針對整個網際網路的所有文檔運作這個函數,該如何考慮性能和正确性?

如果這個子程式被每一個google查詢所調用,而且由于外部的封裝層面已經對參數做了驗證,傳遞的指針是安全的,或許減少一個空指針的檢查會每天節省上億次的cpu調用周期,并縮短使用者的響應時間。最少要了解全部參數驗證帶來的潛在影響。

考慮基于常量的優化。

我們可以假設輸入的資料是已經排好順序的嗎?如果是那樣,我們或許可以在找到第一個大寫字母b之後就快速退出。輸入的資料是什麼結構?多數情況下都是a嗎?多數是字元的混合,還是隻包含字母a和空格?如果那樣,在我們比較指令的地方或許可以做些優化。當在處理大資料,甚至小資料的時候,在代碼執行的時候對于真實的計算延遲也會有比較顯著的亞線性變化。

考慮安全性。

在許多系統上,如果這是一段對于安全敏感的代碼,可以考慮更多的非空的指針做測試。在某些系統上,1是一個非法的指針。

增加一個字元長度的參數,用以保證代碼不會運作到指定字元串之外的部分。檢查字元串長度,這個參數的值是否正常。那些不是以null結尾的字元串是黑客們的最愛。

如果指針指向的資料能被其他的線程修改,這裡就有潛在的線程安全問題。

我們是否應該使用try/catch來捕獲異常的發生?或者如果未能如預期那樣正常的調用代碼,我們或許應該傳回錯誤代碼給調用者。如果有錯誤代碼的話,這些代碼經過良好的定義并有文檔嗎?這意味着候選人在思考大型代碼庫和運作時刻的上下文環境方面的問題,這樣的思索可以避免錯誤代碼的重複和遺漏。

基本上,最佳候選人會有針對性地提出一些新觀點。如果這些觀點比較明智的話,它們都是值得考慮的。

一個優秀set候選人不應該被告之要去測試代碼,這應該是set自然要考慮的地方。

所有這些面試問題,無論是針對問題本身還是針對輸入參數都有一個關鍵之處,那就是任何通過入門級别程式設計課程的工程師都可以針對這個問題寫出簡單的功能代碼。優秀的候選人和普通的候選人在提問和思路上的表現會迥然不同。我們要確定候選人能夠感覺足夠舒适地去提出問題,如果沒有問題,我們就引導他們去提問,確定他們不會因為目前是在面試就直接去寫代碼。google的人應該質疑幾乎所有事情,但仍然會把問題解決掉。

在這裡,如果把這個面試問題的所有正确實作與常見錯誤都羅列一遍,肯定會招人讨厭,畢竟這不是一本關于程式設計或面試的書。但為了讨論的需要,讓我們使用一個簡單且常見的代碼實作方式來做讨論(譯注:在下面代碼中,第6行代碼中的‘a’應該是大寫字母‘a’,原書有誤)。注意,候選人一般都會選擇使用自己喜歡的程式設計語言,如java、python等,但這經常會引起一些問題,例如垃圾收集、類型安全、編譯和運作時刻的不同關注點等。我們同時要確定候選人可以正确了解這些問題。

候選人應該可以走查他們的代碼,指出程式中出現的指針或計數器的值在測試資料輸入之後在代碼運作時刻是如何變化的。

一般來說,普通的set候選人會做到以下這些。

在通過編寫代碼解決問題的過程中很少遇到問題。在編碼時,函數重寫沒有麻煩,很少出現基本文法錯誤,也不會混淆不同語言的文法和關鍵詞。

在了解指針方面沒有明顯錯誤,或者沒有配置設定不必要的記憶體。

在代碼開始的地方做一些輸入驗證,避免由于取值到空指針等引起比較麻煩的程式崩潰。若在被問到為何不做參數驗證的時候,則可以很好地解釋為什麼要這樣做。

在被指出代碼中有小的問題時,可以修正它們。

寫的代碼幹淨易讀。如果使用了位操作或把所有的代碼都寫在一行,則絕對不是一個好現象。代碼即使在功能上可以正常工作,讀起來也會令人嘔吐。

在輸入為一個a或null的時候,走查代碼確定能正常工作。

更優秀的候選人會做的更多一些。

考慮使用64位整型int64作為計數器變量和傳回值的類型,為了以後的相容性和避免使用者使用非常長的字元串而導緻溢出。

針對分布式的計數計算而準備一些代碼。一些對mapreduce不熟悉的候選人,會針對大字元串并行計算使用自己的簡單變量來提高響應速度。

在代碼注釋中對條件假設和常量做解釋說明。

在有很多不同的資料輸入時可以走查代碼,修複所發現的錯誤。不懂得如何發現和修複缺陷的set候選人不是合格的候選人。

在被要求去做功能測試之前就去做相應的測試。測試不應是被要求了才去做的事情。

在被要求停止之前,不停地嘗試優化解決方案。在經過區區幾分鐘的編碼和簡單測試之後,沒人敢說他的代碼就是完美的。程式的穩定性和韌性比功能正确要重要的多。

現在,我們想看候選人是否可以測試他們自己寫的代碼。令人費解或複雜棘手的測試代碼是世界上最差的代碼,但這也比沒有測試代碼強。在google,如果測試運作失敗,需要清楚地知道測試代碼在做什麼。否則,這個測試就應該被禁止掉,或是被标記為怪異的測試,或是忽略這個測試的運作失敗。如果這樣的事情發生了,這是編寫出壞代碼的swe的責任,或是代碼審查時給予通過投票的set/swe的失誤。

set應該可以用黑盒測試方法做測試,假設其他人已經實作了功能;也可以用白盒測試的方式,考慮其内部的實作可以知道哪些用例是無關的。

通常情況下,普通的候選人會這樣做。

他們會比較有條理地或體系化地提供特定的字元串(如不同的字元串大小)而不是随機的字元串。

專注于産生有意義的測試資料。考慮如何去運作大型測試和使用真實環境的資料做測試。

更優秀的候選人會這樣做的更多一些。

在并發線程中調用這個函數,去檢視在串擾(cross talk)、死鎖和記憶體洩露方面是否存在問題。

建構長時間持續運作的測試場景。例如在一個while(true)循環中調用函數,并確定他們在不間斷地長時間運作過程中保持功能正常。

在建構測試用例、測試資料的産生方法、驗證和執行上保持濃厚的興趣。

優秀候選人的例子

by jason arbon

最近有一個候選人(後來被證明他在實際工作上的表現也确實令人吃驚)在被問到如何針對這個傳回值為64位整形的api做邊界測試時,他很快地意識到由于時間和空間的限制,不可能使用實體的方法做測試。但為了做完這個題目和出于好奇心,在思考這個級别的擴充性時嘗試使用非常大量的資料來做這個測試,并提出使用google的網頁索引作為輸入資料來源。

他是如何驗證這個結果的呢?他建議使用一個并行來實作,進而保證産生兩份相同的結果。他也考慮到使用統計學上抽樣的方法:大寫字母a在網頁上出現的期望頻率。由于我們知道網頁索引後的數量,計算後的數字應該比較接近。這正是google思考測試的方式。即便我們不會真的建構這樣龐大的測試,思考這些解決方案一般也會對正正常模的測試工作提供有意義或有效的借鑒。

在面試中需要另外考慮的是文化上是否比對。set候選人在面試過程中是否在技術上有好奇心?當面對一些新想法的時候,候選人是否能夠把它融入到解決方案裡呢?又是如何處理有歧義的地方的?是否熟悉品質方面的理論學術方法?是否了解品質度量或其他領域的自動化?例如土木工程或航空工程方面的自動化。當你發現在實作中存在缺陷時,是否心存戒備,思路又是否足夠開闊?候選人不必具備所有的這些特質,但仍是越多越好。最後還要考慮在日常的工作中,我們是否願意和這個人一起工作。

需要着重強調的一點是,如果某人在應聘set崗位的時候沒有具備足夠強的編碼能力,這并不意味着此人不是一個合格的te。我們已雇傭到的一些優秀的te,之前都是來應聘set崗位的。

一個有趣的現象值得我們注意,google在set招聘過程中經常會與優秀的候選人失之交臂,原因是這些人最後成為非測試類的swe或對測試過度專注的te。我們希望set的候選人具有多樣性,他們可能會在以後工作上成為同僚。set是一個真正的混合體,但有些時候這也會導緻一些令人不悅的面試得分。我們想確定的一點是,這些低分是由于我們的面試官在使用嚴格的set考核标準而導緻。

正如patrick copeland在序言中說的那樣,關于set的招聘目前還有一些不同的觀點。如果set是一個優秀的程式設計者,他就應該隻去做功能開發的工作嗎?swe也是很難雇傭到的。如果他們擅長做測試,就應該隻是專注于解決純粹的測試問題麼?事實總是存在于兩者之間。

招聘優秀的set是一件很麻煩的事情,但這是值得的。一個明星級的set能夠對一個團隊産生巨大的影響。

繼續閱讀