Skip to content

Instantly share code, notes, and snippets.

@holyshared
Last active June 29, 2023 11:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save holyshared/6fbc738b3b125c2ac7cc120247934e38 to your computer and use it in GitHub Desktop.
Save holyshared/6fbc738b3b125c2ac7cc120247934e38 to your computer and use it in GitHub Desktop.
Design-for-testability - OCaml version
open Greeter
module Greeter = Greeter.Make(struct
(* 05:00:00以上 12:00:00未満 *)
let morning = Time_range.create
~stime:(Time.create ~hour:5 ~min:0 ~sec:0)
~etime:(Time.create ~hour:12 ~min:0 ~sec:0)
(* 12:00:00以上 18:00:00未満 *)
let noon = Time_range.create
~stime:(Time.create ~hour:12 ~min:0 ~sec:0)
~etime:(Time.create ~hour:18 ~min:0 ~sec:0)
(* 18:00:00以上 05:00:00未満 *)
let night1 = Time_range.create
~stime:(Time.create ~hour:18 ~min:0 ~sec:0)
~etime:(Time.create ~hour:23 ~min:59 ~sec:60)
let night2 = Time_range.create
~stime:(Time.create ~hour:0 ~min:0 ~sec:0)
~etime:(Time.create ~hour:5 ~min:0 ~sec:0)
let messages = [
(Greeter_message.create ~range:morning ~msg: "おはようございます");
(Greeter_message.create ~range:noon ~msg: "こんにちは");
(Greeter_message.create ~range:night1 ~msg: "こんばんは");
(Greeter_message.create ~range:night2 ~msg: "こんばんは")
]
end)
open Unix
let current_tm () = localtime (time ())
module Time = struct
type t = int * int * int
let create ~hour ~min ~sec =
(hour, min, sec)
let to_int t =
let (hour, min, sec) = t in
(10000 * hour) + (100 * min) + sec
let tm_to_time tm = (tm.tm_hour, tm.tm_min, tm.tm_sec)
module Op = struct
module Gte = struct
let compare t current =
let tm_to_int current = tm_to_time current |> to_int in
tm_to_int current >= to_int t
end
module Lt = struct
let compare t current =
let tm_to_int current = tm_to_time current |> to_int in
tm_to_int current < to_int t
end
end
let gte t current = Op.Gte.compare t current
let lt t current = Op.Lt.compare t current
end
module Time_range = struct
type t = Time.t * Time.t
let create ~stime ~etime =
(stime, etime)
let in_range t current =
let (stime, etime) = t in
if Time.gte stime current then Time.lt etime current
else false
end
module Greeter_message = struct
type t = Time_range.t * string
let create ~range ~msg =
(range, msg)
let matched t current =
let (range, _) = t in
Time_range.in_range range current
let message t =
let (_, msg) = t in
msg
end
module Greeter = struct
module Messages = struct
module type S = sig
val messages: Greeter_message.t list
end
end
module type S = sig
val greet: ?at: Unix.tm -> unit -> string option
end
module Make (S: Messages.S) = struct
let messages = S.messages
let greet ?(at = current_tm ()) () =
let rec find_message at messages =
match messages with
| [] -> None
| hd::tail ->
if Greeter_message.matched hd at then Some (Greeter_message.message hd)
else find_message at tail in
find_message at messages
end
end
module Time: sig
type t
val create: hour:int -> min:int -> sec:int -> t
val gte: t -> Unix.tm -> bool
val lt: t -> Unix.tm -> bool
end
module Time_range: sig
type t
val create: stime: Time.t -> etime: Time.t -> t
val in_range: t -> Unix.tm -> bool
end
module Greeter_message: sig
type t
val create: range: Time_range.t -> msg: string -> t
val matched: t -> Unix.tm -> bool
end
module Greeter: sig
module Messages: sig
module type S = sig
val messages: Greeter_message.t list
end
end
module type S = sig
val greet: ?at: Unix.tm -> unit -> string option
end
module Make (S: Messages.S): S
end
open Unix
open App
let current_tm () = localtime (time ())
let () =
match Greeter.greet () with
| None -> print_endline "nothing"
| Some msg -> print_endline msg
.PHONY: all clean test
all:
ocamlfind ocamlopt -o main -package unix -linkpkg greeter.mli greeter.ml app.ml main.ml
ocamlfind ocamlopt -o test_greeter -package unix -linkpkg greeter.mli greeter.ml app.ml test_greeter.ml
clean:
rm test_greeter main greeter.cm* greeter.o app.cm* app.o test_greeter.cm* test_greeter.o main.cm* main.o
test: all
./test_greeter
open Greeter
let create_tm ~hour ~min ~sec =
let open Unix in
{
tm_sec=sec;
tm_min=min;
tm_hour=hour;
tm_mday=1;
tm_year=2019;
tm_mon=0;
tm_wday=0;
tm_yday=0;
tm_isdst = false
}
module Time_test = struct
let t04_59_59_arg = create_tm ~hour:4 ~min:59 ~sec:59
let t04_59_58_arg = create_tm ~hour:4 ~min:59 ~sec:58
let t05_01_00_arg = create_tm ~hour:5 ~min:1 ~sec:0
let t05_00_01_arg = create_tm ~hour:5 ~min:0 ~sec:1
let t05_00_00_arg = create_tm ~hour:5 ~min:0 ~sec:0
let t05_00_00 = Time.create ~hour:5 ~min:0 ~sec:0
let t04_59_59 = Time.create ~hour:4 ~min:59 ~sec:59
let time_test_1 () = ("05:00:00 <= 04:59:59", (Time.gte t05_00_00 t04_59_59_arg), false)
let time_test_2 () = ("05:00:00 <= 05:01:00", (Time.gte t05_00_00 t05_01_00_arg), true)
let time_test_3 () = ("05:00:00 <= 05:00:01", (Time.gte t05_00_00 t05_00_01_arg), true)
let time_test_4 () = ("05:00:00 <= 05:00:00", (Time.gte t05_00_00 t05_00_00_arg), true)
let time_test_5 () = ("04:59:59 > 04:59:59", (Time.lt t04_59_59 t04_59_59_arg), false)
let time_test_6 () = ("04:59:59 > 04:59:58", (Time.lt t04_59_59 t04_59_58_arg), true)
let time_test_run fn =
let (msg, result, expect) = fn () in
if result = expect then Ok (msg ^ ": PASS")
else Error (msg ^ ": FAIL")
let run () =
List.map time_test_run [time_test_1; time_test_2; time_test_3; time_test_4; time_test_5; time_test_6]
end
module Time_range_test = struct
let t10_00_00_arg = create_tm ~hour:10 ~min:0 ~sec:0
let t10_01_00_arg = create_tm ~hour:10 ~min:1 ~sec:0
let t10_00_11_00 = Time_range.create
~stime:(Time.create ~hour:10 ~min:0 ~sec:0)
~etime:(Time.create ~hour:11 ~min:0 ~sec:0)
let t10_00_10_01 = Time_range.create
~stime:(Time.create ~hour:10 ~min:0 ~sec:0)
~etime:(Time.create ~hour:10 ~min:1 ~sec:0)
let range_test_1 () = ("10:00:00 <= v:10:00:00 < 11:00:00", (Time_range.in_range t10_00_11_00 t10_00_00_arg), true)
let range_test_2 () = ("10:00:00 <= v:10:01:00 < 10:01:00", (Time_range.in_range t10_00_10_01 t10_01_00_arg), false)
let range_test_run fn =
let (msg, result, expect) = fn () in
if result = expect then Ok (msg ^ ": PASS")
else Error (msg ^ ": FAIL")
let run () =
List.map range_test_run [range_test_1; range_test_2]
end
(*
- 朝(05:00:00以上 12:00:00未満)の場合、「おはようございます」と返す
- 昼(12:00:00以上 18:00:00未満)の場合、「こんにちは」と返す
- 夜(18:00:00以上 05:00:00未満)の場合、「こんばんは」と返す
*)
module App_greeter_test = struct
module App_greeter = App.Greeter
let t05_00_00_arg = create_tm ~hour:5 ~min:0 ~sec:0
let t11_59_59_arg = create_tm ~hour:11 ~min:59 ~sec:59
let t12_00_00_arg = create_tm ~hour:12 ~min:0 ~sec:0
let t17_59_59_arg = create_tm ~hour:17 ~min:59 ~sec:59
let t18_00_00_arg = create_tm ~hour:18 ~min:0 ~sec:0
let t04_59_59_arg = create_tm ~hour:4 ~min:59 ~sec:59
let t00_00_00_arg = create_tm ~hour:0 ~min:0 ~sec:0
let greeter_test_1 () = ("05:00:00", (App_greeter.greet ~at:t05_00_00_arg ()), Some "おはようございます")
let greeter_test_2 () = ("11:59:59", (App_greeter.greet ~at:t11_59_59_arg ()), Some "おはようございます")
let greeter_test_3 () = ("12:00:00", (App_greeter.greet ~at:t12_00_00_arg ()), Some "こんにちは")
let greeter_test_4 () = ("17:59:59", (App_greeter.greet ~at:t17_59_59_arg ()), Some "こんにちは")
let greeter_test_5 () = ("18:00:00", (App_greeter.greet ~at:t18_00_00_arg ()), Some "こんばんは")
let greeter_test_6 () = ("04:59:59", (App_greeter.greet ~at:t04_59_59_arg ()), Some "こんばんは")
let greeter_test_7 () = ("00:00:00", (App_greeter.greet ~at:t00_00_00_arg ()), Some "こんばんは")
let fail_message res =
match res with
| None -> "nothing"
| Some msg -> msg
let greeter_test_run fn =
let (msg, result, expect) = fn () in
if result = expect then Ok (msg ^ ": PASS")
else Error (msg ^ ": FAIL " ^ (fail_message result))
let run () =
List.map greeter_test_run [
greeter_test_1;
greeter_test_2;
greeter_test_3;
greeter_test_4;
greeter_test_5;
greeter_test_6;
greeter_test_7
]
end
let print_results results =
let print_result r remain =
let print v = print_endline v in
Result.fold ~ok:print ~error:print r;
remain in
let rec print results =
match results with
| [] -> ()
| hd::tail -> print_result hd tail |> print in
print results
let report_results results =
let rec _is_pass results =
match results with
| [] -> Ok ()
| hd::tail ->
if Result.is_ok hd then _is_pass tail
else Error () in
let done_by ~msg ~code results = begin
print_endline msg;
print_results results;
(ignore (exit code))
end in
let ok () = done_by ~msg: "test pass" ~code:0 results in
let error () = done_by ~msg: "test failed" ~code:1 results in
Result.fold ~ok ~error (_is_pass results)
let () =
List.map (fun fn -> fn ()) [Time_test.run; Time_range_test.run; App_greeter_test.run]
|> List.concat
|> report_results
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment