wip
This commit is contained in:
parent
3250dc4fac
commit
6fbe9b41c7
@ -45,15 +45,15 @@ endpoints is that there are no tests *for* tests - tests that check that new
|
|||||||
behaviour and tests conforms to higher-level, more general best practices.
|
behaviour and tests conforms to higher-level, more general best practices.
|
||||||
|
|
||||||
`servant-quickcheck` aims to solve that. It allows describing properties that
|
`servant-quickcheck` aims to solve that. It allows describing properties that
|
||||||
*all* endpoints myst satisfy. If a new endpoint comes along, it too will be
|
*all* endpoints must satisfy. If a new endpoint comes along, it too will be
|
||||||
tested for that property, without any further work.
|
tested for that property, without any further work.
|
||||||
|
|
||||||
Why isn't this idea already popular? Well, most web frameworks don't have a
|
Why isn't this idea already popular? Well, most web frameworks don't have a
|
||||||
reified description of APIs. When you don't know what the endpoints of an
|
reified description of APIs (beyond perhaps the routes). When you don't know
|
||||||
application are, and what request body they expect, trying to generate arbitrary
|
what the endpoints of an application are, and what request body they expect,
|
||||||
requests is almost entirely going to result in 404s (not found) and 400s (bad
|
trying to generate arbitrary requests is almost entirely going to result in
|
||||||
request). Maybe one in a thousand requests will actually test a handler. Not
|
404s (not found) and 400s (bad request). Maybe one in a thousand requests will
|
||||||
very useful.
|
actually test a handler. Not very useful.
|
||||||
|
|
||||||
`servant` applications, on the other hand, have a machine-readable API description
|
`servant` applications, on the other hand, have a machine-readable API description
|
||||||
already available. And they already associate "correct" requests with particular
|
already available. And they already associate "correct" requests with particular
|
||||||
@ -178,11 +178,14 @@ import Test.QuickCheck (Arbitrary(..))
|
|||||||
import Database.PostgreSQL.Simple (connectPostgreSQL)
|
import Database.PostgreSQL.Simple (connectPostgreSQL)
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = describe "the species application" $ do
|
spec = describe "the species application" $ beforeAll check $ do
|
||||||
let pserver = do
|
let pserver = do
|
||||||
conn <- connectPostgreSQL "dbname=servant-quickcheck"
|
conn <- connectPostgreSQL "dbname=servant-quickcheck"
|
||||||
return $ server conn
|
return $ server conn
|
||||||
|
|
||||||
|
|
||||||
|
it "should not return 500s" $ do
|
||||||
|
|
||||||
it "should not return 500s" $ do
|
it "should not return 500s" $ do
|
||||||
withServantServer api pserver $ \url ->
|
withServantServer api pserver $ \url ->
|
||||||
serverSatisfies api url defaultArgs (not500 <%> mempty)
|
serverSatisfies api url defaultArgs (not500 <%> mempty)
|
||||||
@ -191,6 +194,12 @@ spec = describe "the species application" $ do
|
|||||||
withServantServer api pserver $ \url ->
|
withServantServer api pserver $ \url ->
|
||||||
serverSatisfies api url defaultArgs (onlyJsonObjects <%> mempty)
|
serverSatisfies api url defaultArgs (onlyJsonObjects <%> mempty)
|
||||||
|
|
||||||
|
where
|
||||||
|
check = do
|
||||||
|
mvar <- newMVar []
|
||||||
|
withServantServer api pserver $ \url ->
|
||||||
|
serverSatisfies api url defaultArgs (onlyJsonObjects <%> mempty)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
hspec spec
|
hspec spec
|
||||||
@ -201,30 +210,51 @@ instance Arbitrary Species where
|
|||||||
|
|
||||||
But this fails in quite a few ways.
|
But this fails in quite a few ways.
|
||||||
|
|
||||||
|
|
||||||
|
<<TODO>>
|
||||||
|
|
||||||
|
This was an example created with the knowledge of what it was supposed to
|
||||||
|
exemplify. To try to get a more accurate assessment of the practical usefulness
|
||||||
|
of `servant-quickcheck`, I tried running `serverSatisfies` with a few
|
||||||
|
predicates over some of the open-source `servant` servers I could find, and
|
||||||
|
results were also promising.
|
||||||
|
|
||||||
|
There are probably a lot of other interesting properties that one might to add
|
||||||
|
besides those I've included. As an example, we could have a property that
|
||||||
|
all HTML is checked against, which is sometimes tricky for HTML that's
|
||||||
|
generated dynamically. Or check that every page has a Portuguese translation.
|
||||||
|
|
||||||
### Why best practices are good
|
### Why best practices are good
|
||||||
|
|
||||||
As a side note: you might have wondered "why bother with API best practices?".
|
As a side note: you might have wondered "why bother with API best practices?".
|
||||||
It is, it would be said, a lot of extra (as in not only getting the feature done)
|
It is, it has to be said, a lot of extra (as in not only getting the feature done)
|
||||||
work to do, for dubious benefit. And indeed, the relevance of discoverability, for
|
work to do, for dubious benefit. And indeed, the relevance of discoverability, for
|
||||||
example, unclear, since not that many tools use it.
|
example, unclear, since not that many tools use it as perhaps was anticipated.
|
||||||
|
|
||||||
But `servant-quickcheck` both makes it *easier* to conform to best practices,
|
But `servant-quickcheck` both makes it *easier* to conform to best practices,
|
||||||
and exemplifies their advantage. If we pick 201 (Success, the 'resource' was
|
and exemplifies their advantage in enabling better tooling. If we pick 201 (Success, the 'resource' was
|
||||||
created), rather than the more generic 200 (Success), `servant-quickcheck` knows
|
created), rather than the more generic 200 (Success), and do a *little* more work
|
||||||
this means there should be some representation of the rec°as a response
|
by knowing to make this decision, `servant-quickcheck` knows this means there
|
||||||
|
should be some representation of the resource created. So it knows to ask you
|
||||||
|
for a link to it (the RFC creators thought to ask for this). And if you do (again,
|
||||||
|
a little more work), `servant-quickcheck` will know to try to look at that
|
||||||
|
resource by following the link, checking that it's not broken, and maybe even
|
||||||
|
returns a response that equivalent to the original POST request). And then it
|
||||||
|
finds a real bug - your application allows species with '/' in their name to
|
||||||
|
be created, but not queried with a 'GET' for! This, I think, is already a win.
|
||||||
|
|
||||||
|
|
||||||
## `serversEqual`
|
## `serversEqual`
|
||||||
|
|
||||||
There's another very appealing application of the ability to generate "sensible"
|
There's another very appealing application of the ability to generate "sensible"
|
||||||
arbitrary requests. It's testing that two applications are equal. Generate arbitrary
|
arbitrary requests. It's for testing that two applications are equal. We can generate arbitrary
|
||||||
requests, send them to both servers (in the same order), and check that the responses
|
requests, send them to both servers (in the same order), and check that the responses
|
||||||
are equivalent. (This was, in fact, one of the first applications of
|
are equivalent. (This was, incidentally, one of the first applications of
|
||||||
`servant-client`, albeit in a much more manual way, when we rewrote a microservice
|
`servant-client`, albeit in a much more manual way, when we rewrote a microservice
|
||||||
originally in Python in Haskell.) Generally with rewrites, even if there's some
|
originally in Python in Haskell.) Generally with rewrites, even if there's some
|
||||||
behaviour that isn't optimal, if a lot of things already depend on that service,
|
behaviour that isn't optimal, perhaps a lot of things already depend on that service
|
||||||
it makes sense to first mimick *exactly* the original behaviour, and only then
|
and make interace poorly with "improvements", so it makes sense to first mimick
|
||||||
aim for improvements.
|
*exactly* the original behaviour, and only then aim for improvements.
|
||||||
|
|
||||||
`servant-quickcheck` provides a single function, `serversEqual`, that attempts
|
`servant-quickcheck` provides a single function, `serversEqual`, that attempts
|
||||||
to verify the equivalence of servers. Since some aspects of responses might not
|
to verify the equivalence of servers. Since some aspects of responses might not
|
||||||
@ -243,17 +273,24 @@ One area is extensive automatic benchmarking. Currently we use tools such as
|
|||||||
we are interested in, and write a request that gets made thousands of times.
|
we are interested in, and write a request that gets made thousands of times.
|
||||||
But now we can have a multiplicity of requests to benchmark with! This allows
|
But now we can have a multiplicity of requests to benchmark with! This allows
|
||||||
*finding* slow endpoints, as well as (I would imagine, though I haven't actually
|
*finding* slow endpoints, as well as (I would imagine, though I haven't actually
|
||||||
tried this yet) synchronization issues that make threads wait for too long (such
|
tried this yet) finding synchronization issues that make threads wait for too
|
||||||
as waiting on an MVar that's not really needed), bad asymptotics with respect
|
long (such as waiting on an MVar that's not really needed), bad asymptotics
|
||||||
to some other type of request.
|
with respect to some other type of request.
|
||||||
|
|
||||||
(On this last point, imagine not having an index in a database for "people",
|
(On this last point, imagine not having an index in a database for "people",
|
||||||
and having a tool that discovers that the latency on a search by first name
|
and having a tool that discovers that the latency on a search by first name
|
||||||
grows linearly with the number of POST requests to a *different* endpoint! We'd
|
grows linearly with the number of POST requests to a *different* endpoint! We'd
|
||||||
need to do some to do this well, possibly involving some machine learning, but
|
need to do some work to do this well, possibly involving some machine
|
||||||
it's an interesting and probably useful idea.)
|
learning, but it's an interesting and probably useful idea.)
|
||||||
|
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
|
||||||
|
I hope this library presents some useful functionality already, but I hope
|
||||||
|
you'll also think how it could be improved!
|
||||||
|
|
||||||
|
There'll be a few more packages in the comings weeks - check back soon!
|
||||||
|
|
||||||
**Note**: This post is an anansi literate file that generates multiple source
|
**Note**: This post is an anansi literate file that generates multiple source
|
||||||
files. They are:
|
files. They are:
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user