5

什麼被認爲是單元測試複雜單元(如編譯器)的最佳方法?單元測試編譯器

多年來我已經編寫了一些編譯器和解釋器,而且我發現這種代碼很難以很好的方式進行測試。

如果我們採取類似抽象語法樹的生成。你如何使用TDD測試這個?

小構造可能很容易測試。 例如沿線的東西:

string code = @"public class Foo {}"; 
AST ast = compiler.Parse(code); 

因爲這將不會產生大量的ast節點。

但如果其實我是想測試該編譯器可以生成的方法等的東西的AST:

[TestMethod] 
public void Can_parse_integer_instance_method_in_class() 
{ 
    string code = @"public class Foo { public int method(){ return 0;}}"; 
    AST ast = compiler.Parse(code); 

你會斷言的是什麼? 手動定義代表給定代碼的AST,並斷言生成的AST符合手動定義的AST,這看起來非常麻煩,甚至可能容易出錯。

那麼什麼是TDD'ing這樣的複雜場景的最佳戰術?

+0

這只是單元測試爲什麼無用和劣質的衆多例子之一,應該集中在集成測試上。 TDD適用於CRUD,不適用於嚴重的問題。對於編譯器來說,隨機生成的代碼測試是迄今爲止最好的方法。例如:http://www.cs.utah.edu/~regehr/papers/pldi11-preprint.pdf – 2013-03-14 10:11:06

+0

您可能還對安全編譯器構造的優秀方法感興趣:http://compcert.inria.fr/doc /index.html - 形式化規範絕對比任何可能的測試更能保證質量。 – 2013-03-14 10:12:54

+0

@peer,你在說什麼「方法」?如果生成一個解析器(比如'bison'等),你將會擁有一個單一的語法和一堆生成的代碼。除了語法作爲一個整體外,沒有什麼可以測試的。如果它是一個手寫遞歸下降解析器,則更難以進行單元測試(例如,參見Clang源代碼並嘗試考慮如何模擬ASTContext和每個微小解析器條目的輸入流)。對於任何複雜的代碼,單元測試都是毫無意義的。 – 2013-03-14 10:19:24

回答

1

首先解析通常是編譯器項目的一個微不足道的部分。從我的經驗來看,它永遠不會超過10%的時間(除非我們正在談論C++,但如果您正在設計C++,那麼您不會在這裏問問題),所以您不應該將大量時間投入到解析器測試中。儘管如此,TDD(或者你稱之爲)仍然在共同開發你經常需要驗證的中間端,例如,您剛添加的優化確實會導致預期的代碼轉換。根據我的經驗,像這樣的測試通常是通過爲編譯器特製的測試程序和grepping輸出程序集預期模式實現的(這個循環是否展開了四次?我們是否設法避免內存寫入是這個函數?等等)。擦除程序集不如分析結構化表示(S-exprs或XML),但它很便宜,在大多數情況下工作正常。隨着編譯器的增長,支持非常困難。

+0

我以前在測試中實際使用過s表達式。例如。使我的AST能夠將ToString()自身轉換爲s表達式,然後簡單地聲明結果等於預期的s表達式。雖然工作很好,但它感覺hack'ish。要麼? – 2013-03-14 20:22:45

+0

嗯,這是件駭人的事,但通常人們會選擇與之共存,而不是將太多時間投入到測試中(沒有人真的喜歡測試)。它可能有助於僅對子表達式進行grep和/或允許一些容錯(不同的寄存器或標籤名稱)。 – yugr 2013-03-15 06:12:08

+0

這裏是一個很好的例子:[LLVM點亮的基礎設施](http://llvm.org/docs/TestingGuide.html) – yugr 2013-03-15 06:21:05

4

首先,如果您測試編譯器,則無法獲得足夠的測試!用戶確實依賴編譯器生成的輸出,就好像它始終是一個黃金標準一樣,所以真正意識到質量。所以,如果可以的話,用你能想出的每一個測試進行測試!其次,使用所有可用的測試方法,但在適當的地方使用它們。事實上,你可能能夠在數學上證明,某種轉變是正確的。如果你能這樣做,你應該。

但是我見過的每個編譯器內部都包含啓發式和其內部的大量優化的手工代碼;從而協助證明方法通常不再適用。在這裏,測試已經到位,我的意思是很多!

當收集的測試,請考慮不同的情況:

  1. 積極標準的一致性:您的前端應該接受某些代碼模式,編譯器必須產生其正確運行的程序。此類別中的測試需要一個黃金參考編譯器或生成器來生成測試程序的正確輸出;或者它涉及手寫程序,其中包括根據人類推理提供的值進行檢查。
  2. 負面測試:每個編譯器必須拒絕錯誤的代碼,如語法錯誤,類型不匹配等等。它必須產生某些類型的錯誤和警告消息。我不知道任何方法來自動生成這樣的測試。所以這些也需要人爲編寫。轉換測試:當你在你的編譯器(中端)中想出一個奇妙的優化時,你可能會想到一些示例代碼來演示優化。要注意在這樣一個模塊之前和之後的轉換,它們可能需要特殊的選項來編譯你的編譯器,或者只需插入一個模塊的裸機編譯器。測試一組合理的大型周邊模塊組合。我通常在特定轉換前後對中間表示進行迴歸測試,並通過與同事的深入推理來定義參考。嘗試在轉換的兩端編寫代碼,即您想要轉換的代碼片段,以及不可以轉換的代碼片段。

現在,這聽起來像是一個可怕的工作!是的,但有幫助:世界上有(C-)編譯器的幾個商業測試套件,以及可能會幫助您應用它們的專家。這裏那些知道我的小清單:

+1

這是高級的高級集成測試,並不是那種奇怪的「單元測試」由網絡編碼趕時髦的人羣。 – 2013-03-27 10:04:03