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

ins*_*itu 10 tdd ocaml

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

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

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

Mic*_*ald 10

似乎包ounit享有相当大的人气,还有其他几个包如kaputt破碎 - 我是后者的作者.

我猜你有兴趣作为TDD的特定部分,测试可以自动化,这是我在自己的项目中如何做到这一点.您可以在GitHub上找到一些示例,例如LemonadeRashell,它们都在各自的testsuite文件夹中找到了测试套件.

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

  1. 我开始同时处理测试和接口(.mli)文件,这样我编写一个最小程序,不仅为我想要实现的函数编写测试用例,而且还有机会试验接口以确保我有一个易于使用的界面.

例如,对于Rashell_Posix中find(1)找到的命令的接口,我首先编写测试用例:

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
Run Code Online (Sandbox Code Playgroud)

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";
    ]
Run Code Online (Sandbox Code Playgroud)

同时我在接口文件上写:

(** 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. *)
Run Code Online (Sandbox Code Playgroud)
  1. 一旦我对我的测试用例和接口感到满意,即使没有实现,我也可以尝试编译它们.通过在Makefile中提供接口文件而不是实现文件,可以使用bsdowl.这里编译可能在我的测试中发现了一些我可以解决的类型错误.

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

    让find _ = failwith"Rashell_Posix.find:Not implemented"

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

  1. 那时,我只需要实现该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";;
Run Code Online (Sandbox Code Playgroud)

这两个#directory指令对应于源和对象的目录.

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

  • 非常感谢您的回答,这正是我所寻找的! (2认同)