diff --git a/.gitignore b/.gitignore
deleted file mode 100644
--- a/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-*~
-.ghc.environment.*
-/dist/
-/dist-newstyle/
-cabal.project.local
-*.nix
-result
diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -38,3 +38,12 @@
 * Switched from GADTs to ExistentialQuantification, as we didn't need the full power of GADTs.
 * Switched to Haskell98, as we didn't need the full power of Haskell2010.
 * Updated copyright dates.
+
+## 0.3.0.0  -- 2024-11-09
+
+* Removed `Show` instance for `Result`.  `Result` output should now be acquired with `resultToString`.
+* Renamed `isPassed` to `resultIsPassed` for consistency with `resultToString`.
+* Removed (unnecessary) `Eq` constraints from `Test`.
+* Added a second type variable `b` to `Expect`, allowing a wider variety of tests.
+* Added `assertFailure`, `assertBool`, and `assertEqual` helper functions for creating tests.
+* Simplified `Predicate` to only accept a boolean argument instead of separate predicate function and value arguments.
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,30 +1,13 @@
-Copyright (c) 2017-2023, Henry Till
-
-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.
+Copyright (c) 2017-2024 Henry Till
 
-    * Neither the name of Henry Till nor the names of other
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
 
-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.
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
diff --git a/README.md b/README.md
deleted file mode 100644
--- a/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# dwergaz
-
-[![Haskell-CI](https://github.com/henrytill/dwergaz/actions/workflows/haskell-ci.yml/badge.svg)](https://github.com/henrytill/dwergaz/actions/workflows/haskell-ci.yml)
-[![Hackage](https://img.shields.io/hackage/v/dwergaz.svg)](http://hackage.haskell.org/package/dwergaz)
-
-`dwergaz` is a minimal testing library with no external dependencies.
-
-See the [tests](https://github.com/henrytill/dwergaz/blob/master/tests/Main.hs) for a usage example.
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,3 @@
 import Distribution.Simple
+
 main = defaultMain
diff --git a/dwergaz.cabal b/dwergaz.cabal
--- a/dwergaz.cabal
+++ b/dwergaz.cabal
@@ -1,25 +1,18 @@
--- Initial dwergaz.cabal generated by cabal init.  For further
--- documentation, see http://haskell.org/cabal/users-guide/
-
+cabal-version:       2.4
 name:                dwergaz
-version:             0.2.0.6
+version:             0.3.0.0
 synopsis:            A minimal testing library
-description:         dwergaz is a minimal testing library with no external dependencies.
-license:             BSD3
+description:         dwergaz is a minimal testing library.
+license:             ISC
 license-file:        LICENSE
 author:              Henry Till
 maintainer:          henrytill@gmail.com
 homepage:            https://github.com/henrytill/dwergaz
-copyright:           Copyright (c) 2017-2023, Henry Till
+copyright:           Copyright (c) 2017-2024, Henry Till
 category:            Testing
 build-type:          Simple
-cabal-version:       >=1.10
-tested-with:         GHC ==9.2.8 || ==9.4.4 || ==9.6.2
-
-extra-source-files:
-  .gitignore
-  ChangeLog.md
-  README.md
+extra-doc-files:     ChangeLog.md
+tested-with:         GHC ==9.4.8 || ==9.6.6 || ==9.8.2
 
 source-repository head
   type:     git
@@ -30,6 +23,7 @@
   -- other-modules:
   -- other-extensions:
   build-depends:       base >=4.8 && <5
+                     , pretty >=1.1 && <1.2
   hs-source-dirs:      src
   default-language:    Haskell98
   ghc-options:         -Wall
@@ -39,6 +33,6 @@
   main-is:             Main.hs
   build-depends:       base >=4.8 && <5
                      , dwergaz
-  hs-source-dirs:      tests
+  hs-source-dirs:      test
   default-language:    Haskell98
   ghc-options:         -Wall
diff --git a/src/Test/Dwergaz.hs b/src/Test/Dwergaz.hs
--- a/src/Test/Dwergaz.hs
+++ b/src/Test/Dwergaz.hs
@@ -3,45 +3,95 @@
 -- |
 -- Module      : Test.Dwergaz
 -- Description : A minimal testing library
--- Copyright   : (c) 2017-2023, Henry Till
--- License     : BSD3
+-- Copyright   : (c) 2017-2024, Henry Till
+-- License     : ISC
 -- Maintainer  : henrytill@gmail.com
 -- Stability   : experimental
 --
 -- = Usage:
 --
--- See the <https://github.com/henrytill/dwergaz/blob/master/tests/Main.hs tests> for a usage example.
---
+-- See the <https://github.com/henrytill/dwergaz/blob/master/test/Main.hs tests> for a usage example.
 module Test.Dwergaz
-  ( Test(..)
-  , Result
-  , isPassed
-  , runTest
-  ) where
+  ( Test (..),
+    assertFailure,
+    assertBool,
+    assertEqual,
+    Result,
+    resultToString,
+    resultIsPassed,
+    runTest,
+  )
+where
 
+import Text.PrettyPrint
 
 data Test
-  = forall a. (Eq a, Show a) => Predicate String (a -> Bool)      a
-  | forall a. (Eq a, Show a) => Expect    String (a -> a -> Bool) a a
+  = forall a b. (Show a, Show b) => Expect
+      -- | Test description
+      String
+      -- | Test function
+      (a -> b -> Bool)
+      -- | Expected value
+      a
+      -- | Actual value
+      b
+  | Predicate
+      -- | Test description
+      String
+      -- | Condition
+      Bool
 
+assertFailure ::
+  -- | Test description
+  String ->
+  Test
+assertFailure = flip Predicate False
+
+assertBool ::
+  -- | Test description
+  String ->
+  -- | Condition to test
+  Bool ->
+  Test
+assertBool = Predicate
+
+assertEqual ::
+  (Eq a, Show a) =>
+  -- | Test description
+  String ->
+  -- | Expected value
+  a ->
+  -- | Actual value
+  a ->
+  Test
+assertEqual desc = Expect desc (==)
+
 data Result
-  = Passed String
-  | forall a. (Show a) => Failed String a a
+  = forall a b. (Show a, Show b) => FailedExpect String a b
+  | Failed String
+  | Passed String
 
-instance Show Result where
-  show (Failed n e a) =
-    "FAILED:   "     ++ n      ++
-    "\nEXPECTED:   " ++ show e ++
-    "\nACTUAL:     " ++ show a
-  show (Passed n) =
-    "PASSED:   "     ++ n
+prettyResult :: Result -> Doc
+prettyResult (FailedExpect n e a) =
+  vcat
+    [ text "FAILED:" <+> text n,
+      nest 2 (text "EXPECTED:") <+> text (show e),
+      nest 2 (text "ACTUAL:") <+> text (show a)
+    ]
+prettyResult (Failed n) = text "FAILED:" <+> text n
+prettyResult (Passed n) = text "PASSED:" <+> text n
 
-isPassed :: Result -> Bool
-isPassed (Passed _) = True
-isPassed _          = False
+resultToString :: Result -> String
+resultToString = render . prettyResult
 
+resultIsPassed :: Result -> Bool
+resultIsPassed (Passed _) = True
+resultIsPassed _ = False
+
 runTest :: Test -> Result
-runTest (Predicate n p v) | p v       = Passed n
-                          | otherwise = Failed n True False
-runTest (Expect n f e a)  | f e a     = Passed n
-                          | otherwise = Failed n e a
+runTest (Expect n f e a)
+  | f e a = Passed n
+  | otherwise = FailedExpect n e a
+runTest (Predicate n c)
+  | c = Passed n
+  | otherwise = Failed n
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,45 @@
+module Main (main) where
+
+import Control.Monad (when)
+import Data.Either (isLeft, isRight)
+import System.Exit (exitFailure)
+import Test.Dwergaz
+
+testFun01 :: a -> String
+testFun01 = const "quux"
+
+testFun02 :: a -> Int
+testFun02 = const 43
+
+testFun03 :: a -> Either String String
+testFun03 = const (Left "quux")
+
+testFun04 :: a -> Either String Int
+testFun04 = const (Left "quux")
+
+expectExample01, expectExample02 :: Test
+expectExample01 = Expect "Strings are equal" (==) "quux" (testFun01 "quux")
+expectExample02 = assertEqual "Ints are equal" 42 (testFun02 "quux")
+
+predicateExample01, predicateExample02, predicateExample03 :: Test
+predicateExample01 = Predicate "Value is a Left" (isLeft $ testFun03 "quux")
+predicateExample02 = assertBool "Value is a Right" (isRight (testFun04 "quux"))
+predicateExample03 = assertFailure "Just fail"
+
+exampleTests :: [Test]
+exampleTests =
+  [ expectExample01,
+    expectExample02,
+    predicateExample01,
+    predicateExample02,
+    predicateExample03
+  ]
+
+results :: [Result]
+results = fmap runTest exampleTests
+
+main :: IO ()
+main = do
+  mapM_ (putStrLn . resultToString) results
+  -- The following should use 'Control.Monad.unless' in real usage.
+  when (all resultIsPassed results) exitFailure
diff --git a/tests/Main.hs b/tests/Main.hs
deleted file mode 100644
--- a/tests/Main.hs
+++ /dev/null
@@ -1,44 +0,0 @@
-module Main (main) where
-
-import           Control.Monad (unless)
-import           Data.Either   (isLeft, isRight)
-import           System.Exit   (exitFailure)
-
-import           Test.Dwergaz
-
-
-testFun01 :: a -> String
-testFun01 = const "quux"
-
-testFun02 :: a -> Int
-testFun02 = const 42
-
-testFun03 :: a -> Either String String
-testFun03 = const (Left "quux")
-
-testFun04 :: a -> Either String Int
-testFun04 = const (Right 42)
-
-expectExample01, expectExample02 :: Test
-expectExample01 = Expect "Strings are equal" (==) "quux" (testFun01 "quux")
-expectExample02 = Expect "Ints are equal"    (==) 42     (testFun02 "quux")
-
-predicateExample01, predicateExample02 :: Test
-predicateExample01 = Predicate "Value is a Left"  isLeft  (testFun03 "quux")
-predicateExample02 = Predicate "Value is a Right" isRight (testFun04 "quux")
-
-exampleTests :: [Test]
-exampleTests =
-  [ expectExample01
-  , expectExample02
-  , predicateExample01
-  , predicateExample02
-  ]
-
-results :: [Result]
-results = fmap runTest exampleTests
-
-main :: IO ()
-main =  do
-  _ <- mapM_ print results
-  unless (all isPassed results) exitFailure
