2016-04-24 61 views
10

我覺得一切都是冠軍,但我是專門找:如何在ocaml中進行測試驅動開發?

  • 什麼是「標準」的單元測試框架ocaml的?
  • 如何在構建中集成執行測試?
  • 如何在每次文件更改時自動執行測試?

作爲獎勵,我會感興趣的測試覆蓋率工具...

+0

沒有堅持ocaml;詹金斯提供了這樣做的框架。關於ocaml的tst覆蓋率:有Bisect(但基於camlp4)。 –

+2

詹金斯不適用於TDD,它是一個持續集成引擎以及廚房水槽......編輯的問題更精確。 – insitu

回答

9

看來,包ounit享有相當大的人氣,還有其他幾個包像kaputtbroken - 我是後者的作者。

我想你有興趣作爲測試可以自動化的TDD的具體部分,下面是我如何在自己的項目上做到這一點。您可以在GitHub上找到幾個例子,例如LemonadeRashell,它們都在其各自的testsuite文件夾中找到了測試套件。

我一般根據根據工作流程工作:

  1. 我開始在測試和接口(.mli)文件同時工作,這樣我寫了一個最小的方案,不要只寫測試用例的功能我想要實現,但也有機會嘗試接口,以確保我有一個易於使用的界面。

例如,對於該界面在Rashell_Posix發現find(1)命令我開始寫test cases

open Broken 
open Rashell_Broken 
open Rashell_Posix 
open Lwt.Infix 

let spec base = [ 
    (true, 0o700, [ base; "a"]); 
    (true, 0o750, [ base; "a"; "b"]); 
    (false, 0o600, [ base; "a"; "b"; "x"]); 
    (false, 0o640, [ base; "a"; "y" ]); 
    (true, 0o700, [ base; "c"]); 
    (false, 0o200, [ base; "c"; "z"]); 
] 

let find_fixture = 
    let filename = ref "" in 
    let cwd = Unix.getcwd() in 
    let changeto base = 
    filename := base; 
    Unix.chdir base; 
    Lwt.return base 
    in 
    let populate base = 
    Toolbox.populate (spec base) 
    in 
    make_fixture 
    (fun() -> 
     Lwt_main.run 
     (Rashell_Mktemp.mktemp ~directory:true() 
      >>= changeto 
      >>= populate)) 
    (fun() -> 
     Lwt_main.run 
     (Unix.chdir cwd; 
      rm ~force:true ~recursive:true [ !filename ] 
      |> Lwt_stream.junk_while (fun _ -> true))) 

let assert_find id ?expected_failure ?workdir predicate lst = 
    assert_equal id ?expected_failure 
    ~printer:(fun fft lst -> List.iter (fun x -> Format.fprintf fft " %S" x) lst) 
    (fun() -> Lwt_main.run(
     find predicate [ "." ] 
     |> Lwt_stream.to_list 
     |> Lwt.map (List.filter ((<>) ".")) 
     |> Lwt.map (List.sort Pervasives.compare))) 
    () 
    lst 

specfind_fixture函數用於創建一個文件的層次結構與給定的名稱和權限,以鍛鍊find功能。然後assert_find函數準備一個測試用例進行比較的呼叫的結果,以find(1)與預期結果:

let find_suite = 
    make_suite ~fixture:find_fixture "find" "Test suite for find(1)" 
    |& assert_find "regular" (Has_kind(S_REG)) [ 
     "./a/b/x"; 
     "./a/y"; 
     "./c/z"; 
    ] 
    |& assert_find "directory" (Has_kind(S_DIR)) [ 
     "./a"; 
     "./a/b"; 
     "./c" 
    ] 
    |& assert_find "group_can_read" (Has_at_least_permission(0o040)) [ 
     "./a/b"; 
     "./a/y" 
    ] 
    |& assert_find "exact_permission" (Has_exact_permission(0o640)) [ 
     "./a/y"; 
    ] 

同時我在寫on the interface file

(** The type of file types. *) 
type file_kind = Unix.file_kind = 
    | S_REG 
    | S_DIR 
    | S_CHR 
    | S_BLK 
    | S_LNK 
    | S_FIFO 
    | S_SOCK 

(** File permissions. *) 
type file_perm = Unix.file_perm 

(** File status *) 
type stats = Unix.stats = { 
    st_dev: int; 
    st_ino: int; 
    st_kind: file_kind; 
    st_perm: file_perm; 
    st_nlink: int; 
    st_uid: int; 
    st_gid: int; 
    st_rdev: int; 
    st_size: int; 
    st_atime: float; 
    st_mtime: float; 
    st_ctime: float; 
} 

type predicate = 
    | Prune 
    | Has_kind of file_kind 
    | Has_suffix of string 
    | Is_owned_by_user of int 
    | Is_owned_by_group of int 
    | Is_newer_than of string 
    | Has_exact_permission of int 
    | Has_at_least_permission of int 
    | Name of string (* Globbing pattern on basename *) 
    | And of predicate list 
    | Or of predicate list 
    | Not of predicate 

val find : 
    ?workdir:string -> 
    ?env:string array -> 
    ?follow:bool -> 
    ?depthfirst:bool -> 
    ?onefilesystem:bool -> 
    predicate -> string list -> string Lwt_stream.t 
(** [find predicate pathlst] wrapper of the 
    {{:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html} find(1)} 
    command. *) 
  • 一旦我對測試用例和接口感到滿意,即使沒有實現,我也可以嘗試編譯它們。這可以通過bsdowl只需提供接口文件而不是Makefile中的實現文件來實現。 這裏的編譯可能在我的測試中發現了一些我可以修復的類型錯誤。

  • 當針對接口編寫的測試,我可以實現的功能,從一個託辭功能:

    讓找_ = failwith「Rashell_Posix。發現:未實現」。

  • 有了這個實現我能夠編譯我的圖書館,我的測試套件在這一點上,當然,測試只是失敗

  • 在這一點上,我只需要實現Rashell_Posix.find功能和重複測試,直到他們通過。
  • 這是我做的測試驅動開發OCaml中,當我使用自動測試。有些人看到互動將REPL作爲測試驅動開發的一種形式,這是一項技術我也喜歡使用它,它的設置和使用相當簡單。在Rashell中使用後一種形式的測試驅動開發的唯一設置步驟是編寫一個.ocamlinit文件,以便加載所有需要的庫。這個文件看起來像:

    #use "topfind";; 
    #require "broken";; 
    #require "lemonade";; 
    #require "lwt.unix";; 
    #require "atdgen";; 
    #directory "/Users/michael/Workshop/rashell/src";; 
    #directory "/Users/michael/obj/Workshop/rashell/src";; 
    

    兩個#directory指令對應目錄的源和目標。

    (免責聲明:如果您在歷史上仔細看,你會發現,我花了一些調戲年表,但也有在那裏我繼續正是這樣其他項目 - 我只是記不準哪些)

    +0

    你也可以做'#需要'foo,bar,baz'' –

    +1

    沒錯,但我傾向於把這些東西放在一行上以便有乾淨的補丁。 :) –

    +2

    非常感謝徹底的答案,這正是我一直在尋找的! – insitu