packages feed

hspec-0.2.0: Specs.hs

-----------------------------------------------------------------------------
--
-- Module      :  Specs
-- Copyright   :  (c) Trystan Spangler 2011
-- License     :  modified BSD
--
-- Maintainer  : trystan.s@comcast.net
-- Stability   : experimental
-- Portability : portable
--
-- |
--
-----------------------------------------------------------------------------

module Specs where

import Test.Hspec.Internal
import Test.Hspec.QuickCheck
import Test.Hspec.HUnit ()
import System.IO
import System.Environment
import Control.Monad (liftM)
import qualified Test.HUnit as HUnit

main :: IO ()
main = do
  ar <- getArgs
  case ar of
    ["README"] -> withFile "README" WriteMode (\ h -> hPutStrLn h preable >> hHspec h specs)
    [filename] -> withFile filename WriteMode (\ h -> hHspec h specs)
    _          -> hspec specs

preable :: String
preable = unlines [
    "hspec aims to be a simple, extendable, and useful tool for Behavior Driven Development in Haskell.", "",
    "",
    "Step 1, write your 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 your dummy function",
    "> myabs n = undefined",
    "",
    "Step 3, watch them fail",
    "> hspec specs",
    "myabs",
    " x returns the original number when given a positive input (Prelude.undefined)",
    " x returns a positive number when given a negative input (Prelude.undefined)",
    " x returns zero when given zero (Prelude.undefined)",
    "",
    "Finished in 0.0002 seconds",
    "",
    "3 examples, 3 failures",
    "",
    "Step 4, implement your requirements",
    "> myabs n = if n < 0 then negate n else n", "",
    "Step 5, watch them pass",
    "> hspec specs",
    "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 specs:" ]

specs :: IO [Spec]
specs = let spec = Spec "Example" "example"
            testSpecs = [spec Success, spec (Fail ""), spec (Pending "")]
        in liftM concat $ sequence [

  describe "describe" [
    it "takes a description of what the requirements are for"
        ((=="Example") . name . head $ testSpecs),

    it "groups requirements for what's being described"
        (all ((=="Example").name) testSpecs)
  ],
  describe "it" [
    it "takes a description of the requirement"
        (requirement (Spec "Example" "whatever" Success) == "whatever" ),

    it "takes an example of the requirement"
        (result (spec Success) == Success),

    it "can use a Bool, HUnit Test, QuickCheck property, or \"pending\" as an example"
        (True),

    it "will treat exceptions as failures"
        (HUnit.TestCase $ do
          innerSpecs <- describe "" [ it "exceptions" (True && undefined)]
          let found = pureHspec innerSpecs !! 2
          HUnit.assertEqual (unlines $ pureHspec innerSpecs) " x exceptions (Prelude.undefined)" found),

    it "allows failed examples to have details"
        (const True (Fail "details"))
  ],
  describe "Bool example" [
    it "is just an expression that evaluates to a Bool"
        (True)
  ],
  describe "HUnit example" [
    it "is specified with the HUnit \"TestCase\" data constructor"
        (HUnit.TestCase $ HUnit.assertBool "example" True),

    it "is the assumed example for IO() actions"
        (HUnit.assertBool "example" True),

    it "will show the assertion text if it fails"
        (HUnit.TestCase $ do
          innerSpecs <- describe "" [ it "fails" (HUnit.assertBool "trivial" False)]
          let found = pureHspec innerSpecs !! 2
          HUnit.assertEqual found " x fails (trivial)" found)
  ],
  describe "QuickCheck example" [
    it "is specified with the \"property\" function"
        (property $ \ b -> b || True)
  ],
  describe "pending example" [
    it "is specified with the \"pending\" function and an explanation"
        (pending "message" == Pending "message"),

    it "accepts a message to display in the report"
        (documentSpec (spec (Pending "t")) == " - example\n     # t")
  ],
  describe "hspec" [
    it "displays each thing being described as a header"
        (documentGroup [spec Success] !! 1 == "Example"),

    it "displays one row for each requirement"
        (length (documentGroup testSpecs) == 5),

    it "displays a '-' for successfully implemented requirements"
        (documentSpec (spec Success) == " - example"),

    it "displays an 'x' for unsuccessfully implmented requirements"
        (documentSpec (spec $ Fail "whatever") == " x example (whatever)" ),

    it "displays optional details for unsuccessfully implmented requirements"
        (documentSpec (spec $ Fail "whatever") == " x example (whatever)" ),

    it "displays a '-' for requirements that are pending an example"
        (documentSpec (spec (Pending "pending")) == " - example\n     # pending" ),

    it "displays a '#' and an additional message for pending examples"
        (documentSpec (spec (Pending "pending")) == " - example\n     # pending" ),

    it "can output to stdout"
        (True),

    it "can output to stdout in color"
        (pending "TODO in the near future, perhaps using System.Console.ANSI?"),

    it "summarizes the time it takes to finish"
        (any (=="Finished in 0.0000 seconds") (pureHspec testSpecs)),

    it "summarizes the number of examples and failures"
        (any (=="3 examples, 1 failure") (pureHspec testSpecs))
  ],
  describe "quantify (an internal function)" [
    it "returns an amount and a word given an amount and word"
        (quantify (1::Int) "thing" == "1 thing"),

    it "returns a singular word given the number 1"
        (quantify (1::Int) "thing" == "1 thing"),

    it "returns a plural word given a number greater than 1"
        (quantify (2::Int) "thing" == "2 things"),

    it "returns a plural word given the number 0"
        (quantify (0::Int) "thing" == "0 things")
  ]]