views:

78

answers:

2

I have a set of Happstack.State MACID methods that I want to test using QuickCheck, but I'm having trouble figuring out the most elegant way to accomplish that. The problems I'm running into are:

  • The only way to evaluate an Ev monad computation is in the IO monad via query or update.
  • There's no way to create a purely in-memory MACID store; this is by design. Therefore, running things in the IO monad means there are temporary files to clean up after each test.
  • There's no way to initialize a new MACID store except with the initialValue for the state; it can't be generated via Arbitrary unless I expose an access method that replaces the state wholesale.
  • Working around all of the above means writing methods that only use features of MonadReader or MonadState (and running the test inside Reader or State instead of Ev. This means forgoing the use of getRandom or getEventClockTime and the like inside the method definitions.

The only options I can see are:

  • Run the methods in a throw-away on-disk MACID store, cleaning up after each test and settling for starting from initialValue each time.
  • Write the methods to have most of the code run in a MonadReader or MonadState (which is more easily testable), and rely on a small amount of non-QuickCheck-able glue around it that calls getRandom or getEventClockTime as necessary.

Is there a better solution that I'm overlooking?

+1  A: 

You might checkout out the quickcheck properties that are included with happstack-state:

http://patch-tag.com/r/mae/happstack/snapshot/current/content/pretty/happstack-state/tests/Happstack/State/Tests

If you are just doing testing, and you want a throw-away data store, then you can use the memory saver, which just stores the state, event files, and checkpoints in RAM. If you lose power, then all your state would be lost. That is fine for tests, but not for a real live server. That message you linked to was talk about real live servers, not just testing.

That won't help with the initialValue issue, but it does make option 1 easier since you don't have to do any disk cleanup.

To replace the initialValue, you would need to create your own method that replaces the current state wholesale.

something like:

newState :: YourState -> Update YourState ()
newState st = put st

or something.

  • jeremy
stepcut
The catch with withMemorySaver is that it requires happstack-state to have been built with -f tests, which isn't the default, and wasn't how it was originally installed on my box. I don't want to expose a method that completely replaces the state, but I suppose I can write a function that uses withMemorySaver and runs NewState, and expose *that* for testing instead of NewState itself.
Paul Kuliniewicz
Hmm. withMemorySaver is somewhat specific to the test suite anyway.In theory you should just able to do: ctl <- runTxSystem (fmap Memory newMemoryStore) (Proxy :: Proxy MyState)But, for some reason Happstack.State.Saver.Impl.Memory is not exposed. I'll fix that in the next release.
stepcut
A: 

If you write your functions as polymorphic over MonadState (or MonadReader for queries) it can be a lot easier to set up a test harness with runState/runReader.

The happstack TH code generators are fine with signatures like that, from what I remember.

Antoine Latter
That's what I was trying to do originally, but then you lose access to things like getEventClockTime and getRandom, since those live in the Ev monad.
Paul Kuliniewicz
It is true!Maybe we need a class which provides various functions in the Ev monad which could then be mocked by something else.
Antoine Latter