TLT 0.1.0.0 → 0.1.0.1
raw patch · 3 files changed
+218/−245 lines, 3 filesdep ~base
Dependency ranges changed: base
Files
- README.md +214/−1
- TLT.cabal +4/−4
- src/Test/TLT.hs +0/−240
README.md view
@@ -7,4 +7,217 @@ transformer is part of the stack. Some control of the results display is available. -See the TLT Haddock page for instructions and examples.+See also the TLT Haddock page for additional examples.++# Overview++A TLT test is a command in the `TLT` monad transformer. There is no+separation between the specification and execution of a test; TLT+makes no record of an executable test itself, only of its result. So+in the main instance for testing, the core `IO` monad should be+wrapped in the `TLT` transformer, and in whatever other layers are+also to be tested.++In TLT, all tests are associated with a string which names or+otherwise describes the test. Each test is introduced with one of the+`~:`, `~::`, or `~::-` infix operators.++The simplest tests simply look for a `True` boolean value. These+tests are introduced with `~::` or `~::-`. The difference between the+two is whether the boolean value is the result of a pure `Bool`+expression, or whether it is returned as the result of a computation.+In TLT, we distinguish between the two cases by including a trailing+hyphen `-` to operators on pure expressions, and omitting the hyphen+from operators on monadic arguments. So these two tests will both+pass,++ "2 is 2 as single Bool" ~::- 2 == 2+ "2 is 2 a returned Bool" ~:: return $ 2 == 2++The `~:` operator introduces a more general form of test. The+right-hand side of `~:` should be an `Assertion` formed with one of+TLT's built-in assertion operators, or returned from a package's+custom assertions. `Assertion`s can give more detailed failure+information then simple `Bool`s.++Syntactically, most assertions are infix operators which start with a+`@` character. The value to the left of the operator is the expected+value, and the symbol to the right is (or returns) the value under+test. A hyphen or `P` suffixes assertion operators which operate on+pure values; for operators without the trailing hyphen, the value+under test should is expected to be returned as the result of a+monadic computation (as with `~::` and `~::-`).++TLT provides these assertion operators:++| Operator | Meaning |+| ------------------------------ | ------------------------------------- |+| `expected @== monadic` | The actual result must be equal to the given expected result. |+| `expected @==- expr` | |+| `unexpected @/= monadic` | The actual result must differ from the given unexpected result. |+| `unexpected @/=- expr` | |+| `expected @< monadic` | The actual result must be greater than the given lower bound. |+| `expected @<- expr` | |+| `expected @ monadic` | The actual result must be less than the given upper bound. |+| `expected @>- expr` | |+| `expected @<= monadic` | The actual result must be greater than or equal to the given lower bound. |+| `expected @<=- expr` | |+| `expected @>= monadic` | The actual result must be less than or equal to the given upper bound. |+| `expected @>=- expr` | |+| `empty monadic` | The actual result must be an empty `Traversable` structure. |+| `emptyP expr` | |+| `nonempty monadic` | The actual result must be a nonempty `Traversable` structure. |+| `nonemptyP expr` | |+| `nothing monadic` | The actual result must be `Nothing` (in a `Maybe`-typed value) |+| `nothingP expr` | |+| `assertFailed message` | Trivial assertions, intended for the less interesting branches of conditional and selection expressions. |+| `assertSuccess` | |++Note that although the assertions are in pairs of one for testing a+pure expression value, and one for testing the result returned from a+monadic computation, in all of the builtin binary assertions the+/expected/ value argument is always a pure value, not itself monadic.++The `inGroup` function allows related tests to be reported as a group.+The function takes two arguments, a `String` name for the group, and+the `TLT` computation housing its tests. Groups have impact only in+terms of organizing the output you see in the final report of tests+run.++Finally, it is straightforward to write new `Assertion`s for+project-specific test criteria: they are simply functions returning+monadic values. There are several functions in the final section of+this document which transform pure predicates into `Assertion`s, or+which transform one form of `Assertion` into another.++# Examples++These examples are from the sample executables and test suite of+the `TLT` package.++## A simple example++The tests in this example are vacuous, but they show a simple+setup with both passing and failing tests.++ main :: IO ()+ main = do+ tlt test++ test :: Monad m = TLT m ()+ test = do+ "True passes" ~::- True+ "2 is 3 as single Bool" ~::- 2 == 3+ "2 is 2 as single Bool" ~::- 2 == 2+ inGroup "== assertions" $ do+ inGroup "pure" $ do+ "2 is 3 as pure assertion" ~: 2 @==- 3+ "2 is 2 as pure assertion" ~: 2 @==- 2+ inGroup "monadic" $ do+ "2 is 3 as result" ~: 2 @== return 3+ "2 is 2 as result" ~: 2 @== return 2+ inGroup "/= pure assertions" $ do+ "2 not 3" ~: 2 @/=- 3+ "2 not 2" ~: 2 @/=- 2+ "2 not 3 as result" ~: 2 @/= return 3+ "2 not 2 as result" ~: 2 @/= return 2++Running these tests should give:++ Running tests:+ - 2 is 3 as single Bool: FAIL Expected True but got False+ - == assertions:+ - pure:+ - 2 is 3 as pure assertion: FAIL Expected 2 but got 3+ - monadic:+ - 2 is 3 as result: FAIL Expected 2 but got 3+ - /= pure assertions:+ - 2 not 2: FAIL Expected other than 2 but got 2+ - 2 not 2 as result: FAIL Expected other than 2 but got 2+ Found 5 errors in 11 tests; exiting++Note that only failing tests appear. This can be configured in the+`test` command: add a call at the beginning of `test` to+`reportAllTestResults` to control this behavior:++ test :: Monad m = TLT m ()+ test = do+ reportAllTestResults True+ "True passes" ~::- True+ ...++and the output will be++ Running tests:+ - True passes: Pass+ - 2 is 3 as single Bool: FAIL Expected True but got False+ - 2 is 2 as single Bool: Pass+ - == assertions:+ - pure:+ - 2 is 3 as pure assertion: FAIL Expected 2 but got 3+ - 2 is 2 as pure assertion: Pass+ - monadic:+ - 2 is 3 as result: FAIL Expected 2 but got 3+ - 2 is 2 as result: Pass+ - /= pure assertions:+ - 2 not 3: Pass+ - 2 not 2: FAIL Expected other than 2 but got 2+ - 2 not 3 as result: Pass+ - 2 not 2 as result: FAIL Expected other than 2 but got 2+ Found 5 errors in 11 tests; exiting++## Testing monad transformers++In the previous example `TLT` was the outermost (in fact only)+monad transformer, but it can appear at any level of the test+suite's application stack. Using `TLT` at other than the top+level is easiest when all of the transformers which might wrap it+are declared as instances of `MonadTLT`.++Consider an application which declares two monad transformers+`M1T` and `M2T`. For simplicity here we take them to be just+aliases for `IdentityT`:++ newtype Monad m = M1T m a = M1T { unwrap1 :: IdentityT m a }+ runM1T :: Monad m = M1T m a - m a+ runM1T = runIdentityT . unwrap1++ newtype Monad m = M2T m a = M2T { unwrap2 :: IdentityT m a }+ runM2T :: Monad m = M2T m a - m a+ runM2T = runIdentityT . unwrap2++And we elide the usual details of including each of them in+`Functor`, `Applicative`, `Monad` and `MonadTrans`. We can+declare instances of each in `MonadTLT`,++ instance MonadTLT m n = MonadTLT (M1T m) n where+ liftTLT = lift . liftTLT++and similarly for `M2T`. Note that this declaration does require+`FlexibleInstances` (because `n` does not appear in the instance+type), `MultiParamTypeClasses` (because we must mention both the top+transformer `m` and the monadic type `n` directly wrapped by `TLT`+within `m`), and `UndecidableInstances` (because `n` is not smaller in+the recursive context of `MonadTLT`, which is not actually a problem+because in the definition of `MonadTLT`, `n` is functionally dependent+on `m`, which /is/ smaller in the recursive context) in the module+where the `MonadTLT` instance is declared.++Now it is convenient to test both transformers:++ ttest = do+ runM1T $ inGroup "M1T tests" $ m1tests+ runM2T $ inGroup "M2T tests" $ m2tests++ m1tests = M1T $ do+ "3 is 3 as pure assertion" ~: 3 @==- 3+ "4 is 4 as pure assertion" ~: 4 @==- 4++ m2tests = M2T $ do+ "5 is 5 as pure assertion" ~: 5 @==- 5+ "6 is 6 as pure assertion" ~: 6 @==- 6++It is not necessary, for example, to harvest test declarations+from the executions of the `MnT`s for assembly into an overall+test declaration.+
TLT.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: TLT-version: 0.1.0.0+version: 0.1.0.1 synopsis: Testing in monads and transformers without explicit specs description: A small unit test system oriented with an emphasis on examining intermediate results of computations in monad transformers. The Test.TLT Haddock page is the main piece of documentation; or see also the GitHub repository <https://github.com/jphmrst/TLT/>. category: Test@@ -36,7 +36,7 @@ HUnit >=1.6.2 && <1.7 , STMonadTrans >=0.4.6 && <0.5 , ansi-terminal >=0.11.1 && <0.12- , base (>=4.14.3 && <4.15) || (>=4.15.1 && <4.16) || (>=4.16.0 && <4.17)+ , base (>=4.14.1 && <4.15) || (>=4.15.1 && <4.16) || (>=4.16.0 && <4.17) , either >=5.0.1 && <5.1 , free >=5.1.7 && <5.2 , mtl >=2.2.2 && <2.3@@ -57,7 +57,7 @@ , STMonadTrans >=0.4.6 && <0.5 , TLT , ansi-terminal >=0.11.1 && <0.12- , base (>=4.14.3 && <4.15) || (>=4.15.1 && <4.16) || (>=4.16.0 && <4.17)+ , base (>=4.14.1 && <4.15) || (>=4.15.1 && <4.16) || (>=4.16.0 && <4.17) , either >=5.0.1 && <5.1 , free >=5.1.7 && <5.2 , mtl >=2.2.2 && <2.3@@ -79,7 +79,7 @@ , STMonadTrans >=0.4.6 && <0.5 , TLT , ansi-terminal >=0.11.1 && <0.12- , base (>=4.14.3 && <4.15) || (>=4.15.1 && <4.16) || (>=4.16.0 && <4.17)+ , base (>=4.14.1 && <4.15) || (>=4.15.1 && <4.16) || (>=4.16.0 && <4.17) , either >=5.0.1 && <5.1 , free >=5.1.7 && <5.2 , mtl >=2.2.2 && <2.3
src/Test/TLT.hs view
@@ -27,246 +27,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Test.TLT (- -- * Overview-- -- |A TLT test is a command in the `TLT` monad transformer. There- -- is no separation between the specification and execution of a- -- test; TLT makes no record of an executable test itself, only of- -- its result. So in the main instance for testing, the core `IO`- -- monad should be wrapped in the `TLT` transformer, and in whatever- -- other layers are also to be tested.- --- -- In TLT, all tests are associated with a string which names or- -- otherwise describes the test. Each test is introduced with one- -- of the @~:@, @~::@, or @~::-@ infix operators.- --- -- The simplest tests simply look for a `True` boolean value. These- -- tests are introduced with @~::@ or @~::-@. The difference- -- between the two is whether the boolean value is the result of a- -- pure `Bool` expression, or whether it is returned as the result- -- of a computation. In TLT, we distinguish between the two cases- -- by including a trailing hyphen @-@ to operators on pure- -- expressions, and omitting the hyphen from operators on monadic- -- arguments. So these two tests will both pass,- --- -- > "2 is 2 as single Bool" ~::- 2 == 2- -- > "2 is 2 a returned Bool" ~:: return $ 2 == 2- --- -- The @~:@ operator introduces a more general form of test. The- -- right-hand side of @~:@ should be an `Assertion` formed with one- -- of TLT's built-in assertion operators, or returned from a- -- package's custom assertions. `Assertion`s can give more detailed- -- failure information then simple `Bool`s.- --- -- Syntactically, most assertions are infix operators which start- -- with a @\@@ character. The value to the left of the operator is- -- the expected value, and the symbol to the right is (or returns)- -- the value under test. A hyphen or @P@ suffixes assertion- -- operators which operate on pure values; for operators without the- -- trailing hyphen, the value under test should is expected to be- -- returned as the result of a monadic computation (as with @~::@- -- and @~::-@).- --- -- TLT provides these assertion operators:- --- -- +---------------------------------+---------------------------------------+- -- | Operator | Meaning |- -- +=================================+=======================================+- -- | @/expected/ \@== /monadic/@ | The actual result must be equal |- -- +---------------------------------+ to the given expected result. |- -- | @/expected/ \@==- /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @/unexpected/ \@\/= /monadic/@ | The actual result must differ |- -- +---------------------------------+ from the given unexpected result. |- -- | @/unexpected/ \@\/=- /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @/expected/ \@< /monadic/@ | The actual result must be greater |- -- +---------------------------------+ than the given lower bound. |- -- | @/expected/ \@<- /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @/expected/ \@> /monadic/@ | The actual result must be less |- -- +---------------------------------+ than the given upper bound. |- -- | @/expected/ \@>- /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @/expected/ \@<= /monadic/@ | The actual result must be greater |- -- +---------------------------------+ than or equal to the given lower |- -- | @/expected/ \@<=- /expr/@ | bound. |- -- +---------------------------------+---------------------------------------+- -- | @/expected/ \@>= /monadic/@ | The actual result must be less than |- -- +---------------------------------+ or equal to the given upper bound. |- -- | @/expected/ \@>=- /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @empty /monadic/@ | The actual result must be an empty |- -- +---------------------------------+ `Traversable` structure. |- -- | @emptyP /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @nonempty /monadic/@ | The actual result must be a nonempty |- -- +---------------------------------+ `Traversable` structure. |- -- | @nonemptyP /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @nothing /monadic/@ | The actual result must be `Nothing` |- -- +---------------------------------+ (in a `Maybe`-typed value) |- -- | @nothingP /expr/@ | |- -- +---------------------------------+---------------------------------------+- -- | @assertFailed /message/@ | Trivial assertions, intended for the |- -- +---------------------------------+ less interesting branches of |- -- | @assertSuccess@ | conditional and selection expressions.|- -- +---------------------------------+---------------------------------------+- --- -- Note that although the assertions are in pairs of one for testing- -- a pure expression value, and one for testing the result returned- -- from a monadic computation, in all of the builtin binary- -- assertions the /expected/ value argument is always a pure value,- -- not itself monadic.- --- -- The `inGroup` function allows related tests to be reported as a- -- group. The function takes two arguments, a `String` name for the- -- group, and the `TLT` computation housing its tests. Groups have- -- impact only in terms of organizing the output you see in the- -- final report of tests run.- --- -- Finally, it is straightforward to write new `Assertion`s for- -- project-specific test criteria: they are simply functions- -- returning monadic values. There are several functions in the- -- final section of this document which transform pure predicates- -- into `Assertion`s, or which transform one form of `Assertion`- -- into another.- --- -- The source repository for TLT lives at- -- <https://github.com/jphmrst/tlt>.-- -- * Examples-- -- |These examples are from the sample executables and test suite of- -- the @TLT@ package.-- -- ** A simple example-- -- |The tests in this example are vacuous, but they show a simple- -- setup with both passing and failing tests.- --- -- > main :: IO ()- -- > main = do- -- > tlt test- -- >- -- > test :: Monad m => TLT m ()- -- > test = do- -- > "True passes" ~::- True- -- > "2 is 3 as single Bool" ~::- 2 == 3- -- > "2 is 2 as single Bool" ~::- 2 == 2- -- > inGroup "== assertions" $ do- -- > inGroup "pure" $ do- -- > "2 is 3 as pure assertion" ~: 2 @==- 3- -- > "2 is 2 as pure assertion" ~: 2 @==- 2- -- > inGroup "monadic" $ do- -- > "2 is 3 as result" ~: 2 @== return 3- -- > "2 is 2 as result" ~: 2 @== return 2- -- > inGroup "/= pure assertions" $ do- -- > "2 not 3" ~: 2 @/=- 3- -- > "2 not 2" ~: 2 @/=- 2- -- > "2 not 3 as result" ~: 2 @/= return 3- -- > "2 not 2 as result" ~: 2 @/= return 2- --- -- Running these tests should give:- --- -- > Running tests:- -- > - 2 is 3 as single Bool: FAIL Expected True but got False- -- > - == assertions:- -- > - pure:- -- > - 2 is 3 as pure assertion: FAIL Expected 2 but got 3- -- > - monadic:- -- > - 2 is 3 as result: FAIL Expected 2 but got 3- -- > - /= pure assertions:- -- > - 2 not 2: FAIL Expected other than 2 but got 2- -- > - 2 not 2 as result: FAIL Expected other than 2 but got 2- -- > Found 5 errors in 11 tests; exiting- --- -- Note that only failing tests appear. This can be configured in the- -- @test@ command: add a call at the beginning of @test@ to- -- @reportAllTestResults@ to control this behavior:- --- -- > test :: Monad m => TLT m ()- -- > test = do- -- > reportAllTestResults True- -- > "True passes" ~::- True- -- > ...- --- -- and the output will be- --- -- > Running tests:- -- > - True passes: Pass- -- > - 2 is 3 as single Bool: FAIL Expected True but got False- -- > - 2 is 2 as single Bool: Pass- -- > - == assertions:- -- > - pure:- -- > - 2 is 3 as pure assertion: FAIL Expected 2 but got 3- -- > - 2 is 2 as pure assertion: Pass- -- > - monadic:- -- > - 2 is 3 as result: FAIL Expected 2 but got 3- -- > - 2 is 2 as result: Pass- -- > - /= pure assertions:- -- > - 2 not 3: Pass- -- > - 2 not 2: FAIL Expected other than 2 but got 2- -- > - 2 not 3 as result: Pass- -- > - 2 not 2 as result: FAIL Expected other than 2 but got 2- -- > Found 5 errors in 11 tests; exiting-- -- ** Testing monad transformers-- -- |In the previous example `TLT` was the outermost (in fact only)- -- monad transformer, but it can appear at any level of the test- -- suite's application stack. Using `TLT` at other than the top- -- level is easiest when all of the transformers which might wrap it- -- are declared as instances of `MonadTLT`.- --- -- Consider an application which declares two monad transformers- -- @M1T@ and @M2T@. For simplicity here we take them to be just- -- aliases for `IdentityT`:- --- -- > newtype Monad m => M1T m a = M1T { unwrap1 :: IdentityT m a }- -- > runM1T :: Monad m => M1T m a -> m a- -- > runM1T = runIdentityT . unwrap1- -- >- -- > newtype Monad m => M2T m a = M2T { unwrap2 :: IdentityT m a }- -- > runM2T :: Monad m => M2T m a -> m a- -- > runM2T = runIdentityT . unwrap2- --- -- And we elide the usual details of including each of them in- -- `Functor`, `Applicative`, `Monad` and `MonadTrans`. We can- -- declare instances of each in `MonadTLT`,- --- -- > instance MonadTLT m n => MonadTLT (M1T m) n where- -- > liftTLT = lift . liftTLT- --- -- and similarly for @M2T@. Note that this declaration does require- -- @FlexibleInstances@ (because @n@ does not appear in the instance- -- type), @MultiParamTypeClasses@ (because we must mention both the- -- top transformer @m@ and the monadic type @n@ directly wrapped by- -- `TLT` within @m@), and @UndecidableInstances@ (because @n@ is not- -- smaller in the recursive context of `MonadTLT`, which is not- -- actually a problem because in the definition of `MonadTLT`, @n@- -- is functionally dependent on @m@, which /is/ smaller in the- -- recursive context) in the module where the `MonadTLT` instance is- -- declared.- --- -- Now it is convenient to test both transformers:- --- -- > ttest = do- -- > runM1T $ inGroup "M1T tests" $ m1tests- -- > runM2T $ inGroup "M2T tests" $ m2tests- -- >- -- > m1tests = M1T $ do- -- > "3 is 3 as pure assertion" ~: 3 @==- 3- -- > "4 is 4 as pure assertion" ~: 4 @==- 4- -- >- -- > m2tests = M2T $ do- -- > "5 is 5 as pure assertion" ~: 5 @==- 5- -- > "6 is 6 as pure assertion" ~: 6 @==- 6- --- -- It is not necessary, for example, to harvest test declarations- -- from the executions of the @MnT@s for assembly into an overall- -- test declaration.- -- * The TLT transformer TLT, tlt, MonadTLT, liftTLT, -- ** Session options