liveBook · Manning

Test

As explained in Functional Design and Architecture: Examples in Haskell

A test case is a defined test scenario with inputs, actions, and expected results for evaluating specific behavior.

Testing validates that software meets functional and nonfunctional requirements. Its purpose is not simply to search for bugs: it fixes contracts and expectations in executable form, documents behavior, prevents regressions, and reduces project risk. Testability is therefore an architectural concern, not a task postponed until implementation is complete. section 15.0 section 15.3

Write tests early enough to act as design validators. A test can expose a missing interface, an unclear communication type, or components that cannot be assembled before their real implementations exist. Shape the system around the required form, use dummy content at first, and replace fakes only when real behavior is needed. This is a relaxed use of test-driven development: tests should guide design without becoming a rigid procedure. section 4.1.4

Choose a testing approach by the risk and behavior to validate:

  • Use black-box testing through public interfaces when internal implementation details should not matter. section 15.1
  • Use grey-box or white-box testing when knowledge of implementation is needed to cover subtle cases. section 15.1
  • Use functional testing for isolated functions, modules, subsystems, or requirements without calling real external services. section 15.1
  • Use integration testing to validate functional requirements across integrated application parts. section 15.1

A test case should be small, independent, and responsible for one action. Arrange the data and environment, act by performing the behavior under test, then assert the result. Shared mutable state can make cases dependent on execution order; parallel execution then creates race conditions and unreliable results. Pure, deterministic functions are especially convenient to test, though determinism does not make every real-world failure observable in a test environment. section 15.1.1 section 15.1

Testing a pure function

A square-function specification groups cases for positive, negative, and zero inputs, then asserts each expected result. The zero case demonstrates why a test set should include an edge case rather than only ordinary inputs. section 15.1.1

Listing 15.1 Simple tests with hspec
square :: Integer -> Integer                     #1
square x = x * x
  
spec :: Spec
spec = do
  describe "square algorithm tests" $ do         #2
  
    it "squaring positive integer test" $ do
      square 2 `shouldBe` 4
      square 5 `shouldBe` 25
  
    it "squaring negative integer test" $ do
      square (-2) `shouldBe` 4
      square (-5) `shouldBe` 25
 
    it "squaring zero test" $
      square 0 `shouldBe` 0

Inspect a compact hspec specification that organizes examples under a description and checks ordinary and edge-case outputs. section 15.1.1

Good architecture makes meaningful testing possible. Applications that directly depend on many external services and side effects are difficult to control in tests. Decoupling interfaces from implementations, keeping coupling low, and using mocks where necessary improve coverage for IO-bound applications. Property-based testing fits CPU-bound, pure code better than IO-bound code; no single testing style covers every requirement or risk. section 15.2 section 15.3

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains test purposes, testing levels, AAA structure, independence, mocks, and architecture-driven testability without prescribing a complete methodology.
Examples
Demonstrates hspec cases for a square function, an IORef-dependent action, and device-component assembly using fake vendor APIs.
What it assumes
Assumes software-development and architecture concerns while teaching test cases, suites, assertions, and AAA from concrete Haskell examples.
What you can do after
Apply it via hspec to structure independent AAA test cases and select testing approaches for different risks.
How it connects
Connects testing with test-driven development, testability, determinism, mocking, property-based testing, integration testing, and free monads.

Also explained in

Compare how 53 books explain test →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.