-- In order to write a test, we have to import the module Test.Prop: import Test.Prop -- We import the System module for performing some I/O tests on operations -- in this module: import System.Environment -------------------------------------------------------------------------- -- Deterministic tests: -- We can write simple equational tests where both sides -- evaluate to a single value: rev_123 = reverse [1,2,3] -=- [3,2,1] not_True = not True -=- False not_False = not False -=- True -- However, we can also use EasyCheck to guess input values to check -- parameterized properties: not_not_is_id b = not (not b) -=- b -- In the former test, EasyCheck makes an exhaustive test by enumerating -- all possible Boolean values. For types, with infinitely many values, -- this is not possible. Anyway, EasyCheck can also enumerate many values, -- e.g., to check the commutativity property of the addition on integers: plusComm :: Int -> Int -> Prop plusComm x y = x + y -=- y + x -- We can even write a polymorphic test: rev_rev_is_id :: (Eq a, Show a) => [a] -> Prop rev_rev_is_id xs = reverse (reverse xs) -=- xs -- A polymorphic test will be automatically transformed into the same -- test specialized to values of type Ordering. -- Nevertheless, we can still define our own specialization: rev_rev_is_id_int :: [Int] -> Prop rev_rev_is_id_int = rev_rev_is_id -- Sometimes it is necessary to add a condition to the generated -- test inputs. This can be done by the operator `==>`: tail_length xs = not (null xs) ==> length (tail xs) -=- length xs - 1 -------------------------------------------------------------------------- -- Of course, in Curry we also have to test Non-deterministic operations -- like `coin`: coin = 0 ? 1 -- We can test whether `coin` evaluates at least to some value: coin_yields_0 = coin ~> 0 coin_yields_1 = coin ~> 1 -- If we want to check for all results of an operation, we can also -- check the set of all results for equality: coin_yields_0_1 = coin <~> (0?1) -- In this way, we can check whether Curry really implements call-time choice: double x = x+x double_coin_yields_0_2 = double coin <~> (0?2) -- Note that the operator `<~>` compares the set of all results of both sides. -- Thus, duplicated elements do not count: coin_plus_coin = coin+coin <~> (0?1?2) -- However, if we are interested in the detailed operational semantics, -- we could also compare the multi-sets of the values with the operator `<~~>`: coin_plus_coin_multi = coin+coin <~~> (0?1?1?2) -- As a more advanced example, we want to test whether the operation -- `last` defined with a functional pattern always yields a single result. -- This can be done by checking whether each call of `last` with -- a non-empty list yields a single result: last :: Data a => [a] -> a last (_ ++ [x]) = x last_has_single_results xs = not (null xs) ==> last xs # 1 -------------------------------------------------------------------------- -- I/O tests: -- We can also check properties of I/O actions. In this case, -- these I/O actions must be deterministic (otherwise, currycheck reports -- failure) and we can specify which value we expect from the I/O action. -- As an example, we check the setting of environment variables. -- For this purpose, we use the following environment variable: evar = "abc123" -- First, we check whether setting this variable works: set_environ = (setEnv evar "SET" >> getEnv evar) `returns` "SET" -- Now we check whether unsetting works: unset_environ = (unsetEnv evar >> getEnv evar) `returns` "" -- We can also compare the results of two actions with `sameReturns`: sameIO = return (6*7) `sameReturns` return 42 --------------------------------------------------------------------------