Note to readers: Bear with me. I promise there's a question.
I have a problem to solve and think to myself "Oh, I'll do it in Ruby."
$ bundle gem problemsolver
create problemsolver/Gemfile
create problemsolver/Rakefile
create problemsolver/.gitignore
create problemsolver/problemsolver.gemspec
create problemsolver/lib/problemsolver.rb
create problemsolver/lib/problemsolver/version.rb
Initializating git repo in /tmp/harang/problemsolver
Remove the comment on s.add_development_dependency "rspec"
in problemsolver/problemsolver.gemspec
and then
$ bundle exec rspec --init
The --configure option no longer needs any arguments, so true was ignored.
create spec/spec_helper.rb
create .rspec
New tests go into spec/
and must be in files that end in _spec.rb
. For instance, spec/version_spec.rb
describe 'Problemsolver' do
it 'should be at version 0.0.1' do
Problemsolver::VERSION.should == '0.0.1'
end
end
To run specs--ignorning code-change runners like guard--is trivial:
$ bundle exec rspec
.
Finished in 0.00021 seconds
1 example, 0 failures
You can't see it, but the message is nicely color coded for quick "Did I screw up?" scanning? The things that are very good about this:
- Setup was rapid, almost brainless (though figuring out which commands to invoke is not trivial).
- Standardized layout of the source tree reduces the familiarization period with a new code-base, making collaboration more simple and reducing the lull time when picking up a project you've left for a bit.
- A heavy reliance on tooling distributes best-practices through the community, roughly at the speed of new project creation.
Adding coverage tools, code watchers, linters, behavior test tools and others is no more difficult.
This stands unfavorably in contrast to the situation if one thinks, "Oh, I'll do it in Haskell."
$ mkdir problemsolver
$ cd problemsolver/
$ cabal init
Package name [default "problemsolver"]?
Package version [default "0.1"]? 0.0.1
Please choose a license:
1) GPL
2) GPL-2
3) GPL-3
4) LGPL
5) LGPL-2.1
6) LGPL-3
* 7) BSD3
8) MIT
9) PublicDomain
10) AllRightsReserved
11) OtherLicense
12) Other (specify)
Your choice [default "BSD3"]?
Author name? Brian L. Troutwine
Maintainer email [default "brian@troutwine.us"]?
Project homepage/repo URL?
Project synopsis? Solves a problem.
Project category:
1) Codec
2) Concurrency
3) Control
4) Data
5) Database
6) Development
7) Distribution
8) Game
9) Graphics
10) Language
11) Math
12) Network
13) Sound
14) System
15) Testing
16) Text
17) Web
18) Other (specify)
Your choice? ProblemSolver
ProblemSolver is not a valid choice.
Your choice? 18
Please specify? ProblemSolver
What does the package build:
1) Library
2) Executable
Your choice? 2
Generating LICENSE...
Generating Setup.hs...
Generating y.cabal...
You may want to edit the .cabal file and add a Description field.
"Great," you think, "I was so pestered I bet all the latest Haskell best-practices in software development are just waiting on my disk."
$ ls
LICENSE problemsolver.cabal Setup.hs
Allow me to summarize my feelings: :(
The generated cabal file doesn't even have a Main
specified, much less instructions for setting up a rudimentary project. Still, okay. If you fart around for a bit trying to find the right search keywords you'll land on How to write a Haskell program which is okay except:
- All of Haq source code gets thrown into the root directory.
- The test code for Haq is only in
Test.hs
, is only QuickCheck and has no facility for continuing the project with split-file tests. - All of this has to be manually written or copied for each new project.
Checking Real World Haskell's Chapter 11 you'll find it doesn't even mention cabal and skirts the issue of project layout entirely. None of the resources that Don Stewart kindly answers with here are addressed in either of the aforementioned and, I'll note, Mr. Stewart doesn't explain how to use any of the tools referenced.
Note that the accepted answer in Haskell testing workflow references a project that's since moved on sufficiently so as not be a good answer but does say
As cabal test doesn't yet exist -- we have a student working on it for this year's summer of code! -- the best mechanism we have is to use cabal's user hook mechanism.
Hey, okay, the cabal documentation! The appropriate section does have examples, but they're awfully contrived but don't fail to give the impression that everyone is on their own and good luck to you.
Of course, there's always test-framework that seems to be nice but it example code doesn't offer anything beyond what's seen in the wiki and is non-scalable in the sense that as soon as my program grows in complexity I'm on the hook to develop ways of dividing up tests into manageable modules. I'm not even sure what's going on with HTF and agree with Mr. Volkov's assessment.
Mr. Jelvis' comment on the linked HTF question was of particular interest to me: the Haskell tool-chain suffers, very badly, from a tyranny of small decisions. I can't actually get down to the task at hand--solving my problem in Haskell--because I'm on the hook for getting my environment just right. Why this is bad:
- It's wasted effort. Unless I'm writing a test tool, I will very, very rarely care about how my tests are slurped up, only from where.
- It's difficult to learn. There seems to be no singular resource for setting up a project with testing baked in, and the various sources that do exist are sufficiently diverse as to be unhelpful.
- It's difficult to reproduce. With so many moving pieces to arrange I'm bound to do it differently each time.
- As a corollary, it's idiosyncratic. That means its difficult to collaborate and to pick up dormant projects.
This just plain stinks.
Maybe I'm wrong, though. Does there exist some poorly advertised tool or closely developed tools to do something similar to Bundler+Rspec in the Haskell space? If not, is there a poorly advertised canonical example of modern Haskell testing with all of Mr. Stewart's referenced goodies baked right in? The project created or demonstrated:
- should by convention and tooling keep test code separate from application code in a well-defined manner (in Ruby-land, Rspec tests go in
spec/
, Cucumber features infeatures/
), - should not require end-users to compile and install testing dependencies
- should be easily reproducible, desirably in no more than 10 minutes and
- should be standardized or have the hope of standardization.
Am I wrong in believing that there's nothing at all like this in Haskell-land?
Edit0: Please note, the Ruby language's community isn't the only applicable comparison. Paul R. is correct in identifying the strong current of configuration over convention. Other languages solve the problem of getting a scalable project structure off the ground in other ways:
- C :: This language is venerable and so well-documented that you'll have trouble figuring out which well-documented approach to take. No tooling as such.
- Java :: Configuration over convention: you're bound into it at the compiler level. Many tools and very well documented.
- Scala :: Strong tool support.
- Erlang :: Venerable and loosely documented if you know what you're looking for. Arguably configuration over convention if you're using rebar or are otherwise targeting the OTP.
Paul R.'s solution of using a custom template works great if, like C, there's sufficient documentation to compile such a thing. This still runs into issues that I attempted to identify explicitly in the post, but it's workable. Haskell's best offering--that I'm aware of--is "How to write a Haskell program" but falls short of being more than the equivalent of dumping a lone Cub Scout off in the woods with a flashlight and a flask of water.
Also, yes, Static Types are great and do solve many problems that would otherwise need explicit testing. If they were an end-all solution, or mostly sufficient, even, the snap-framework would not be so thoroughly tested. (Arguably "Copy snap-core." is an answer to my question.)