2016年5月4日 星期三

Python 參數產生unittest

最近在寫傳說中 jserv 大神的amacc,一個奉行「極致的簡」的C compiler
參與專案的第一步當然是先看issues,那時剛好看到這個:
https://github.com/jserv/amacc/issues/13
看起來不算太難,改一下code 執行流程就好了

不過首先,這個project 有點難以測試,的確make check 會嘗試編譯所有tests/*.c 檔,並且用jit 和產生elf 的方式去執行,可是卻不會通知結果是不是正確的,nested for loop 的結果也會和其他檔案混在一起,直接下指令去測又很麻煩(要用qemu,指令好長…)

覺得測試這麼麻煩,連帶著改issue 也好麻煩的感覺,因此決定不想改了先來幫這個project 加上一些便於測試的功能

想到test 就想到python unittest,設計概念是:利用python subprocess 去呼叫amacc跟arm-linux-gnueabihf-gcc 分別編譯執行檔,執行後,直接比較兩者的output,看結果是否相同,如此就能驗證amacc 的行為(這有個問題是gcc 未必遵照C standard,真正正確的答案應該要看C standard 才對,不過我們假定大多數狀況gcc會遵照C standard)

這裡遇到一個問題,在tests 裡面有許多的c files,因為對每個檔案要做的事情其實是一樣的,自己寫只會變成複製貼上,還可能貼錯內容;寫一個test 去跑所有檔案也不行,這樣等於是所有測試混在一起,test 沒過可能是任一個檔案有錯,根本看不出是誰錯了。

所幸這個解法也不困難,簡單google 就找到了:

概念是,本來的module 變成一個容器,裡面要自己寫的function 就不寫了:
class TestAmacc(unittest.TestCase):
  pass

另外寫一個共同的test generator,會傳回測試的function,我這個test function 裡面就是本來要對檔案做的事:amacc編譯、gcc 編譯、執行、比較結果,參數f為要執行的檔名:
def testGenerator(f):
  def test(self):
  # test function here
return test

最後是對每個檔案去呼叫這個generator,利用setattr 的方式,把這個function 督進先前宣告的容器當中,python 的彈性由此可見:
for dirpath, _, filenames in os.walk("tests"):
  for f in filter(lambda name: namePattern in name, filenames):
    testfile = os.path.abspath(os.path.join(dirpath, f))
    test_func = testGenerator(testfile)
    setattr(TestAmacc, 'test_%s' % (os.path.splitext(f)[0]), test_func)
unittest.main(argv=[sys.argv[0]])

如此就能自動產生test case 了,如上所示,還可以幫檔名加上filter,用參數來指定要跑什麼測試,極度方便;這裡遇到一點卡關是,unittest 預設會吃主程式的argv作為要跑的test module 名稱,argv 加上參數的話會讓unittest 炸開,必須要避免unittest.main 吃到不是給它的argv 參數以,免它找不到測試module 直接回報出錯。

沒有留言:

張貼留言