Skip to content

Commit 93a1356

Browse files
authored
Merge pull request mouredev#2654 from luishendrix92/main
#13 #14 - OCaml
2 parents 5e4c1ad + 51e612e commit 93a1356

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
open OUnit2
2+
3+
(*****************************************************************************)
4+
(* *)
5+
(* Unit Testing *)
6+
(* *)
7+
(* There are 2 main ways of testing in OCaml: using inline tests with pre- *)
8+
(* processors which live in the same file as the code we want to test, *)
9+
(* leveraging the power of testing frameworks such as OUnit, Alcotest, and *)
10+
(* QCheck; combined with a build system like Dune. *)
11+
(* *)
12+
(* For this example, I'm using OUnit2 in the same executable as the code *)
13+
(* file that contains the code I want to test which should normally live *)
14+
(* in a separate file. It is the most endorsed testing framework in OCaml *)
15+
(* and is very similar other familiar frameworks: you can group tests, *)
16+
(* assert different things, describe tests, skip them, mock, and more! *)
17+
(* *)
18+
(*****************************************************************************)
19+
20+
module StringMap = Map.Make (String)
21+
22+
module Testable : sig
23+
(** [Testable] contains code that should be otherwise consumed in a separate
24+
test file and should only expose the functions that the client of the
25+
library we are writing is concerned with. We can achieve this level of
26+
encapsulation through signatures, thus {b sealing} the module. *)
27+
28+
val add : int -> int -> int
29+
(** [add a b] is the sum of integers [a] and [b]. *)
30+
31+
val create_programmer
32+
: string
33+
-> int
34+
-> string
35+
-> string list
36+
-> string StringMap.t
37+
(** [create_programmer name age birth_date programming_languages] creates an
38+
immutable map/dictionary with 4 string keys corresponding to a programmer
39+
with a name, age, birth date and favourite programming languages.
40+
41+
Due to type constraints, all values of the map once created are of type
42+
[string], so the integer and list values need to be converted to their
43+
respective original types [int] and [string list] (comma separated) in
44+
order to use them properly. *)
45+
end = struct
46+
(* Alternative to a [Map], the way we define custom domain types in OCaml
47+
is through [records] but since the extra challenge requires a dictionary,
48+
I'm using a [Map] instead which slightly restricts the type system. *)
49+
50+
let add = ( + )
51+
52+
let create_programmer name age birth_date programming_languages =
53+
StringMap.(
54+
empty
55+
|> add "name" name
56+
|> add "age" (string_of_int age)
57+
|> add "birth_date" birth_date
58+
|> add "programming_languages" (String.concat "," programming_languages))
59+
;;
60+
end
61+
62+
let exercise_tests =
63+
"Exercise #13 Test Suite"
64+
>::: [ ("addition of two positive integers"
65+
>:: fun _ -> assert_equal 15 (Testable.add 5 10))
66+
; ("addition of two negative integers"
67+
>:: fun _ -> assert_equal (-90) (Testable.add (-40) (-50)))
68+
; ("addition of negative and positive integers"
69+
>:: fun _ ->
70+
(* This test fails, but without the [printer] named argument set, it
71+
would print [not equal]. To improve the output, the argument needs
72+
to be set to a function that knows how to transform the type of
73+
the two compared values to [string]; in this case, [string_of_int]
74+
allows the test framework to output the following report:
75+
76+
{[
77+
Error: Exercise #13 Test Suite:2:
78+
addition of negative and positive integers.
79+
80+
<SOME EXTRA INFO I OMITTED>
81+
82+
expected: 1 but got: 0
83+
---------------------------------------------------------------
84+
Ran: 3 tests in: 0.10 seconds.
85+
FAIL: Cases: 3 Tried: 3 Errors: 0 Failures: 1 Skip: 0 Todo: 0.
86+
]}
87+
88+
The real output contains extra information such as the file where
89+
the assertion failure happened, the line and column, along with a
90+
small stack trace of which function call produced the failure. *)
91+
assert_equal 0 (Testable.add 9 (-9));
92+
assert_equal ~printer:string_of_int 1 (Testable.add (-3) 3))
93+
]
94+
;;
95+
96+
let _ = run_test_tt_main exercise_tests
97+
98+
(**************************************************************************)
99+
(* *)
100+
(* Dificultad Extra (Opcional) *)
101+
(* *)
102+
(* Crea un diccionario con las siguientes claves y valores: *)
103+
(* - [name]: Tu nombre *)
104+
(* - [age]: Tu edad *)
105+
(* - [birth_date]: Tu fecha de nacimiento *)
106+
(* - [programming_languages]: Listado de lenguajes de programación *)
107+
(* *)
108+
(* Crea dos test: *)
109+
(* - Un primero que determine que existen todos los campos. *)
110+
(* - Un segundo que determine que los datos introducidos son correctos. *)
111+
(* *)
112+
(**************************************************************************)
113+
114+
module DateValidator = struct
115+
open Core
116+
117+
let years_of_date date_str =
118+
let today = Date.today ~zone:Time_float.Zone.utc in
119+
let date = Core.Date.of_string date_str in
120+
Date.diff today date / 365
121+
;;
122+
123+
let validate_date_str date_str =
124+
let year, month, day =
125+
match String.split ~on:'/' date_str with
126+
| year :: month :: day :: _ ->
127+
int_of_string year, int_of_string month, int_of_string day
128+
| _ -> failwith "Date parsing failed"
129+
in
130+
year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= 31
131+
;;
132+
end
133+
134+
let challenge_tests =
135+
let mock =
136+
Testable.create_programmer
137+
"Luis Lopez"
138+
32
139+
"1992/04/09"
140+
[ "Haskell"
141+
; "Clojure"
142+
; "Typescript"
143+
; "OCaml"
144+
; "Elixir"
145+
; "Java"
146+
; "Python"
147+
]
148+
in
149+
let open Testable in
150+
"Optional Challenge #13 Test Suite"
151+
>::: [ ("a mock programmer map has all fields set"
152+
>:: fun _ ->
153+
assert_bool "[name] field absent" (StringMap.mem "name" mock);
154+
assert_bool "[age] field absent" (StringMap.mem "age" mock);
155+
assert_bool
156+
"[birth_date] field absent"
157+
(StringMap.mem "birth_date" mock);
158+
assert_bool
159+
"[programming_languages] exists"
160+
(StringMap.mem "programming_languages" mock))
161+
; ("a mock programmer map has is valid and has the correct data"
162+
>:: fun _ ->
163+
let name = StringMap.find "name" mock in
164+
let age = int_of_string @@ StringMap.find "age" mock in
165+
let birth_date = StringMap.find "birth_date" mock in
166+
let programming_languages =
167+
StringMap.find "programming_languages" mock
168+
|> String.split_on_char ','
169+
in
170+
assert_bool "[name] field is empty" (name <> "");
171+
assert_equal "Luis Lopez" name;
172+
assert_bool "[age] field is not greater than 0" (age > 0);
173+
assert_equal 32 age;
174+
assert_bool "[birth_date] is empty" (birth_date <> "");
175+
assert_bool
176+
"[birth_date] is invalid"
177+
(DateValidator.validate_date_str birth_date);
178+
assert_bool
179+
"[birth_date] conflicts with [age]"
180+
(DateValidator.years_of_date birth_date = 32);
181+
assert_equal "1992/04/09" birth_date;
182+
assert_equal
183+
programming_languages
184+
[ "Haskell"
185+
; "Clojure"
186+
; "Typescript"
187+
; "OCaml"
188+
; "Elixir"
189+
; "Java"
190+
; "Python"
191+
])
192+
]
193+
;;
194+
195+
let _ = run_test_tt_main challenge_tests
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
open Printf
2+
open Core
3+
4+
(*****************************************************************************)
5+
(* *)
6+
(* Working with Dates and Time *)
7+
(* *)
8+
(* The standard library does not provide a Date or DateTime module but it *)
9+
(* leverages the Unix module to work with dates and timestamps. The [Unix] *)
10+
(* module provides a set of time functions and a specific record type that *)
11+
(* represents wallclock and calendar time (it's called [tm]). *)
12+
(* *)
13+
(* Thankfully, the popular library [Core] offers two different modules: *)
14+
(* 1. [Date]: Type and functions to work with calendar dates. *)
15+
(* 2. [Time_float]: Type and functions to work with raw time. It also has *)
16+
(* a submodule [Zone] needed for when a function requires a timezone *)
17+
(* argument. The most used time zone is [Core.Time_float.Zone.utc]. *)
18+
(* *)
19+
(*****************************************************************************)
20+
21+
module Exercise = struct
22+
let my_birthday =
23+
let date = Date.create_exn ~y:1992 ~m:Month.Apr ~d:9 in
24+
let time = Time_float.Ofday.of_string "10:36:15" in
25+
Time_float.of_date_ofday ~zone:Time_float.Zone.utc date time
26+
;;
27+
28+
let today = Time_float.now ()
29+
30+
let run () =
31+
let living_days =
32+
Time_float.Span.to_day @@ Time_float.abs_diff today my_birthday
33+
in
34+
let living_years = living_days /. 365.0 in
35+
printf "%f years have passed since I was born.\n" living_years
36+
;;
37+
end
38+
39+
(*****************************************************************************)
40+
(* *)
41+
(* Dificultad Extra (Opcional) *)
42+
(* *)
43+
(* Utilizando la fecha de tu cumpleaños, formatéala y muestra su resultado *)
44+
(* de 10 maneras diferentes. Por ejemplo: *)
45+
(* *)
46+
(* - Día, mes y año. *)
47+
(* - Hora, minuto y segundo. *)
48+
(* - Día del año. *)
49+
(* - Día de la semana. *)
50+
(* - Nombre del mes. *)
51+
(* (lo que se te ocurra...) *)
52+
(* *)
53+
(*****************************************************************************)
54+
55+
module Challenge = struct
56+
let pst = Time_float.Zone.of_utc_offset ~hours:(-8)
57+
let my_birthday = Exercise.my_birthday
58+
let bday_as_date = Time_float.to_date ~zone:pst my_birthday
59+
60+
let run () =
61+
print_endline "My birthday formatted in 10 different times:";
62+
printf "%s\n" @@ Time_float.to_string_utc my_birthday;
63+
printf "%s\n" @@ Time_float.to_sec_string_with_zone ~zone:pst my_birthday;
64+
printf "%s\n" @@ Time_float.to_filename_string ~zone:pst my_birthday;
65+
printf "%s\n" @@ Date.to_string_american bday_as_date;
66+
printf "%s\n" @@ Date.to_string_iso8601_basic bday_as_date;
67+
printf "Day %d\n" @@ Date.day bday_as_date;
68+
printf "%s\n" (Date.day_of_week bday_as_date |> Day_of_week.to_string_long);
69+
printf "%s\n" (Date.month bday_as_date |> Month.to_string);
70+
printf
71+
"Unix Timestamp (ms): %d\n"
72+
(Time_float.to_span_since_epoch my_birthday
73+
|> Time_float.Span.to_sec
74+
|> int_of_float);
75+
Time_float.to_string_abs_parts ~zone:pst my_birthday
76+
|> List.iter ~f:print_endline
77+
;;
78+
end
79+
80+
let _ =
81+
Exercise.run ();
82+
Challenge.run ()
83+
;;

0 commit comments

Comments
 (0)