hspec 1.0.0.1 → 1.1.0
raw patch · 14 files changed
+573/−458 lines, 14 filesdep +hspec-shouldbe
Dependencies added: hspec-shouldbe
Files
- Specs.hs +77/−159
- Test/Hspec.hs +86/−65
- Test/Hspec/Core.hs +54/−82
- Test/Hspec/Formatters.hs +9/−8
- Test/Hspec/Formatters/Internal.hs +7/−7
- Test/Hspec/HUnit.hs +25/−17
- Test/Hspec/Internal.hs +95/−0
- Test/Hspec/Monadic.hs +100/−58
- Test/Hspec/Pending.hs +21/−0
- Test/Hspec/QuickCheck.hs +17/−15
- Test/Hspec/Runner.hs +47/−34
- hspec.cabal +26/−8
- runtests.hs +0/−5
- test/Spec.hs +9/−0
Specs.hs view
@@ -1,109 +1,33 @@-{-# LANGUAGE StandaloneDeriving #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module Specs where+{-# OPTIONS_GHC -fno-warn-deprecations #-}+module Main (main) where -import Test.Hspec-import Test.Hspec.Runner (hHspecWithFormat, toExitCode)-import Test.Hspec.Core (Spec(..), Result(..), quantify, failedCount, evaluateExample)-import Test.Hspec.Formatters-import Test.Hspec.QuickCheck-import Test.Hspec.HUnit ()-import Test.HUnit-import System.IO-import System.IO.Silently-import System.Environment-import System.Exit (exitWith)-import Data.List (isPrefixOf)-import qualified Test.HUnit as HUnit+import Test.Hspec.ShouldBe (Specs, describe, it, hspecX) -deriving instance Show Result+import qualified Test.Hspec as H+import Test.Hspec hiding (Specs, describe, it, hspecX)+import Test.Hspec.Runner (hHspecWithFormat)+import Test.Hspec.Internal (SpecTree(..), Spec(..), Result(..), quantify)+import Test.Hspec.Formatters+import Test.Hspec.QuickCheck+import Test.Hspec.HUnit ()+import Test.HUnit+import System.IO+import System.IO.Silently+import Data.List (isPrefixOf)+import qualified Test.HUnit as HUnit main :: IO ()-main = do- ar <- getArgs- specs' <- specs- ss <- case ar of- ["README"] -> withFile "README" WriteMode (\ h -> hPutStrLn h preamble >> hHspec h specs')- [filename] -> withFile filename WriteMode (\ h -> hHspec h specs')- _ -> hspec specs'- exitWith $ toExitCode (failedCount ss == 0)--preamble :: String-preamble = unlines [- "hspec aims to be a simple, extendable, and useful tool for Behavior Driven Development in Haskell.", "",- "",- "Step 1, write descriptions and examples of your desired behavior",- "> module Myabs where",- ">",- "> import Test.Hspec",- ">",- "> specs :: Specs",- "> specs = describe \"myabs\" [",- "> it \"returns the original number when given a positive input\"",- "> (myabs 1 == 1),",- "> ",- "> it \"returns a positive number when given a negative input\"",- "> (myabs (-1) == 1),",- "> ",- "> it \"returns zero when given zero\"",- "> (myabs 0 == 0)",- "> ]",- "",- "Step 2, write whatever you are describing",- "> myabs n = undefined",- "",- "Step 3, watch your examples fail with red text by running from the .hs file itself",- "> main = hspec specs",- "",- "myabs",- " - returns the original number when given a positive input FAILED [1]",- " - returns a positive number when given a negative input FAILED [2]",- " - returns zero when given zero FAILED [3]",- "",- "1) myabs returns the original number when given a positive input FAILED",- "Prelude.undefined",- "",- "2) myabs returns a positive number when given a negative input FAILED",- "Prelude.undefined",- "",- "3) myabs returns zero when given zero FAILED",- "Prelude.undefined",- "",- "Finished in 0.0002 seconds",- "",- "3 examples, 3 failures",- "",- "",- "Specs can also be run from the command line using the hspec program",- " $ hspec myabs.hs",- "",- "Step 4, implement your desired behavior",- "> myabs n = if n < 0 then negate n else n",- "",- "Step 5, watch your examples succeed with green text when rerun",- "myabs",- " - returns the original number when given a positive input",- " - returns a positive number when given a negative input",- " - returns zero when given zero",- "",- "Finished in 0.0000 seconds",- "",- "3 examples, 0 failures",- "",- "",- "",- "",- "Here's the report of hspec's behavior:" ]+main = specs >>= hspecX specs :: IO Specs specs = do- let testSpecs = [describe "Example" [- it "success" (Success),- it "fail 1" (Fail "fail message"),- it "pending" (Pending "pending message"),- it "fail 2" (HUnit.assertEqual "assertEqual test" 1 (2::Int)),- it "exceptions" (undefined :: Bool),- it "quickcheck" (property $ \ i -> i == (i+1::Integer))]+ let testSpecs = [H.describe "Example" [+ H.it "success" (Success),+ H.it "fail 1" (Fail "fail message"),+ H.it "pending" (pending "pending message"),+ H.it "fail 2" (HUnit.assertEqual "assertEqual test" 1 (2::Int)),+ H.it "exceptions" (undefined :: Bool),+ H.it "quickcheck" (property $ \ i -> i == (i+1::Integer))] ] (reportContents, exampleSpecs) <- capture $ hHspecWithFormat specdoc False stdout testSpecs@@ -113,128 +37,122 @@ let report = lines reportContents - return [- describe "the \"describe\" function" [+ return $ do+ describe "the \"describe\" function" $ do it "takes a description of what the behavior is for" $ case exampleSpecs of [SpecGroup "Example" _] -> True _ -> False- ,+ it "groups behaviors for what's being described" $ case exampleSpecs of [SpecGroup _ xs] -> length xs == 6 _ -> False- ,- describe "a nested description" [++ describe "a nested description" $ do it "has it's own specs" (True)- ]- ],- describe "the \"it\" function" [++ describe "the \"it\" function" $ do it "takes a description of a desired behavior" $- case it "whatever" Success of+ case unSpec $ H.it "whatever" Success of SpecExample requirement _ -> requirement == "whatever" _ -> False- ,+ it "takes an example of that behavior" $ do- case it "whatever" Success of+ case unSpec $ H.it "whatever" Success of SpecExample _ example -> do- r <- evaluateExample example+ r <- example r @?= Success SpecGroup _ _ -> assertFailure "unexpected SpecGroup"- ,+ it "can use a Bool, HUnit Test, QuickCheck property, or \"pending\" as an example"- (True),+ (True) it "will treat exceptions as failures" (any (==" - exceptions FAILED [3]") report)- ],- describe "the \"hspec\" function" [++ describe "the \"hspec\" function" $ do it "displays a header for each thing being described"- (any (=="Example") report),+ (any (=="Example") report) it "displays one row for each behavior"- (HUnit.assertEqual "" 29 (length report)),+ (HUnit.assertEqual "" 29 (length report)) it "displays a row for each successfull, failed, or pending example"- (any (==" - success") report && any (==" - fail 1 FAILED [1]") report),+ (any (==" - success") report && any (==" - fail 1 FAILED [1]") report) it "displays a detailed list of failed examples"- (any (=="1) Example fail 1 FAILED") report),+ (any (=="1) Example fail 1 FAILED") report) it "displays a '#' with an additional message for pending examples"- (any (==" # pending message") report ),+ (any (==" # PENDING: pending message") report ) it "summarizes the time it takes to finish"- (any ("Finished in " `isPrefixOf`) report),+ (any ("Finished in " `isPrefixOf`) report) it "summarizes the number of examples and failures"- (any (=="6 examples, 4 failures") report),+ (any (=="6 examples, 4 failures") report) it "outputs failed examples in red, pending in yellow, and successful in green" (True)- ],- describe "Bool as an example" [++ describe "Bool as an example" $ do it "is just an expression that evaluates to a Bool" (True)- ],- describe "HUnit TestCase as an example" [++ describe "HUnit TestCase as an example" $ do it "is specified with the HUnit \"TestCase\" data constructor"- (HUnit.TestCase $ HUnit.assertBool "example" True),+ (HUnit.TestCase $ HUnit.assertBool "example" True) it "is the assumed example for IO() actions"- (HUnit.assertBool "example" True),+ (HUnit.assertBool "example" True) - it "will show the failed assertion text if available (e.g. assertBool)"- (HUnit.TestCase $ do- (innerReport, _) <- capture $ hspec $ [describe "" [ it "" (HUnit.assertBool "trivial" False)]]- HUnit.assertBool "should find assertion text" $ any (=="trivial") (lines innerReport)),+ it "will show the failed assertion text if available (e.g. assertBool)" $ do+ (innerReport, _) <- capture $ hspec $ [H.describe "" [ H.it "" (HUnit.assertBool "trivial" False)]]+ HUnit.assertBool "should find assertion text" $ any (=="trivial") (lines innerReport) - it "will show the failed assertion expected and actual values if available (e.g. assertEqual)"- (HUnit.TestCase $ do- (innerReportContents, _) <- capture $ hspec $ [describe "" [ it "" (HUnit.assertEqual "trivial" (1::Int) 2)]]- let innerReport = lines innerReportContents- HUnit.assertBool "should find assertion text" $ any (=="trivial") innerReport- HUnit.assertBool "should find 'expected: 1'" $ any (=="expected: 1") innerReport- HUnit.assertBool "should find ' but got: 2'" $ any (==" but got: 2") innerReport)- ],- describe "QuickCheck property as an example" [+ it "will show the failed assertion expected and actual values if available (e.g. assertEqual)" $ do+ (innerReportContents, _) <- capture $ hspec $ [H.describe "" [ H.it "" (HUnit.assertEqual "trivial" (1::Int) 2)]]+ let innerReport = lines innerReportContents+ HUnit.assertBool "should find assertion text" $ any (=="trivial") innerReport+ HUnit.assertBool "should find 'expected: 1'" $ any (=="expected: 1") innerReport+ HUnit.assertBool "should find ' but got: 2'" $ any (==" but got: 2") innerReport++ describe "QuickCheck property as an example" $ do it "is specified with the \"property\" function"- (property $ \ b -> b || True),+ (property $ \ b -> b || True) it "will show what falsified it" (any (=="0") report)- ],- describe "pending as an example" [- it "is specified with the \"pending\" function and an explanation"- (pending "message" == Pending "message"), - it "accepts a message to display in the report"- (any (== " # pending message") report)- ],- describe "the \"hHspecWithFormat\" function" [+ describe "pending as an example" $ do+ it "is specified with the \"pending\" function and an explanation" True++ it "accepts a message to display in the report" True++ describe "the \"hHspecWithFormat\" function" $ do it "can use the \"silent\" formatter to show no output"- (null silentReportContents),+ (null silentReportContents) it "can use the \"progress\" formatter to show '..F...FF.F' style output"- (HUnit.assertEqual "" ".F.FFF" (head $ lines progressReportContents)),+ (HUnit.assertEqual "" ".F.FFF" (head $ lines progressReportContents)) it "can use the \"specdoc\" formatter to show all examples (default)"- (HUnit.assertEqual "" "Example" (lines reportContents !! 1)),+ (HUnit.assertEqual "" "Example" (lines reportContents !! 1)) it "can use the \"failed_examples\" formatter to show only failed examples" (HUnit.assertEqual "" "1) Example fail 1 FAILED" (lines failed_examplesReportContents !! 1))- ],- describe "quantify (an internal function)" [++ describe "quantify (an internal function)" $ do it "returns an amount and a word given an amount and word"- (quantify (1::Int) "thing" == "1 thing"),+ (quantify (1::Int) "thing" == "1 thing") it "returns a singular word given the number 1"- (quantify (1::Int) "thing" == "1 thing"),+ (quantify (1::Int) "thing" == "1 thing") it "returns a plural word given a number greater than 1"- (quantify (2::Int) "thing" == "2 things"),+ (quantify (2::Int) "thing" == "2 things") it "returns a plural word given the number 0" (quantify (0::Int) "thing" == "0 things")- ]]
Test/Hspec.hs view
@@ -1,28 +1,68 @@---- | Hspec is a Behaviour-Driven Development tool for Haskell programmers. BDD is an approach--- to software development that combines Test-Driven Development, Domain Driven Design, and--- Acceptance Test-Driven Planning. Hspec helps you do the TDD part of that equation, focusing--- on the documentation and design aspects of TDD.+-- |+-- Hspec is a Behaviour-Driven Development tool for Haskell programmers. BDD is+-- an approach to software development that combines Test-Driven Development,+-- Domain Driven Design, and Acceptance Test-Driven Planning. Hspec helps you+-- do the TDD part of that equation, focusing on the documentation and design+-- aspects of TDD. ----- Hspec (and the preceding intro) are based on the Ruby library RSpec. Much of what applies to--- RSpec also applies to Hspec. Hspec ties together /descriptions/ of behavior and /examples/ of--- that behavior. The examples can also be run as tests and the output summarises what needs to--- be implemented.+-- Hspec (and the preceding intro) are based on the Ruby library RSpec. Much of+-- what applies to RSpec also applies to Hspec. Hspec ties together+-- /descriptions/ of behavior and /examples/ of that behavior. The examples can+-- also be run as tests and the output summarises what needs to be implemented. ----- The three functions you'll use the most are 'hspec', 'describe', and 'it'. Here is an--- example of functions that format and unformat phone numbers and the specs for them.+-- NOTE: There is a monadic and a non-monadic API. This is the documentation+-- for the non-monadic API. The monadic API is more stable, so you may prefer+-- it over this one. For documentation on the monadic API look at+-- "Test.Hspec.Monadic".++module Test.Hspec (++-- * Introduction+-- $intro++-- * Types+ Spec+, Specs+, Example+, Pending++-- * Defining a spec+, describe+, it+, pending++-- * Running a spec+, hspec+, hspecB+, hspecX+, hHspec++-- * Deprecated functions+, descriptions+) where++import Test.Hspec.Core+import Test.Hspec.Runner+import Test.Hspec.Pending++-- $intro --+-- The three functions you'll use the most are 'hspecX', 'describe', and 'it'.+-- Here is an example of functions that format and unformat phone numbers and+-- the specs for them.+-- -- > import Test.Hspec -- > import Test.Hspec.QuickCheck--- > import Test.Hspec.HUnit+-- > import Test.Hspec.HUnit () -- > import Test.QuickCheck -- > import Test.HUnit -- >--- > main = hspec mySpecs+-- > main = hspecX mySpecs ----- Since the specs are often used to tell you what to implement, it's best to start with--- undefined functions. Once we have some specs, then you can implement each behavior--- one at a time, ensuring that each behavior is met and there is no undocumented behavior.+-- Since the specs are often used to tell you what to implement, it's best to+-- start with undefined functions. Once we have some specs, then you can+-- implement each behavior one at a time, ensuring that each behavior is met+-- and there is no undocumented behavior. -- -- > unformatPhoneNumber :: String -> String -- > unformatPhoneNumber number = undefined@@ -30,69 +70,50 @@ -- > formatPhoneNumber :: String -> String -- > formatPhoneNumber number = undefined ----- The 'describe' function takes a list of behaviors and examples bound together with the 'it' function+-- The 'describe' function takes a list of behaviors and examples bound+-- together with the 'it' function -- -- > mySpecs = [describe "unformatPhoneNumber" [ -- -- A boolean expression can act as a behavior's example. ----- > it "removes dashes, spaces, and parenthesies"--- > (unformatPhoneNumber "(555) 555-1234" == "5555551234"),+-- > it "removes dashes, spaces, and parenthesies" $+-- > unformatPhoneNumber "(555) 555-1234" == "5555551234"+-- > , ----- The 'pending' function marks a behavior as pending an example. The example doesn't count as failing.+-- The 'pending' function marks a behavior as pending an example. The example+-- doesn't count as failing. ----- > it "handles non-US phone numbers"--- > (pending "need to look up how other cultures format phone numbers"),+-- > it "handles non-US phone numbers" $+-- > pending "need to look up how other cultures format phone numbers"+-- > , ----- An HUnit 'Test' can act as a behavior's example. (must import @Test.Hspec.HUnit@)+-- An HUnit 'Test.HUnit.Test' can act as a behavior's example. (must import+-- "Test.Hspec.HUnit") ----- > it "removes the \"ext\" prefix of the extension"--- > (TestCase $ let expected = "5555551234135"--- > actual = unformatPhoneNumber "(555) 555-1234 ext 135"--- > in assertEqual "remove extension" expected actual),+-- > it "removes the \"ext\" prefix of the extension" $ TestCase $ do+-- > let expected = "5555551234135"+-- > actual = unformatPhoneNumber "(555) 555-1234 ext 135"+-- > expected @?= actual+-- > , ----- An @IO()@ action is treated like an HUnit 'TestCase'. (must import @Test.Hspec.HUnit@)+-- An @IO()@ action is treated like an HUnit 'TestCase'. (must import+-- "Test.Hspec.HUnit") ----- > it "converts letters to numbers"--- > (do--- > let expected = "6862377"--- > let actual = unformatPhoneNumber "NUMBERS"--- > assertEqual "letters to numbers" expected actual),+-- > it "converts letters to numbers" $ do+-- > let expected = "6862377"+-- > actual = unformatPhoneNumber "NUMBERS"+-- > actual @?= expected+-- > , ----- The 'property' function allows a QuickCheck property to act as an example. (must import @Test.Hspec.QuickCheck@)+-- The 'property' function allows a QuickCheck property to act as an example.+-- (must import "Test.Hspec.QuickCheck") ----- > it "can add and remove formatting without changing the number"--- > (property $ forAll phoneNumber $--- > \ n -> unformatPhoneNumber (formatPhoneNumber n) == n)+-- > it "can add and remove formatting without changing the number" $ property $+-- > forAll phoneNumber $ \n -> unformatPhoneNumber (formatPhoneNumber n) == n -- > ]] -- > -- > phoneNumber :: Gen String -- > phoneNumber = do--- > nums <- elements [7,10,11,12,13,14,15]--- > vectorOf nums (elements "0123456789")----module Test.Hspec (-- -- * Types- Spec- , Result- , Specs-- -- * Defining a spec- , describe- , it- , pending-- -- * Running a spec- , hspec- , hspecB- , hspecX- , hHspec-- -- * Deprecated functions- , descriptions-) where--import Test.Hspec.Core-import Test.Hspec.Runner-+-- > n <- elements [7,10,11,12,13,14,15]+-- > vectorOf n (elements "0123456789")
Test/Hspec/Core.hs view
@@ -1,106 +1,78 @@-{-# OPTIONS -XFlexibleInstances -XExistentialQuantification #-}---- | This module contains the core types, constructors, classes,--- instances, and utility functions common to hspec.----module Test.Hspec.Core where+{-# OPTIONS_GHC -fno-warn-deprecations #-}+-- |+-- This module contains the core types, constructors, classes, instances, and+-- utility functions common to hspec.+module Test.Hspec.Core (+ SpecTree (..)+, Example (..)+, Result (..)+, descriptions -import System.IO.Silently-import Control.Exception+, describe+, it+, Spec+, Specs+, UnevaluatedSpec+, EvaluatedSpec --- | The result of running an example.-data Result = Success | Pending String | Fail String- deriving Eq+, quantify +-- * Deprecated types and functions+-- | The following types and functions are deprecated and will be removed with+-- the next release.+--+-- If you still need any of those, please open an issue and describe your use+-- case: <https://github.com/hspec/hspec/issues>+, AnyExample+, safeEvaluateExample -type UnevaluatedSpec = Spec AnyExample-type EvaluatedSpec = Spec Result+, success+, failure+, isFailure+, failedCount+) where -data Spec a = SpecGroup String [Spec a]- | SpecExample String a+import Test.Hspec.Internal hiding (safeEvaluateExample)+import qualified Test.Hspec.Internal as Internal -describe :: String -> [Spec a] -> Spec a-describe = SpecGroup+{-# DEPRECATED UnevaluatedSpec "use Spec instead" #-}+type UnevaluatedSpec = Spec -- | DEPRECATED: This is no longer needed (it's just an alias for `id` now).-descriptions :: [Spec a] -> [Spec a]+descriptions :: Specs -> Specs descriptions = id {-# DEPRECATED descriptions "this is no longer needed, and will be removed in a future release" #-} -safeEvaluateExample :: AnyExample -> IO Result-safeEvaluateExample example' = do- evaluateExample example' `catches` [- -- Re-throw AsyncException, otherwise execution will not terminate on- -- SIGINT (ctrl-c). All AsyncExceptions are re-thrown (not just- -- UserInterrupt) because all of them indicate severe conditions and- -- should not occur during normal test runs.- Handler (\e -> throw (e :: AsyncException)),-- Handler (\e -> return $ Fail (show (e :: SomeException)))- ]----- | Create a set of specifications for a specific type being described.--- Once you know what you want specs for, use this.------ > describe "abs" [--- > it "returns a positive number given a negative number"--- > (abs (-1) == 1)--- > ]----it :: Example a => String -> a -> UnevaluatedSpec-it requirement' example' = SpecExample requirement' (AnyExample example')--class Example a where- evaluateExample :: a -> IO Result--instance Example Bool where- evaluateExample bool = evaluateExample $ if bool then Success else Fail ""--instance Example Result where- evaluateExample result' = silence $ result' `seq` return result'---- | An existentially quantified @Example@. This way they can be mixed within the same set of Specs-data AnyExample = forall a. Example a => AnyExample a--instance Example AnyExample where- evaluateExample (AnyExample a) = evaluateExample a------ | Declare an example as not successful or failing but pending some other work.--- If you want to report on a behavior but don't have an example yet, use this.------ > describe "fancyFormatter" [--- > it "can format text in a way that everyone likes"--- > (pending "waiting for clarification from the designers")--- > ]----pending :: String -- ^ An explanation for why this behavior is pending.- -> Result-pending = Pending+type Specs = [Spec] +type EvaluatedSpec = SpecTree Result -failedCount :: [EvaluatedSpec] -> Int-failedCount = sum . map count- where- count (SpecGroup _ xs) = sum (map count xs)- count (SpecExample _ x) = if isFailure x then 1 else 0+{-# DEPRECATED success "This will be removed with the next major release. If you still need this, raise your voice!" #-}+success :: [EvaluatedSpec] -> Bool+success = not . failure +{-# DEPRECATED failure "This will be removed with the next major release. If you still need this, raise your voice!" #-} failure :: [EvaluatedSpec] -> Bool failure = any p where p (SpecGroup _ xs) = any p xs p (SpecExample _ x) = isFailure x -success :: [EvaluatedSpec] -> Bool-success = not . failure-+{-# DEPRECATED isFailure "This will be removed with the next major release. If you still need this, raise your voice!" #-} isFailure :: Result -> Bool isFailure (Fail _) = True isFailure _ = False --- | Create a more readable display of a quantity of something.-quantify :: (Show a, Num a, Eq a) => a -> String -> String-quantify 1 s = "1 " ++ s-quantify n s = show n ++ " " ++ s ++ "s"+{-# DEPRECATED failedCount "This will be removed with the next major release. If you still need this, raise your voice!" #-}+failedCount :: [EvaluatedSpec] -> Int+failedCount = sum . map count+ where+ count (SpecGroup _ xs) = sum (map count xs)+ count (SpecExample _ x) = if isFailure x then 1 else 0++{-# DEPRECATED AnyExample "This will be removed with the next major release. If you still need this, raise your voice!" #-}+type AnyExample = IO Result++{-# DEPRECATED safeEvaluateExample "This will be removed with the next major release. If you still need this, raise your voice!" #-}+safeEvaluateExample :: AnyExample -> IO Result+safeEvaluateExample = Internal.safeEvaluateExample
Test/Hspec/Formatters.hs view
@@ -38,10 +38,11 @@ , withFailColor ) where -import Test.Hspec.Core (quantify)-import Data.List (intersperse)-import Text.Printf-import Control.Monad (when)+import Data.Maybe+import Test.Hspec.Core (quantify)+import Data.List (intersperse)+import Text.Printf+import Control.Monad (unless) -- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make -- sure, that we only use the public API to implement formatters.@@ -92,11 +93,11 @@ writeLine $ indentationForExample nesting ++ " - " ++ requirement , exampleFailed = \nesting requirement _ -> withFailColor $ do- failed <- getFailCount- writeLine $ indentationForExample nesting ++ " - " ++ requirement ++ " FAILED [" ++ show failed ++ "]"+ n <- getFailCount+ writeLine $ indentationForExample nesting ++ " - " ++ requirement ++ " FAILED [" ++ show n ++ "]" , examplePending = \nesting requirement reason -> withPendingColor $ do- writeLine $ indentationForExample nesting ++ " - " ++ requirement ++ "\n # " ++ reason+ writeLine $ indentationForExample nesting ++ " - " ++ requirement ++ "\n # PENDING: " ++ fromMaybe "No reason given" reason , failedFormatter = defaultFailedFormatter @@ -129,7 +130,7 @@ defaultFailedFormatter = withFailColor $ do failures <- getFailMessages mapM_ writeLine ("" : intersperse "" failures)- when (not $ null failures) (writeLine "")+ unless (null failures) (writeLine "") defaultFooter :: FormatM () defaultFooter = do
Test/Hspec/Formatters/Internal.hs view
@@ -30,11 +30,11 @@ ) where import qualified System.IO as IO-import System.IO (Handle)-import Control.Monad (when)-import Control.Exception (bracket_)-import System.Console.ANSI-import Control.Monad.Trans.State hiding (gets, modify)+import System.IO (Handle)+import Control.Monad (when)+import Control.Exception (bracket_)+import System.Console.ANSI+import Control.Monad.Trans.State hiding (gets, modify) import qualified Control.Monad.Trans.State as State import qualified Control.Monad.IO.Class as IOClass import qualified System.CPUTime as CPUTime@@ -126,7 +126,7 @@ -- | evaluated after each failed example , exampleFailed :: Int -> String -> String -> FormatM () -- | evaluated after each pending example-, examplePending :: Int -> String -> String -> FormatM ()+, examplePending :: Int -> String -> Maybe String -> FormatM () -- | evaluated after a test run , failedFormatter :: FormatM () -- | evaluated after `failuresFormatter`@@ -182,7 +182,7 @@ getCPUTime = do t1 <- liftIO CPUTime.getCPUTime t0 <- gets cpuStartTime- return ((fromIntegral $ t1 - t0) / (10.0^(12::Integer)))+ return (fromIntegral (t1 - t0) / (10.0^(12::Integer))) -- | Get the passed real time since the test run has been started. getRealTime :: FormatM Double
Test/Hspec/HUnit.hs view
@@ -1,29 +1,37 @@-{-# OPTIONS -XFlexibleInstances -fno-warn-orphans #-}+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}+{-# OPTIONS -fno-warn-orphans #-} --- | Importing this module allows you to use an @HUnit@ test case as an example--- for a behavior. You can use an explicit @TestCase@ data constructor or--- use an @IO()@ action. For an @IO()@ action, any exception means the example--- failed; otherwise, it's successfull. Any output from the example to stdout is--- ignored. If you need to write out for debugging, you can write to stderr or--- a file handle.+-- |+-- Importing this module allows you to use an @HUnit@ `HU.Test` as an example+-- for a behavior. You can use an explicit `HU.TestCase` data constructor or+-- use an `HU.Assertion`. For an @Assertion@, any exception means the example+-- failed; otherwise, it's successfull. ----- > describe "cutTheDeck" [--- > it "puts the first half of a list after the last half"--- > (TestCase $ assertEqual "cut the deck" [3,4,1,2] (cutTheDeck [1,2,3,4])),+-- NOTE: Any output from the example to @stdout@ is ignored. If you need to+-- write out for debugging, you can write to @stderr@ or a file handle.+--+-- > import Test.Hspec.Monadic+-- > import Test.Hspec.HUnit ()+-- > import Test.HUnit -- >--- > it "restores an even sized list when cut twice"--- > (assertEqual "cut the deck twice" [3,4,1,2] (cutTheDeck (cutTheDeck [1,2,3,4]))),--- > ]+-- > main :: IO ()+-- > main = hspecX $ do+-- > describe "reverse" $ do+-- > it "reverses a list" $ do+-- > reverse [1, 2, 3] @?= [3, 2, 1] -- >+-- > it "gives the original list, if applied twice" $ TestCase $+-- > (reverse . reverse) [1, 2, 3] @?= [1, 2, 3]+-- module Test.Hspec.HUnit ( ) where -import System.IO.Silently-import Test.Hspec.Core+import System.IO.Silently+import Test.Hspec.Core import qualified Test.HUnit as HU-import Data.List (intersperse)+import Data.List (intersperse) -instance Example (IO ()) where+instance Example HU.Assertion where evaluateExample io = evaluateExample (HU.TestCase io) instance Example HU.Test where
+ Test/Hspec/Internal.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE FlexibleInstances #-}+module Test.Hspec.Internal (+ SpecTree (..)+, Spec (..)+, Example (..)+, safeEvaluateExample+, Result (..)++, describe+, it++, quantify+)+where++import Control.Exception+import qualified Test.Hspec.Pending as Pending++-- | The result of running an example.+data Result = Success | Pending (Maybe String) | Fail String+ deriving (Eq, Show)++newtype Spec = Spec {unSpec :: SpecTree (IO Result)}++-- IMPORTANT: Remove this warnings and remove -fno-warn-deprecations from the+-- following modules/files when making SpecGroup abstract.+--+-- * Spec.hs+-- * Test.Hspec.Runner+-- * Test.Hspec.Core+--+{-# WARNING SpecExample "This will be removed from the public interface with the next major release. If you need this, raise your voice!" #-}+{-# WARNING SpecGroup "This will be removed from the public interface with the next major release. If you need this, raise your voice!" #-}++-- | Internal representation of a spec.+--+-- This will be made abstract with the next release. If you still need access+-- to any constructors, open an issue and describe your use case:+-- <https://github.com/hspec/hspec/issues>+data SpecTree a = SpecGroup String [SpecTree a]+ | SpecExample String a++describe :: String -> [Spec] -> Spec+describe str specs = Spec . SpecGroup str $ map unSpec specs++safeEvaluateExample :: IO Result -> IO Result+safeEvaluateExample action = do+ action `catches` [+ -- Re-throw AsyncException, otherwise execution will not terminate on+ -- SIGINT (ctrl-c). All AsyncExceptions are re-thrown (not just+ -- UserInterrupt) because all of them indicate severe conditions and+ -- should not occur during normal test runs.+ Handler (\e -> throw (e :: AsyncException)),++ Handler (\e -> return $ Fail (show (e :: SomeException)))+ ]+++-- | Create a set of specifications for a specific type being described.+-- Once you know what you want specs for, use this.+--+-- > describe "abs" [+-- > it "returns a positive number given a negative number"+-- > (abs (-1) == 1)+-- > ]+--+it :: Example a => String -> a -> Spec+it requirement' = Spec . SpecExample requirement' . evaluateExample++-- | A type class for examples.+--+-- To use an HUnit `Test.HUnit.Test` or an `Test.HUnit.Assertion` as an example+-- you need to import "Test.Hspec.HUnit".+--+-- To use a QuickCheck `Test.QuickCheck.Property` as an example you need to+-- import "Test.Hspec.QuickCheck".+class Example a where+ evaluateExample :: a -> IO Result++instance Example Bool where+ evaluateExample b = if b then return Success else return (Fail "")++instance Example Result where+ evaluateExample r = r `seq` return r++instance Example Pending.Pending where+ evaluateExample (Pending.Pending reason) = evaluateExample (Pending reason)++instance Example (String -> Pending.Pending) where+ evaluateExample _ = evaluateExample (Pending.Pending Nothing)++-- | Create a more readable display of a quantity of something.+quantify :: (Show a, Num a, Eq a) => a -> String -> String+quantify 1 s = "1 " ++ s+quantify n s = show n ++ " " ++ s ++ "s"
Test/Hspec/Monadic.hs view
@@ -1,21 +1,66 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}--- | This module contains the runners that take a set of specs, specified in a monadic style, evaluate their examples, and--- report to a given handle.+-- |+-- There is a monadic and a non-monadic API. This is the documentation for the+-- monadic API. The monadic API is suitable for most use cases, and it is more+-- stable than the non-monadic API. ----- The three functions you'll use the most are 'hspec', 'describe', and 'it'. Here is an--- example of functions that format and unformat phone numbers and the specs for them.+-- For documentation on the non-monadic API look at "Test.Hspec". --+module Test.Hspec.Monadic (+-- * Introduction+-- $intro+-- * Types+ Specs+, Example+, Pending++-- * Defining a spec+, describe+-- , context+, it+, pending++-- * Running a spec+, hspec+, hspecB+, hspecX+, hHspec++-- * Interface to the non-monadic API+, runSpecM+, fromSpecList++-- * Deprecated functions+, descriptions+) where++import System.IO+import Test.Hspec.Core (EvaluatedSpec, Example)+import qualified Test.Hspec.Core as Core+import qualified Test.Hspec.Runner as Runner+import Test.Hspec.Pending (Pending)+import qualified Test.Hspec.Pending as Pending++import Control.Monad.Trans.Writer (Writer, execWriter, tell)++-- $intro+--+-- The three functions you'll use the most are 'hspecX', 'describe', and 'it'.+-- Here is an example of functions that format and unformat phone numbers and+-- the specs for them.+-- -- > import Test.Hspec.Monadic -- > import Test.Hspec.QuickCheck--- > import Test.Hspec.HUnit+-- > import Test.Hspec.HUnit () -- > import Test.QuickCheck -- > import Test.HUnit -- >--- > main = hspec mySpecs+-- > main = hspecX mySpecs ----- Since the specs are often used to tell you what to implement, it's best to start with--- undefined functions. Once we have some specs, then you can implement each behavior--- one at a time, ensuring that each behavior is met and there is no undocumented behavior.+-- Since the specs are often used to tell you what to implement, it's best to+-- start with undefined functions. Once we have some specs, then you can+-- implement each behavior one at a time, ensuring that each behavior is met+-- and there is no undocumented behavior. -- -- > unformatPhoneNumber :: String -> String -- > unformatPhoneNumber number = undefined@@ -23,7 +68,8 @@ -- > formatPhoneNumber :: String -> String -- > formatPhoneNumber number = undefined ----- The 'describe' function takes a list of behaviors and examples bound together with the 'it' function+-- The 'describe' function takes a list of behaviors and examples bound+-- together with the 'it' function -- -- > mySpecs = describe "unformatPhoneNumber" $ do --@@ -32,79 +78,54 @@ -- > it "removes dashes, spaces, and parenthesies" $ -- > unformatPhoneNumber "(555) 555-1234" == "5555551234" ----- The 'pending' function marks a behavior as pending an example. The example doesn't count as failing. --+-- The 'pending' function marks a behavior as pending an example. The example+-- doesn't count as failing.+-- -- > it "handles non-US phone numbers" $ -- > pending "need to look up how other cultures format phone numbers" ----- An HUnit 'Test' can act as a behavior's example. (must import @Test.Hspec.HUnit@) ----- > it "removes the \"ext\" prefix of the extension" $ do+-- An HUnit 'Test.HUnit.Test' can act as a behavior's example. (must import+-- "Test.Hspec.HUnit")+--+-- > it "removes the \"ext\" prefix of the extension" $ TestCase $ do -- > let expected = "5555551234135" -- > actual = unformatPhoneNumber "(555) 555-1234 ext 135"--- > assertEqual "remove extension" expected actual+-- > expected @?= actual ----- An @IO()@ action is treated like an HUnit 'TestCase'. (must import @Test.Hspec.HUnit@) --+-- An @IO()@ action is treated like an HUnit 'TestCase'. (must import+-- "Test.Hspec.HUnit")+-- -- > it "converts letters to numbers" $ do -- > let expected = "6862377" -- > actual = unformatPhoneNumber "NUMBERS"--- > assertEqual "letters to numbers" expected actual+-- > actual @?= expected ----- The 'property' function allows a QuickCheck property to act as an example. (must import @Test.Hspec.HUnit@) --+-- The 'property' function allows a QuickCheck property to act as an example.+-- (must import "Test.Hspec.QuickCheck")+-- -- > it "can add and remove formatting without changing the number" $ property $--- > forAll phoneNumber $ \ n -> unformatPhoneNumber (formatPhoneNumber n) == n+-- > forAll phoneNumber $ \n -> unformatPhoneNumber (formatPhoneNumber n) == n -- > -- > phoneNumber :: Gen String -- > phoneNumber = do--- > nums <- elements [7,10,11,12,13,14,15]--- > vectorOf nums (elements "0123456789")+-- > n <- elements [7,10,11,12,13,14,15]+-- > vectorOf n (elements "0123456789") -- -module Test.Hspec.Monadic (-- -- * Types- Spec- , Result- , Specs-- -- * Defining a spec- , describe- , it- , pending-- -- * Running a spec- , hspec- , hspecB- , hspecX- , hHspec-- -- * Interface to the non-monadic API- , runSpecM- , fromSpecList-- -- * Deprecated functions- , descriptions-) where--import System.IO-import Test.Hspec.Core hiding (describe,descriptions,it)-import qualified Test.Hspec.Core as Core-import qualified Test.Hspec.Runner as Runner--import Control.Monad.Trans.Writer (Writer, execWriter, tell)- type Specs = SpecM () -newtype SpecM a = SpecM (Writer [UnevaluatedSpec] a)+newtype SpecM a = SpecM (Writer [Core.Spec] a) deriving Monad -- | Create a document of the given specs and write it to stdout. hspec :: Specs -> IO [EvaluatedSpec] hspec = Runner.hspec . runSpecM --- | Use in place of @hspec@ to also exit the program with an @ExitCode@+-- | Use in place of `hspec` to also exit the program with an @ExitCode@ hspecX :: Specs -> IO a hspecX = Runner.hspecX . runSpecM @@ -114,24 +135,45 @@ -- | Create a document of the given specs and write it to the given handle. ----- > writeReport filename specs = withFile filename WriteMode (\ h -> hHspec h specs)+-- > writeReport filename specs = withFile filename WriteMode (\h -> hHspec h specs) -- hHspec :: Handle -> Specs -> IO [EvaluatedSpec] hHspec h = Runner.hHspec h . runSpecM -- | Convert a monadic spec into a non-monadic spec.-runSpecM :: Specs -> [UnevaluatedSpec]+runSpecM :: Specs -> [Core.Spec] runSpecM (SpecM specs) = execWriter specs -- | Convert a non-monadic spec into a monadic spec.-fromSpecList :: [UnevaluatedSpec] -> Specs+fromSpecList :: [Core.Spec] -> Specs fromSpecList = SpecM . tell describe :: String -> Specs -> Specs describe label action = SpecM . tell $ [Core.describe label (runSpecM action)] +{-+context :: String -> Specs -> Specs+context = describe+-}+ it :: Example v => String -> v -> Specs it label action = (SpecM . tell) [Core.it label action]++-- | A pending example.+--+-- If you want to report on a behavior but don't have an example yet, use this.+--+-- > describe "fancyFormatter" $ do+-- > it "can format text in a way that everyone likes" $+-- > pending+--+-- You can give an optional reason for why it's pending.+--+-- > describe "fancyFormatter" $ do+-- > it "can format text in a way that everyone likes" $+-- > pending "waiting for clarification from the designers"+pending :: String -> Pending+pending = Pending.pending -- | DEPRECATED: Use `sequence_` instead. descriptions :: [Specs] -> Specs
+ Test/Hspec/Pending.hs view
@@ -0,0 +1,21 @@+module Test.Hspec.Pending where++newtype Pending = Pending (Maybe String)++-- | A pending example.+--+-- If you want to report on a behavior but don't have an example yet, use this.+--+-- > describe "fancyFormatter" [+-- > it "can format text in a way that everyone likes" $+-- > pending+-- > ]+--+-- You can give an optional reason for why it's pending.+--+-- > describe "fancyFormatter" [+-- > it "can format text in a way that everyone likes" $+-- > pending "waiting for clarification from the designers"+-- > ]+pending :: String -> Pending+pending = Pending . Just
Test/Hspec/QuickCheck.hs view
@@ -1,33 +1,35 @@ {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-}--- | Importing this module allows you to use a QuickCheck property as an example--- for a behavior. Use the 'property' function to indicate a QuickCkeck property.--- Any output from the example to stdout is ignored. If you need to write out for--- debugging, you can write to stderr or a file handle.+-- |+-- Importing this module allows you to use a QuickCheck `QC.Property` as an+-- example for a behavior. Use `QC.property` to turn any `QC.Testable` into a+-- @Property@. ----- > describe "cutTheDeck" [--- > it "puts the first half of a list after the last half"--- > (property $ \ xs -> let top = take (length xs `div` 2) xs--- > bot = drop (length xs `div` 2) xs--- > in cutTheDeck xs == bot ++ top),+-- NOTE: Any output from the example to @stdout@ is ignored. If you need to+-- write out for debugging, you can write to @stderr@ or a file handle.+--+-- > import Test.Hspec.Monadic+-- > import Test.Hspec.QuickCheck -- >--- > it "restores an even sized list when cut twice"--- > (property $ \ xs -> even (length xs) ==> cutTheDeck (cutTheDeck xs) == xs)--- > ]+-- > main :: IO ()+-- > main = hspecX $ do+-- > describe "reverse" $ do+-- > it "gives the original list, if applied twice" $ property $+-- > \xs -> (reverse . reverse) xs == (xs :: [Int]) -- module Test.Hspec.QuickCheck ( QC.property , prop ) where -import System.IO.Silently-import Test.Hspec.Core+import System.IO.Silently+import Test.Hspec.Core import qualified Test.QuickCheck as QC -- just for the prop shortcut import qualified Test.Hspec.Monadic as DSL --- | Monadic DSL shortcut, use this instead of @it@+-- | Monadic DSL shortcut, use this instead of `DSL.it`. prop :: QC.Testable t => String -> t -> DSL.Specs prop n p = DSL.it n (QC.property p)
Test/Hspec/Runner.hs view
@@ -1,4 +1,4 @@-+{-# OPTIONS_GHC -fno-warn-deprecations #-} -- | This module contains the runners that take a set of specs, evaluate their examples, and -- report to a given handle. --@@ -6,55 +6,69 @@ Specs, hspec, hspecX, hspecB, hHspec, hHspecWithFormat, describe, it, toExitCode ) where -import Test.Hspec.Core-import Test.Hspec.Formatters-import Test.Hspec.Formatters.Internal-import System.IO-import System.Exit--type Specs = [UnevaluatedSpec]+import Test.Hspec.Internal+import Test.Hspec.Core (EvaluatedSpec, Specs)+import Test.Hspec.Formatters+import Test.Hspec.Formatters.Internal+import System.IO+import System.Exit -- | Evaluate and print the result of checking the spec examples.-runFormatter :: Formatter -> Int -> String -> UnevaluatedSpec -> FormatM EvaluatedSpec-runFormatter formatter nesting _ (SpecGroup group xs) = do- exampleGroupStarted formatter (nesting) group- ys <- mapM (runFormatter formatter (succ nesting) group) xs- return (SpecGroup group ys)-runFormatter formatter nesting group (SpecExample requirement e) = do- result <- liftIO $ safeEvaluateExample e- case result of- Success -> do- increaseSuccessCount- exampleSucceeded formatter nesting requirement- Fail err -> do- increaseFailCount- exampleFailed formatter nesting requirement err- n <- getFailCount- addFailMessage $ failureDetails group requirement err n- Pending reason -> do- increasePendingCount- examplePending formatter nesting requirement reason- return (SpecExample requirement result)+runFormatter :: Formatter -> Spec -> FormatM EvaluatedSpec+runFormatter formatter = go 0 "" . unSpec+ where+ go nesting _ (SpecGroup group xs) = do+ exampleGroupStarted formatter nesting group+ ys <- mapM (go (succ nesting) group) xs+ return (SpecGroup group ys)+ go nesting group (SpecExample requirement e) = do+ result <- liftIO $ safeEvaluateExample e+ case result of+ Success -> do+ increaseSuccessCount+ exampleSucceeded formatter nesting requirement+ Fail err -> do+ increaseFailCount+ exampleFailed formatter nesting requirement err+ n <- getFailCount+ addFailMessage $ failureDetails group requirement err n+ Pending reason -> do+ increasePendingCount+ examplePending formatter nesting requirement reason+ return (SpecExample requirement result) failureDetails :: String -> String -> String -> Int -> String failureDetails group requirement err i = concat [ show i, ") ", group, " ", requirement, " FAILED", if null err then "" else "\n" ++ err ] --- | Use in place of @hspec@ to also exit the program with an @ExitCode@+-- | Use in place of `hspec` to also exit the program with an @ExitCode@ hspecX :: Specs -> IO a hspecX ss = hspecB ss >>= exitWith . toExitCode -- | Use in place of hspec to also give a @Bool@ success indication hspecB :: Specs -> IO Bool-hspecB ss = hspec ss >>= return . success+hspecB ss = success `fmap` hspec ss+ where+ success :: [EvaluatedSpec] -> Bool+ success = not . failure + failure :: [EvaluatedSpec] -> Bool+ failure = any p+ where+ p (SpecGroup _ xs) = any p xs+ p (SpecExample _ x) = isFailure x++ isFailure :: Result -> Bool+ isFailure (Fail _) = True+ isFailure _ = False+ -- | Create a document of the given specs and write it to stdout. hspec :: Specs -> IO [EvaluatedSpec]-hspec ss = hHspec stdout ss+hspec = hHspec stdout -- | Create a document of the given specs and write it to the given handle. ----- > writeReport filename specs = withFile filename WriteMode (\ h -> hHspec h specs)+-- > writeReport filename specs = withFile filename WriteMode (\h -> hHspec h specs) -- hHspec :: Handle -> Specs -> IO [EvaluatedSpec] hHspec h specs = do@@ -65,7 +79,7 @@ -- THIS IS LIKELY TO CHANGE hHspecWithFormat :: Formatter -> Bool -> Handle -> Specs -> IO [EvaluatedSpec] hHspecWithFormat formatter useColor h ss = runFormatM useColor h $ do- specList <- mapM (runFormatter formatter 0 "") ss+ specList <- mapM (runFormatter formatter) ss failedFormatter formatter footerFormatter formatter return specList@@ -73,4 +87,3 @@ toExitCode :: Bool -> ExitCode toExitCode True = ExitSuccess toExitCode False = ExitFailure 1-
hspec.cabal view
@@ -1,5 +1,5 @@ name: hspec-version: 1.0.0.1+version: 1.1.0 cabal-version: >= 1.8 build-type: Simple license: BSD3@@ -19,7 +19,8 @@ tests. Compared to other options, it provides a much nicer syntax that makes tests very easy to read. .- Read the introductory documentation: <http://hspec.github.com/>+ New to Hspec? Start with the introductory documentation:+ <http://hspec.github.com/> source-repository head type: git@@ -48,21 +49,37 @@ , Test.Hspec.QuickCheck other-modules:+ Test.Hspec.Pending+ Test.Hspec.Internal Test.Hspec.Formatters.Internal test-suite spec type: exitcode-stdio-1.0-+ hs-source-dirs:+ ., test main-is:- runtests.hs-- other-modules:- Specs-+ Spec.hs ghc-options: -Wall -Werror+ build-depends:+ base >= 4 && <= 5+ , silently >= 1.1.1 && < 2+ , ansi-terminal == 0.5.5+ , time < 1.5+ , transformers >= 0.2.0 && < 0.4.0+ , HUnit >= 1 && <= 2+ , QuickCheck >= 2.4.0.1 && <= 2.5+ , hspec-shouldbe +-- this should be merged with spec+test-suite old-spec+ type:+ exitcode-stdio-1.0+ main-is:+ Specs.hs+ ghc-options:+ -Wall -Werror build-depends: base >= 4 && <= 5 , silently >= 1.1.1 && < 2@@ -71,3 +88,4 @@ , transformers >= 0.2.0 && < 0.4.0 , HUnit >= 1 && <= 2 , QuickCheck >= 2.4.0.1 && <= 2.5+ , hspec-shouldbe
− runtests.hs
@@ -1,5 +0,0 @@-import Test.Hspec-import Specs (specs)--main :: IO ()-main = specs >>= hspecX
+ test/Spec.hs view
@@ -0,0 +1,9 @@+module Main (main) where++import Test.Hspec.ShouldBe++import qualified Test.Hspec.MonadicSpec++main :: IO ()+main = hspecX $ do+ describe "Test.Hspec.Monadic" Test.Hspec.MonadicSpec.spec