# Lists, and Testing with OUnit In today's lab we'll have some gentle practice using lists. Then we'll learn how to unit test in OCaml using OUnit, a library similar to Java's JUnit. We need lists in order to use OUnit. Finally, we'll have some more advanced practice using lists. ## Practice with lists Becoming fluent with lists is an essential part of learning functional programming. So let's do a few exercises with lists. ##### Exercise: list expressions [✭] * Construct a list that has the integers 1 through 5 in it. Use the square bracket notation for lists. * Construct the same list, but do not use the square bracket notation. Instead use `::` and `[]`. * Construct the same list again. This time, the following expression must appear in your answer: `[2;3;4]`. Use the `@` operator, and do not use `::`. □ ##### Exercise: product [✭✭] Write a function that returns the product of all the elements in a list. The product of all the elements of an empty list is `1`. *Hint: recall the `sum` function we defined in lecture.* Put your code in a file named `rec03.ml`. Use the toplevel to test your code. □ ##### Exercise: concat [✭✭, optional] Write a function that concatenates all the strings in a list. The concatenation of all the strings in an empty list is the empty string `""`. *Hint: this function is really not much different than `sum` or `product`.* Put your code in a file named `rec03.ml`. Use the toplevel to test your code. □ ## Unit testing Using the toplevel to test functions will only work for very small programs. Larger programs need *test suites* that contain many *unit tests* and can be re-run every time we update our code base. A unit test is a test of one small piece of functionality in a program, such as an individual function. We've now learned enough features of OCaml to see how to do unit testing with a library called OUnit. It is a popular testing framework (#1 in downloads on [OCaml Forge][ocamlforge]) similar to JUnit in Java, HUnit in Haskell, etc. The basic workflow for using OUnit is as follows: * Write a function in a file `f.ml`. There could be many other functions in that file too. * Write unit tests for that function in a separate file `f_test.ml`. That exact name, with an underscore and "test" is not actually essential, but is a convention we'll often follow in 3110. * Build and run `f_test.byte` to execute the unit tests. The [OUnit documentation][ounitdoc] is available should you wish to peruse it. [ocamlforge]: https://forge.ocamlcore.org/ [ounitdoc]: http://ounit.forge.ocamlcore.org/api-ounit/index.html The following exercise shows you how to create an OUnit test suite. There are some things in the exercise that might at first seem mysterious; they are discussed below the exercise. ##### Exercise: unit test sum [✭✭] Create a file named `sum.ml`, and put the following code into it: ``` let rec sum = function | [] -> 0 | x::xs -> x + sum xs ``` Now create a second file named `sum_test.ml`, and put this code into it: ``` open OUnit2 open Sum let tests = "test suite for sum" >::: [ "empty" >:: (fun _ -> assert_equal 0 (sum [])); "one" >:: (fun _ -> assert_equal 1 (sum [1])); "onetwo" >:: (fun _ -> assert_equal 3 (sum [1; 2])); ] let _ = run_test_tt_main tests ``` Finally, run these commands: ``` $ ocamlbuild -package oUnit sum_test.byte $ ./sum_test.byte ``` You will get a response something like this: ``` ... Ran: 3 tests in: 0.12 seconds. OK ``` □ Let's study more carefully what we just did in that exercise. In the test file, `open OUnit2` brings into scope the many definitions in OUnit2, which is version 2 of the OUnit framework. And `open Sum` brings into scope the definitions from `sum.ml`. Then we created a list of test cases: ``` [ "empty" >:: (fun _ -> assert_equal 0 (sum [])); "one" >:: (fun _ -> assert_equal 1 (sum [1])); "onetwo" >:: (fun _ -> assert_equal 3 (sum [1; 2])); ] ``` Each line of code is a separate test case. A test case has a string giving it a descriptive name, and a function to run as the test case. In between the name and the function we write `>::`, which is a custom operator defined by the OUnit framework. Let's look at the first function from above: ``` fun _ -> assert_equal 0 (sum []) ``` Every test case function receives as input a parameter that OUnit calls a *test context*. Here (and in many of the test cases we write) we don't actually need to worry about the context, so we use the underscore to indicate that the function ignores its input. The function then calls `assert_equal`, which is a function provided by OUnit that checks to see whether its two arguments are equal. If so the test case succeeds. If not, the test case fails. Then we created a test suite: ``` let tests = "test suite for sum" >::: [ "empty" >:: (fun _ -> assert_equal 0 (sum [])); "one" >:: (fun _ -> assert_equal 1 (sum [1])); "onetwo" >:: (fun _ -> assert_equal 3 (sum [1; 2])); ] ``` The `>:::` operator is another custom OUnit operator. It goes between the name of the test suite and the list of test cases in that suite. Then we ran the test suite: ``` let _ = run_test_tt_main tests ``` The function `run_test_tt_main` is provided by OUnit. It runs a test suite and prints the results of which test cases passed vs. which failed to standard output. The use of underscore here indicates that we don't care what value the function returns; it just gets discarded. Finally, when we compiled the test file, we linked in the OUnit package, which has slightly unusual capitalization: ``` $ ocamlbuild -package oUnit sum_test.byte ``` ##### Exercise: failed test [✭] Modify `sum.ml` to introduce a bug by changing the code in it to the following: ``` let rec sum = function | [] -> 1 (* bug *) | x::xs -> x + sum xs ``` Now rebuild and re-execute `sum_test.byte`. All test cases should now fail. Note how the output tells you the names of the failing cases. □ ##### Exercise: bad add [✭✭] Create a file named `add.ml`, and in it put the following buggy version of an addition function: ``` let add x y = if x mod 7 = 0 then 0 (* bug *) else x+y ``` Now create a file named `add_test.ml`. Create and run an OUnit test suite for `add` in that file. Make sure to write some test cases that will pass (e.g., `add 1 2`) and some test cases that will fail (e.g., `add 7 1`). □ ##### Exercise: product test [✭✭✭] Unit test the function `product` that you wrote in an exercise above. □ ## More list exercises ##### Exercise: patterns [✭✭✭] Using pattern matching, write three functions, one for each of the following properties. Your functions should return `true` if the input list has the property and `false` otherwise. * the list's first element is `"bigred"` * the list has exactly two or four elements; do not use the `length` function * the first two elements of the list are equal □ ##### Exercise: library [✭✭] Consult the [`List` standard library][listdoc] to solve these exercises: * Write a function that takes an `int list` and returns the fifth element of that list, if such an element exists. If the list has fewer than five elements, return `0`. *Hint: `List.length` and `List.nth`.* * Write a function that takes an `int list` and returns the list sorted in descending order. *Hint: `List.sort` with `Pervasives.compare` as its first argument, and `List.rev`.* [listdoc]: http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html □ ##### Exercise: library test [✭✭✭] Write a couple unit tests for each of the functions you wrote in the previous exercise. □ ##### Exercise: library puzzle [✭✭✭] * Write a function that returns the last element of a list. Your function may assume that the list is non-empty. *Hint: Use two library functions, and do not write any pattern matching code of your own.* * Write a function `any_zeroes : int list -> bool` that returns `true` if and only if the input list contains at least one `0`. *Hint: use one library function, and do not write any pattern matching code of your own.* Your solutions will be only one or two lines of code each. Write a couple unit tests for each of the functions. □ ##### Exercise: take drop [✭✭✭] * Write a function `take : int -> 'a list -> 'a list` such that `take n lst` returns the first `n` elements of `lst`. If `lst` has fewer than `n` elements, return all of them. * Write a function `drop : int -> 'a list -> 'a list` such that `drop n lst` returns all but the first `n` elements of `lst`. If `lst` has fewer than `n` elements, return the empty list. Your functions must be tail recursive. Test them on long lists with large values of `n` to see whether they run out of stack space. Here is code to produce a long list: ``` let (--) i j = let rec from i j l = if i>j then l else from i (j-1) (j::l) in from i j [] let longlist = 0 -- 1_000_000 ``` Note that code itself is tail recursive. □