microspec (empty) → 0.1.0.0
raw patch · 4 files changed
+360/−0 lines, 4 filesdep +QuickCheckdep +basesetup-changed
Dependencies added: QuickCheck, base
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- Test/Microspec.hs +272/−0
- microspec.cabal +56/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Tom Murphy++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Tom Murphy nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Test/Microspec.hs view
@@ -0,0 +1,272 @@+-- | Tests can be structured as nested 'it' / 'describe' statements+-- +-- E.g.+-- +-- > microspec $ do+-- > describe "plus" $ do+-- > it "adds positive numbers" $ do+-- > it "does 1 + 1" $+-- > 1 + 1 == 2+-- > it "does 2 + 2" $+-- > 2 + 2 == 4+-- > it "is commutative" $+-- > \x y -> x + y == y + (x :: Int)+-- +-- ...which will return, nicely in green instead of bold:+-- +-- @+-- plus+-- adds positive numbers+-- __does 1 + 1__+-- __does 2 + 2__+-- __is commutative__+-- @++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}++module Test.Microspec (+ -- * Specification+ microspec+ , microspecWith+ , describe+ , it+ , pending+ , prop+ , Microspec+ , MTestable++ -- * Configuration+ , MArgs(..)+ , defaultMArgs++ -- * Compatibility+ , shouldBe++ -- Reexports+ , module Test.QuickCheck+ , module Test.QuickCheck.Modifiers+ , module Test.QuickCheck.Monadic+ -- , module Test.QuickCheck.Property+ ) where++import Control.Monad+-- import Data.Time (getCurrentTime, diffUTCTime)+import Test.QuickCheck as QC+import Test.QuickCheck+import Test.QuickCheck.Modifiers+import Test.QuickCheck.Monadic+-- import Test.QuickCheck.Property++---------- User-facing:++-- | Run your spec. Put this at the top level, e.g.:+-- +-- > main = microspec $ do+-- > describe "plus 1" $+-- > 3 + 1 == 4+microspec :: Microspec () -> IO ()+microspec = microspecWith defaultMArgs++-- | 'microspec' with 'MArgs'+microspecWith :: MArgs -> Microspec () -> IO ()+microspecWith args spec = do+ putStrLn ""+ forM_ (buildTestTrees spec) $ \test -> do+ (runTestWith args {- 1 -} 0) test+ putStrLn ""++runTestWith :: MArgs -> Int -> TestTree -> IO ()+runTestWith args depth = \case+ TestLeaf testLabel (Right aProp) -> do+ let timeoutMaybe = case _mArgs_timeoutSecs args of+ Nothing -> id+ Just numSecs -> within $ fromEnum $ numSecs * (10^(6::Int))+ result <- quickCheckWithResult (_mArgs_qcArgs args) $ timeoutMaybe aProp+ putStrLn $ showResult testLabel result+ TestLeaf testLabel (Left Pending) ->+ putStrLn $ indentationFor depth ++ inYellow (testLabel ++ " - PENDING")+ TestBranch testLabel forest -> do+ putStrLn $ indentationFor depth ++ testLabel+ forM_ forest $ runTestWith args (depth + 1)+ where+ indentationFor :: Int -> String+ indentationFor n = replicate (n*2) ' ' -- ++ "- "++ showResult :: String -> QC.Result -> String+ showResult testLabel = \case+ -- note: if we wanted to show quickcheck labels, this is where we would:+ Success _ _ _ ->+ indentationFor depth ++ inGreen testLabel+ failure@(Failure{theException=Nothing}) ->+ indentationFor depth+ ++ inRed (testLabel ++ " - "++replaceNewline (output failure))+{-+ ++ "\n"+ ++ indentationFor (depth + 1) ++ inRed (show $ usedSeed failure)+-}+ failure {- @(Failure{}) -} ->+ indentationFor depth+ ++ inRed (testLabel ++" - "++replaceNewline (output failure))+ replaceNewline = concatMap $ \case '\n' -> " | " ; x -> [x]+++-- If you like the word 'pending', this is the place for you!:+data Pending = Pending+-- | Describe a test as unwritten, e.g.:+-- +-- > describe "meaning of life" $ pending+pending :: Pending+pending = Pending+++---------- Handy++-- | An alias for 'describe'. Usually used inside a 'describe' block:+-- +-- > describe "replicate" $ do+-- > it "doubles with 2" $+-- > replicate 2 'x' === "xx"+-- > it "creates a list of the right size" $+-- > \(Positive n) -> length (replicate n 'x') == n+it :: MTestable t => String -> t -> Microspec ()+it = describe++-- | Make a test case from a QuickCheck function. Alias for 'Test.QuickCheck.property'.+-- +-- Note that you don't need to use this to create a test, e.g.:+-- +-- > describe "reverse preserves length" $+-- > \l -> length (reverse l) == length l+prop :: Testable prop => prop -> Property+prop = property++++---------- Constructing a test tree:++addTestTree :: TestTree -> Microspec ()+addTestTree tree = Microspec [tree] ()++-- | Something which can be tested+-- +-- Note both Bools and Properties can be tested, but only Properties show+-- the values that weren't equal+-- +-- For both unit tests and property tests, if you want to see the outputs+-- of failed tests use 'Test.QuickCheck.==='. If you just want to test for+-- equality, use 'Prelude.=='.+-- +-- For example, the outputs of running:+-- +-- @+-- microspec $ do+-- describe "baddies" $ do+-- it "isn't 1 ==" $ 0 == (1 :: Int)+-- it "isn't 1 ===" $ 0 === (1 :: Int)+-- it "isn't always 1 ==" $ \x -> x == (1 :: Int)+-- it "isn't always 1 ===" $ \x -> x === (1 :: Int)+-- @+-- +-- are:+-- +-- @+-- isn't 1 == - *** Failed! Falsifiable (after 1 test)+-- isn't 1 === - *** Failed! Falsifiable (after 1 test): | 0 /= 1+-- isn't always 1 == - *** Failed! Falsifiable (after 1 test): | 0+-- isn't always 1 === - *** Failed! Falsifiable (after 1 test): | 0 | 0 /= 1+-- @+++class MTestable t where+ -- | Describe a test, e.g.:+ -- + -- > describe "reverse 'foo' is 'oof'" $+ -- > reverse "foo" === "oof"+ describe :: String -> t -> Microspec ()+instance MTestable Property where+ describe testLabel aProp = addTestTree $ TestLeaf testLabel (Right aProp)+instance MTestable Bool where+ describe testLabel bool = describe testLabel $ property bool+instance MTestable TestTree where+ describe testLabel x = addTestTree $ TestBranch testLabel [x]+instance MTestable Pending where+ describe testLabel pend = addTestTree $ TestLeaf testLabel (Left pend)+instance MTestable (Microspec ()) where+ describe testLabel x =+ let forest = buildTestTrees x+ in addTestTree $ TestBranch testLabel forest+instance (Arbitrary a, Show a, Testable prop) => MTestable (a -> prop) where+ describe testLabel f = describe testLabel $ property f++data TestTree+ = TestBranch String [TestTree]+ | TestLeaf String (Either Pending Property)++instance Show TestTree where+ show = \case+ TestBranch testLabel subs -> "Branch "++show testLabel++" "++show subs+ TestLeaf testLabel _ -> "Leaf " ++ show testLabel++-- Basically a writer monad:++-- | A series of tests, to run with 'microspec'+data Microspec a = Microspec [TestTree] a++instance Functor Microspec where+ fmap f (Microspec forest a) = Microspec forest (f a)+instance Applicative Microspec where+ pure a = Microspec [] a+ f <*> a =+ let Microspec forest0 f' = f+ Microspec forest1 a' = a+ in Microspec (forest0 ++ forest1) (f' a')+instance Monad Microspec where+ return = pure+ ma >>= f =+ let Microspec forest0 a = ma+ Microspec forest1 b = f a+ in Microspec (forest0 ++ forest1) b++buildTestTrees :: Microspec () -> [TestTree]+buildTestTrees (Microspec x _) = x+++++---------- Configuration:++-- | Default arguments. Calling \"microspec\" is the same as calling+-- \"microspecWith defaultMArgs\".+defaultMArgs :: MArgs+defaultMArgs = MArgs {+ _mArgs_timeoutSecs = Nothing -- Just 60+ ,_mArgs_qcArgs = QC.stdArgs { chatty = False }+ }++-- | Tweak how tests are run, with 'microspecWith'.+data MArgs = MArgs {+ _mArgs_timeoutSecs :: Maybe Double -- ^ Number of seconds before each+ -- test times out. If you want to+ -- do this on a per-test basis, try+ -- 'Test.QuickCheck.Property.within'+ ,_mArgs_qcArgs :: QC.Args -- ^ Arguments to use with QuickCheck tests+ }+ deriving (Show, Read) -- , Eq, Ord)++++---------- Pretty-printing:++inRed, inGreen, inYellow :: String -> String+[inRed,inGreen, inYellow] = (`map` [31,32,33]) $ \colorNum ->+ \s -> "\ESC["++show (colorNum::Int)++"m"++s++"\ESC[m"++++---------- HSpec compatibility++-- | Hspec compatibility. Equivalent to using 'Test.QuickCheck.==='+shouldBe :: (Eq x, Show x) => x -> x -> Property+shouldBe = (===)+
+ microspec.cabal view
@@ -0,0 +1,56 @@+name: microspec+version: 0.1.0.0+synopsis: Tiny QuickCheck test library with minimal dependencies+description: + A tiny (1 module, <500 lines) property-based (and unit) testing library with minimal dependencies.+ .+ Don't add a bunch of transitive dependencies just to test your code!+ .+ Instead of reinventing the wheel (<https://xkcd.com/927>), we use a+ RSpec/HSpec-like API and run tests with QuickCheck.+ .+ > import Test.Microspec+ > + > main :: IO ()+ > main = microspec $ do+ > describe "replicate" $ do+ > it "doubles with 2" $+ > replicate 2 'x' == "xx"+ > it "creates a list of the right size" $+ > \(Positive n) -> length (replicate n 'x') == n+ > + > describe "reverse" $ do+ > it "reverse . reverse == id" $ \l ->+ > reverse (reverse l) == (l :: [Int])+ > + > describe "tail" $+ > it "length is -1" $ \(NonEmpty l) ->+ > length (tail l :: [Int]) == length l - 1+ > + > describe "solve the halting problem" $+ > pending+license: BSD3+license-file: LICENSE+author: Tom Murphy+maintainer: Tom Murphy+-- copyright: +category: Test, Testing+build-type: Simple+cabal-version: >=1.10+stability: experimental++library+ exposed-modules:+ Test.Microspec+ -- other-modules: + other-extensions:+ FlexibleInstances+ , LambdaCase+ build-depends:+ -- PVP compat:+ -- base >=4.9 && <5+ -- , QuickCheck >=2.9 && <2.10+ base <5+ , QuickCheck+ -- hs-source-dirs: + default-language: Haskell2010