如何在ocaml中进行test-driven开发?
How to do test-driven development in ocaml?
我想一切都在标题中,但我正在寻找:
- Ocaml 中的 "standard" 单元测试框架是什么?
- 如何在构建中集成执行测试?
- 如何在每次文件更改时自动执行测试?
作为奖励,我会对测试覆盖工具感兴趣...
好像是包ounit enjoys quite a large popularity, there are several other packages like kaputt or broken – 我是后者的作者
我猜您对 TDD 的特定部分感兴趣,其中测试可以自动化,下面是我在自己的项目中的做法。您可以在 GitHub 上找到一些示例,例如 Lemonade or Rashell,它们在各自的 testsuite
文件夹中都有一个测试套件。
通常我按照以下工作流程工作:
- 我开始同时做测试和接口(
.mli
)文件,这样我写了一个最小的程序,不仅为我想实现的功能写了一个测试用例,而且还有机会尝试界面以确保我有一个易于使用的界面。
例如,对于在 Rashell_Posix I started by writing test cases 中找到的 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
spec
和find_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 by just giving an interface file instead of an implementation file in the Makefile 实现。
这里的编译可能在我的测试中发现了一些我可以修复的类型错误。
测试接口编译时,我可以实现函数,从一个alibi函数开始:
让我们找到_ =
失败 "Rashell_Posix.find: Not implemented"
通过这个实现,我能够编译我的库和测试套件。当然,在这一点上,测试只是失败了。
- 那时,我只需要实现
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
指令对应于源和对象的目录。
(免责声明:如果你仔细看历史,你会发现我在时间顺序上做了一些改动,但还有其他项目我也是这样进行的——我只是记不清具体是哪些了。)
我想一切都在标题中,但我正在寻找:
- Ocaml 中的 "standard" 单元测试框架是什么?
- 如何在构建中集成执行测试?
- 如何在每次文件更改时自动执行测试?
作为奖励,我会对测试覆盖工具感兴趣...
好像是包ounit enjoys quite a large popularity, there are several other packages like kaputt or broken – 我是后者的作者
我猜您对 TDD 的特定部分感兴趣,其中测试可以自动化,下面是我在自己的项目中的做法。您可以在 GitHub 上找到一些示例,例如 Lemonade or Rashell,它们在各自的 testsuite
文件夹中都有一个测试套件。
通常我按照以下工作流程工作:
- 我开始同时做测试和接口(
.mli
)文件,这样我写了一个最小的程序,不仅为我想实现的功能写了一个测试用例,而且还有机会尝试界面以确保我有一个易于使用的界面。
例如,对于在 Rashell_Posix I started by writing test cases 中找到的 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
spec
和find_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 by just giving an interface file instead of an implementation file in the Makefile 实现。 这里的编译可能在我的测试中发现了一些我可以修复的类型错误。
测试接口编译时,我可以实现函数,从一个alibi函数开始:
让我们找到_ = 失败 "Rashell_Posix.find: Not implemented"
通过这个实现,我能够编译我的库和测试套件。当然,在这一点上,测试只是失败了。
- 那时,我只需要实现
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
指令对应于源和对象的目录。
(免责声明:如果你仔细看历史,你会发现我在时间顺序上做了一些改动,但还有其他项目我也是这样进行的——我只是记不清具体是哪些了。)