如何在ocaml中进行测试驱动的开发?

时间:2016-04-24 15:42:49

标签: tdd ocaml

我认为一切都在标题中,但我正在寻找:

  • 什么是"标准" Ocaml中的单元测试框架?
  • 如何在构建中集成执行测试?
  • 如何在每次更改文件时自动执行测试?

作为奖励,我会对测试覆盖工具感兴趣...

1 个答案:

答案 0 :(得分:10)

ounit 似乎非常受欢迎,还有其他一些软件包,例如 kaputt {{ 3}} - 我是后者的作者。

我想您对TDD的特定部分感兴趣,其中测试可以自动化,这是我在自己的项目中如何做到这一点。您可以在GitHub上找到一些示例,例如brokenLemonade,它们都在各自的testsuite文件夹中找到了测试套件。

通常我按照相应的工作流程工作:

  1. 我开始同时使用测试和接口(.mli)文件,这样我编写了一个最小程序,不仅为我想要实现的函数编写测试用例,而且还有机会进行实验使用接口确保我有一个易于使用的界面。
  2. 例如,对于Rashell中找到的find(1)命令的界面,我首先写了Rashell_Posix

    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";
        ]
    

    同时我正在写test cases

    (** 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. *)
    
    1. 一旦我对我的测试用例和接口感到满意,即使没有实现,我也可以尝试编译它们。只需在on the interface file中提供接口文件而不是实现文件,bsdowl就可以实现这一点。 这里的编译可能在我的测试中发现了一些我可以解决的类型错误。

    2. 当针对接口编译测试时,我可以实现该函数,从alibi函数开始:

      让我们找_ =    failwith&#34; Rashell_Posix.find:未实现&#34;

    3. 通过这个实现,我能够编译我的库和我的测试套件。当然,在这一点上,测试失败了。

      1. 此时,我只需要实现Rashell_Posix.find函数并迭代测试直到它们通过。
      2. 当我使用自动化测试时,这就是我在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指令对应于源和对象的目录。

        (免责声明:如果你仔细看历史,你会发现我对年代表采取了一些自由,但还有其他项目我会按照这种方式进行 - 我不记得究竟是哪些。)