4

I'm a programmer with a background in many languages, but most recently focused on Ruby/Rails and interested in learning some Haskell. I've done a bit of playing around in Closure as well (pretty basic stuff though).

My current preferred approach to developing a new Ruby application is to start with high-level tests in business-value language using something like Gherkin/Cucumber and then develop smaller-scale components using something like RSpec or Minitest. What is (currently) the most common similar tool-set and strategy for developing a new Haskell application?

Responders: Please be patient waiting for up-votes & answer acceptance from me. I'll have to actually do some work in Haskell in order to make any assessments. Thanks.

Steve Jorgensen
  • 11,725
  • 1
  • 33
  • 43
  • 1
    I think in general you will find less of a TDD approach in Haskell than Ruby, in particular the compiler removes a lot that requires unit testing in Ruby. I found [this answer](http://stackoverflow.com/questions/3120796/haskell-testing-workflow) useful when looking at testing frameworks for Haskell, and `Real World Haskell' has chapter 12 on testing and QA, which recommends QuickCheck and HPC. – kieran Aug 18 '13 at 11:02
  • @kieran Thanks for the link. It's great if the language removes a lot of the testing requirement, but I assume that just means fewer unit tests are warranted, not that TDD loses its value. Also, what about AATs? HSpec mentions "Acceptance Test-Driven Planning" but I don't see anything in that resembling Cucumber or Fitness. I see a cucumber-haskell project on Github, but it looks like an early work in progress. – Steve Jorgensen Aug 18 '13 at 11:22
  • yeah for sure I haven't seen anything like fitnesse for Haskell, which is why I merely commented rather than answered. I like TDD w/ fitnesse for my work in .NET, but my experience with hacking around Haskell is far less of a test oriented system, but rather a preference for small, type/data driven correct functions composed into larger systems, where purity etc. guarantee correctness post composition. thought it was worth mentioning maybe the different approaches, but either way I watch this question with interest – kieran Aug 18 '13 at 12:01
  • A trouble with low-level TDD for Haskell is that to write a meaningful test you need to know the type of what you're testing. And for basic units of generic functionality in Haskell (particular if you're designing to get the types to express as many properties of your code as possible) figuring out the types is often the hardest part! It's a common experience to find that the code almost writes itself once you've got the type right. If that's the case, then a substantial portion of the development of a component (knowing the types) is a pre-requisite for writing the test. – Ben Aug 19 '13 at 04:39

3 Answers3

7

Typically the source of truth in a Haskell app is the type system. You use quickcheck and hspec to increase assurance that your code does what you think, but it's just an aid.

Mark Wotton
  • 632
  • 4
  • 11
  • 5
    +1 for Mark. This is by far the most common design approach: design the business logic in the type system; then code up invariants that aren't expressible in types as QuickCheck properties. For anything that remains, unit tests using HUnit etc. – Don Stewart Aug 18 '13 at 13:22
  • My question is primarily about AATs, which are essentially use case tests or executable documentation. The primary use of such tests is to provide a definition of "done" that is agreed upon by the developer and the stakeholder(s) (though those might be 2 different roles of one person). – Steve Jorgensen Aug 18 '13 at 19:16
  • AATs are usually written in a language that makes sense to non-programmers and cover cases may involve multiple interactions with user(s) and/or other system(s) and possibly multiple changes to persistent data (files, databases, etc.) – Steve Jorgensen Aug 18 '13 at 19:24
  • Regarding TDD at a lower level, I obviously have not done any Haskell programming yet, so shouldn't argue too much, but... The common wisdom regarding TDD is that its value is not diminished by having a helpful type system. 1) There are always kinds of mistakes a programmer can still make regardless of the type system. 2) Having tests in place allows for a level of confidence in code refactoring which is not otherwise possible. 3) TDD is as much about aiding in the development of good APIs as it is about obtaining test coverage. I'm sure there are many more arguments I'm not thinking of. – Steve Jorgensen Aug 18 '13 at 19:33
  • 2
    Regarding TDD: (0) Many dynamic language gurus don't realize how much type systems like GHC's can enforce. (Neither do I.) (1) Also true of tests, but expressive types/proofs have the advantage of being static and hence ruling out a large class of errors; (2) Having previously tried to program in Ruby, I feel much more confident relying on purity and an rich type system (and tests later if needed) than using tests to try to cover all the wrong code paths ruled out by the type system. (3) Type- and/or proof-driven development meets the same goal, and static guarantees help avoid bogus APIs. – Fixnum Aug 18 '13 at 21:39
  • I think the AAT's remain a very interesting value of TDD not replicated by the type system. That said, a high-level description of the business logic of the system in terms of the type language can be useful for agreeing upon the goals of the program and then it can be immediately machined-checked for internal consistency (before writing any implementations). GHC's typechecker is far more powerful than you might expect. I would say it catches many more errors than you'd think it would and it provides a confidence in refactoring that TDD cannot even sort of approach. – J. Abrahamson Aug 18 '13 at 23:05
  • I suppose, Haskell's type system provides even more executable documentation in a very robust, fast, and transparent form. The only downside is that it likely comes at a higher level of abstraction than AAT's are usually written at. That can make it less fluent at the beginning. It also requires consistency which may not always be attainable in a meeting with a stakeholder. A better system might be one like Erlang/Dialyzer's success typing mode but no such thing exists for Haskell at the moment. – J. Abrahamson Aug 18 '13 at 23:07
  • Acknowledging that I'm still arguing about a language I have not used, I'm not buying the concept that some language's properties remove the benefits of TDD. For one thing, TDD is an act of design/documentation. It's not just demonstrating correctness, it's writing code that will invoke the unit, and in so doing, deciding what the unit's responsibilities should be, what would be a good descriptive name for that, etc. In terms of simply demonstrating correctness, I'm also reminded of the Knuth quote "Beware of bugs in the above code; I have only proved it correct, not tried it." – Steve Jorgensen Aug 19 '13 at 13:54
2

For story-based automated acceptance testing in Haskell, you have two major options:

  • chuchu (an incomplete Cucumber/Gherkin implementation in Haskell itself, Hackage page)
  • Cucumber using the Wire protocol with tests written in Ruby

Of course, HSpec fills the specification-based acceptance testing role.

You get better results by using property-based testing instead of TDD with libraries like QuickCheck and Smallcheck.

brijac
  • 21
  • 1
1

There is no Cucumber analogue for Haskell, as far as I know. The closest to what you're looking for is probably HSpec, which is RSpec-ish.

Here's an example, verbatim from the HSpec site:

import Test.Hspec
import Test.QuickCheck
import Control.Exception (evaluate)

main :: IO ()
main = hspec $ do
  describe "Prelude.head" $ do
    it "returns the first element of a list" $ do
      head [23 ..] `shouldBe` (23 :: Int)

    it "returns the first element of an *arbitrary* list" $
      property $ \x xs -> head (x:xs) == (x :: Int)

    it "throws an exception if used with an empty list" $ do
      evaluate (head []) `shouldThrow` anyException

Which produces

Prelude.head
  - returns the first element of a list
  - returns the first element of an *arbitrary* list
  - throws an exception if used with an empty list
jtobin
  • 3,253
  • 3
  • 18
  • 27