2016-04-24 60 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