I have a network protocol to implement, which is can be described as a complicated state machine (Petri net, actually). I can easily slap together something, but how do I test it?
I have thought of inverting control, so that every step in a protocol is implemented as a pure function taking current state, event and producing new state and actions to perform (a-la Erlang gen_server
), but then testing it would require crafting this internal state.
if you do the protocol using code in terms of InputStream and OutputStream making test data for tests is straightforward
oh, right, there's the back and forth thing
@noisesmith I'm afraid this is network protocol, not application one. It's a client for layer3 VPN tunnel running over DNS.
if I am testing a pure function, I always have to create the "state" it operates on
@dottedmag right that's fine, what I meant was that it takes bytes as input, and outputs bytes, at some point
That's why I hope there is another way, or a testsuite will be very brittle: any change in implementation would require changing all the tests.
well, it's a protocol, right? if the bytes change meaning you need to change tests...
Yes, but not the internal state.
if you mean implementation as in internal states ... OK you aren't using pure functions
you can make it pure by passing state as an argument (conventionally the first)
attach that to a driver that gives it bytes
could use the network, could use some data in a test
and yes, if the state changes implementation, your functions need to change!
and the tests of course
but you should be able to define "reasonable-resting-state" or whatever in the implementing code
it's part of the implementation clearly
yeah - it would make sense to make the initial state at the very least
They come from somewhere in the production — so maybe the same code should be exposed to the tests to produce the testing state.
and if you express the state right it should be straightforward to express it as a literal in the test via some assoc calls or whatever
E.g. something like (session-is-now-authenticated state)
should be used both by a test which starts from an authenticated state, and by a protocol code itself once the handshake is complete.
also, I have a library for dumping app state (args, return values, whatever) into a document (via transit) and load it as a one liner in my test
That's a good way too. Though I wonder how readable the resulting tests are.
@dottedmag (let [app (restore-from-disk "./test/data/overcommit.transit.json"] (is (ok? (recover app))))
@noisesmith And how readable is overcommit.transit.json
?
it's a transit file - on disk it's a json, in a repl you can do anything with it
I know, but can you really read it and understand the state the system in?
but the name should be informative at the very least 😄
ok 🙂
@dottedmag with a repl haha
Got it
so yeah, that is a concern to that approach, good point
All right, I'll start simple (with a handcrafted protocol state), try extracting common code to be used both by implementation and test code and see how it goes.
@noisesmith Thanks for thoughts.
@dottedmag one thing I do to offset the transit files not being especially human readable is making test assertions where the fact that the assertion passes tells you something about the nature of the thing stored
but that's not the same as being able to read the data of course