Facebook 如何做大規模服務的自主測試

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"允許開發者快速開發新特性的原型設計、測試和迭代,對於 Facebook 的成功至關重要。要想有效地實現這一點,關鍵是要有一個穩定的基礎設施,並且不會帶來不必要的摩擦。如果相關的基礎設施還必須擴大,以支持全球 30 多億人口,利用日益增長的算力,以及應對一個極其龐大且不斷增長的代碼庫,那麼這一點顯然更加具有挑戰性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解決這個問題的兩種方式是更好的抽象和自動化測試。抽象包含了面向服務的基礎設施,它允許業務邏輯結構變成獨立編寫、部署和擴展組件。儘管這對於快速迭代非常重要,但是也增加了測試的複雜性。在檢查服務中的邏輯時,單元測試是有用的,但是無法測試服務間的依賴關係。集成測試可以起到拯救作用,但是,相對標準化的單元測試框架來說,沒有現成的集成測試框架可供我們用於後端服務。所以我們設計並構建了這個。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/c0\/d9\/c036fc0fff842283748aa34ddd448fd9.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"Facebook 的基礎設施俯瞰視圖,強調了後端測試選項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"今天,我們將詳細介紹在這個集成測試基礎設施之上建立的一個新的自主測試擴展,並且也是對這一基礎設施本身的幕後觀察。這個擴展借鑑了模糊測試的理念,即一種自動化技術,它使用隨機輸入來發現 bug,並利用軟件棧的同質性來提供無縫的開發體驗,鼓勵快速迭代。迄今爲止,Facebook 的大多數自主測試集中在我們的前端,或者通過 Infer、Sapienz 和 Zoncolan 等工具進行的安全測試。在此,我們將討論我們如何自主測試後端服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"集成測試基礎設施"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"集成測試基礎設施需要鼓勵工程師編寫有效的測試,在需要時自動運行測試,並以直觀的方式顯示結果。這種方式通過提供代碼框架、測試調度和執行能力,以及持續集成系統的適當鉤子來實現這一點。代碼框架封裝了樣板文件,並提供了常見的抽象和模式,以消除編寫測試時使用片狀結構等常見陷阱。本文討論了編寫測試的三個方面,着重於與集成測試相關的部分:定義測試環境、指定輸入以及檢查輸出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/74\/f1\/74919e2eca19c50d553e7b29bac7d4f1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"集成測試的組件。測試基礎設施爲工程師編寫測試提供了基礎,併爲運行測試提供了執行平臺。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定義測試環境"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲提供確定的結果和避免副作用,測試通常不在生產環境中運行。這對於單元測試尤其適用,單元測試集中於一個小的代碼單元,使用模擬或假象來替代外部依賴。儘管這樣做可以避免副作用,但是它有不利的一面,那就是測試系統沒有足夠逼近。模擬程序本身僅實現了一些真正依賴關係中的行爲。所以,有些 bug 可能無法檢測到。維護模擬也需要相當大的工程努力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"集成測試較少依賴模擬。測試後端服務通常涉及一個或多個未修改的服務。無需修改服務進行測試,這有一些好處。第一,它避免了服務所有者的負擔,但更重要的是,它使測試與在生產中運行的代碼相同,從而使它們更具代表性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這提出了兩個必須解決的重要挑戰。首先是創建測試環境,適合運行未修改的服務。其次,必須確定如何設置測試環境的邊界以及如何處理這些邊界之間的連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些挑戰要求採取務實的做法。我們的解決方案重用生產基礎設施,尤其是用於構建測試環境的容器化和路由器系統。但是,我們在基礎設施中爲每一個測試創建單獨的短暫實體。這樣,測試環境就可以不接受生產請求,而不自動限制連接到生產系統。這使得一些測試可以與生產環境共享只讀資產或 API。與此同時,我們使用附加的隔離層來限制其他測試到生產系統的連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過基於希望檢查的服務交互,我們授權服務所有者定義測試環境的邊界。在相同環境中,與調用者運行的服務優先提供網絡請求。如果環境中不存在適當的服務,請求可以轉到模擬、轉到生產系統的內存副本,或者被阻止,或者轉發到生產環境(例如,只讀請求)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於此設置,模擬是通過動態創建和啓動一個服務來工作的,這個服務與原始服務具有相同的接口,但實現方式簡單。模擬服務運行在測試工具所在的地址空間中。這樣可以方便地進行交互。可以在運行時更改模擬實現,這與更改單元測試模擬的方式相似。我們把每個模擬方法處理程序包裝成一個標準的 Python MagicMock 或 StrictMock。通過這樣操作,可以輕鬆地檢查它的調用次數以及它接收的參數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於常見的依賴關係,例如存儲,內存複製非常有用。出這些選項外,測試基礎設施還組織了測試環境中的連接。稍後,我們將在自主測試中詳細討論這個問題。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"測試輸入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常來說,測試輸入是以測試工具的方式提供的,它是一個在測試環境中與測試服務一起執行的程序。一個測試工具可以直接執行服務,也可以通過遠程調用(RPC),或者在測試環境中間接執行更改。舉例來說,它可以應用新的全局配置設置或者關閉測試服務的副本,儘管測試框架爲這些操作提供了原語,但是構建測試由服務所有者負責。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在集成測試中,模板是另一種輸入源。可將其配置爲發送特定的響應,從根本上作爲被測服務的輸入。依賴失敗代表輸入的一種特殊情況,可以通過拋出模擬中的異常來模擬。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"測試 Oracle"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大多數測試 Oracle 都是針對服務行爲的自定義斷言。儘管這些斷言原則上和單元測試斷言類似,但是它們只能檢查被測服務的外部可見行爲,而非內部狀態。它包括 RPC 響應、傳遞到模擬調用的參數、寫入到短暫的測試數據庫的數據以及其他示例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"測試基礎設施還可以檢測常見的錯誤,比如崩潰或由消毒器標記的 bug,以及被監控基礎設施標記的服務健康問題。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"可擴展性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"集成測試基礎設施的設計目標之一就是讓團隊能夠在其之上構建擴展。對於這個擴展,我們主要有兩種用途。首先要解決團隊服務中心出現的常見模式,例如測試環境設置或者經常使用的自定義。它們還可以爲特定類型的測試定義基礎,例如災難準備測試。在需要時,這些測試驗證了我們能夠從頭啓動最基本的基礎設施服務。比如 ZooKeeper。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"自主集成測試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述框架爲服務所有者編寫集成測試提供了框架。但是,在很多情況下,測試基礎設施可以通過提供合理的默認值甚至自動生成所有測試組件來做得更好。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要定義測試環境,這個基礎設施將反映服務的生產環境。在 Facebook 的集羣管理系統 Twine 上,它以一種標準的方式定義了所有的服務。但是,它也可以通過編程的方式對這些組件進行檢查。在進行特定修改之前,測試可以檢查環境,並檢查它的合理性,然後將它返回到 Twine 進行實例化。當服務所有者在某些情況下需要干預時,比如一個服務需要特殊的硬件,而在默認的測試機池中不存在,則健全檢查負責通知服務所有者。特定的修改測試包括將測試實例與生產系統隔離,減少服務的資源需求以節省容量,以及其他較小的修改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隔離是自主測試中一個特別重要的元素。這是因爲測試基礎設施決定了使用哪些輸入。不過,無論它的選擇如何,測試一定不能產生副作用。舉例來說,有一種情況是,一個測試中的 API 失敗的數據到達了監控基礎設施,它錯誤地認爲故障是來自生產系統。結果,它產生了虛假的警報。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘管從技術角度來看非常簡單,但是將測試環境與基礎設施的其他部分完全隔離常常會導致測試失敗。正因爲如此,我們必須採用更細粒度的方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以通過已知的只讀流量。我們讓服務所有者可以爲安全目標設置允許列表。我們將所有標準 PRC 流量都重新路由到通用模擬中,這樣就可以模擬任何服務並返回虛假的值。我們禁止所有其他網絡請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣,我們就可以在一個安全的測試環境中運行三分之一的服務,而不需要人工干預。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實施隔離時,我們結合了兩種方法:一種是細粒度的應用級隔離,另一種是粗粒度的網絡級隔離。我們對 RPC 調用了應用級隔離。這可以基於調用的 API 來阻止連接。在 IP: 端口的粒度級別,網絡級隔離是工作的。一般情況下,我們使用它允許連接到諸如 DNS 等知名端口上的監聽服務。除基於連接目的地作出決定外,隔離系統還可以根據啓動連接的代碼作出決定。這樣做是有價值的,因爲某些代碼可以安全地使用可能不安全的 API,該隔離邏輯通過在運行時檢查堆棧跟蹤來識別調用者。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於隔離層的構建,我們考慮了兩種實現方案。BPF 和 LD_PRELOAD。最後,我們決定採用後者,因爲它提供了更大的靈活性。預加載邏輯從我們的配置管理系統中檢索特定於服務的隔離配置,並通過攔截對 libc connect、sendto、sendmsg 和 sendmmsg 函數的調用,從而相應地阻止連接。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"測試輸入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲深入探討測試自動化,我們研究了現有的自動化技術。模糊測試是我們集成測試結構的自然匹配。它的動態性質非常適合經典的測試範式,而它的自動輸入生成則補充了手動編寫的測試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模糊測試的核心是一種隨機測試形式。不過,儘管它很簡單,但是設置模糊測試仍然需要幾個手工步驟:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將需要模糊測試的代碼分割成一個獨立的單元(測試目標)。與單元測試相同,典型的模糊測試是在一個相對較小的代碼單元上進行的。編寫一個模糊測試工具,負責將隨機數據生成爲模糊測試代碼所需的類型,並在測試目標中調用正確的函數。保證隨機數據符合測試目標的預期約束條件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後一點需要進一步澄清。假定需要對 strlen 函數進行模糊處理,該函數期望一個有效的指針指向一個以 NULL 結尾的字符串。在模糊處理生成輸入時,它需要確保所有輸入都是指向 NULL 結束的字符串的有效指針。否則可能會導致代碼崩潰——並不是因爲在代碼中存在 bug,而是由於調用者參數與被調用者期望之間的不匹配。這些期望(也稱爲 API 契約)常常是隱式的,因此需要手工完成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在結合了模糊測試和集成測試時,我們可以實現上述手工步驟的自動化:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們根據前面描述的生產環境來創建環境。這使得我們無需手工劃出代碼,而這些代碼必須進行測試。這要歸功於 Facebook 的 Thrift RPC 框架,這個服務的 API 契約是顯式的,可以通過編程的方式獲得。Thrift 提供了一個接口定義語言,它具有反射特性,可以枚舉 API 及其參數。而且,參數類型和其屬性也可以像需求一樣被遞歸地檢查。基於這些信息生成相應的值之後,就可以動態地實例化每個參數。用兩種方法來使用這個能力。第一,構建所測試服務的輸入。其次,自動模擬服務的依賴關係,並將其返回默認值。這樣就可以用正確的格式自動生成隨機數據,自動創建模糊控制。最後,一個服務可能不期望接收到網絡上的輸入。這樣可以消除由於輸入值和服務預期不匹配而導致崩潰的所有機會,這意味着所有遇到的崩潰都會指向實際的 bug。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構建輸入最簡單的方法是爲每個數據類型隨機地選擇合適的值。這種方法自動提供了一個測試基線,並且具有確定工程師在手工設計測試時可能忽略的極端情況的優勢。對於每一個數據類型,像 MAX_INT 這樣的極端情況值,可以增強這一過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨機(和模糊)測試的弱點是,當輸入的有效性涉及複雜的約束時,例如校驗和,它是無效的。單純的模糊測試難以在這種情況下找到有效的輸入。除了任何初始的輸入驗證外,它不會執行服務邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們使用請求記錄來克服這一問題和提高自主測試的效率。對於訪問生產服務的一小部分請求,我們定期記錄,對這些請求進行“清理”,並將它們提供給測試基礎設施。在這種情況下,測試不會以完全隨機的輸入運行,而是記錄的請求會有不同程度的變化。這種方法的基本原則是,所得到的的輸入將保留足夠的原始請求的有效結構,以執行“深層路徑”,但是也有足夠的隨機性,可以鍛鍊這些路徑的極端情況。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在純粹的模糊處理的另一端,我們使用記錄的請求,而不改變它。它證明,新版本的服務能夠在沒有異常行爲的情況下處理上一版本的流量,就像經典的金絲雀測試一樣。以記錄和重放的方式解決了這個問題,好處是不需要獨立的測試基礎設施,也不會影響生產系統。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"測試 Oracle"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除崩潰和類似 ASAN 等“消毒器”外,我們還要查找未聲明的異常,並檢查日誌中的可疑信息。MySQL ProgrammingError 異常是一個有趣的示例。這種異常通常由模糊器能夠影響 SQL 查詢的功能所觸發。可以通過調用帶有意外參數的 API 實現,這表示存在 SQL 注入漏洞。Python 語法錯誤(SyntaxError)異常也是相似的。本例中,模糊器可以修改傳遞給 eval 的字符串,並指向可能執行的任意代碼。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"部署與經驗教訓"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自主集成測試的部署策略包括兩個步驟。首先,我們開始在後臺爲儘可能多的服務運行測試,而不需要服務所有者的參與。這樣,我們就可以知道有什麼機會可以改善,以及報告問題的最佳方式。下一步,我們鼓勵服務所有者選擇在部署其新版本服務之前自動運行該測試。我們選擇了 opt-in 模式,因爲測試失敗需要立即採取行動來解除服務的部署管道。我們現在從第一步進入第二步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在第一步中,通過應用於模糊集成測試的隔離,我們可以安全、自動地對大約三分之一的 Facebook 的 Thrift 服務進行模糊測試。這個模糊測試發現了超過 1000 個 bug。對於每一個 bug,我們都給服務所有者分配了一份報告。餘下的三分之二的服務都具有非標準的設置,或者具有嚴格的權限,使得我們不能重用他們的生產工件,或者由於我們執行的嚴格隔離而失敗。在這一步中,我們只通過 bug 報告與服務所有者接觸。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過這一過程,我們學到了一些東西。首先,通過我們的測試,我們發現在隔離測試環境方面有許多機會可以改進。因此,我們支持使用更細粒度和可擴展的方法來標記只讀 API。此外,我們還繼續考慮如何爲集成測試環境提供一個一流的抽象,並通過可組合性提供重複使用測試環境的能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二,我們學到了,向服務所有者提供儘可能多的有關我們檢測到的 bug 的信息是非常重要的。與單元測試相比,在集成測試失敗的情況下,調試本來就很困難。儘管堆棧跟蹤有助於瞭解崩潰,但是有效的調試還需要對崩潰的服務及其使用的庫有很好的理解。我們注意到,從總體上看,違反 API 契約要比崩潰更容易調試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三,我們注意到,隨機輸入使得解釋 bug 變得更加困難,工程師們也更難找出 bug 的根源。通過更廣泛地使用記錄的流量和提供大多數形式良好的輸入,我們可以解決這一問題。有些情況下,工程師可能會認爲,給定隨機輸入,服務可能會通過拋出未聲明的異常或崩潰而破壞其 API 契約。建議反對這一做法,而應依靠全面的輸入驗證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,瞭解模式測試的有效性也非常重要。迄今爲止,我們僅將發現的 bug 作爲有效性度量,現在我們開始測量整個服務覆蓋範圍。我們希望這能讓服務所有者深入瞭解服務的哪些部分需要額外的測試。本文也指出了我們可以對集成模糊測試基礎設施進行改變,以增加未來的整體覆蓋。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Paul Marinescu,Facebook 研究科學家。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/engineering.fb.com\/2021\/10\/20\/developer-tools\/autonomous-testing\/"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章