Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@djspiewak
Last active August 6, 2020 22:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djspiewak/c6d12c6af1b2022898ac7816cde37214 to your computer and use it in GitHub Desktop.
Save djspiewak/c6d12c6af1b2022898ac7816cde37214 to your computer and use it in GitHub Desktop.

My Specs3(?) Wishlist

Prescriptive Structural Syntax

I like the mutable DSL. What I don't like is the fact that there are so many variants of it. This makes Specs a very difficult framework to teach to newcomers (similar to ScalaTest), since there are so many options. I personally prefer should/to, but there are a lot of caveats with that syntax. Instead, I think it's better to just settle on >> (or some variant thereof). It keeps the best feature of the mutable fragments construction (easy arbitrary nesting) without generating the linguistic or API ambiguity of the current menagerie.

I would remove the immutable specification definitions entirely.

Better Matcher Construction

Specs2's absolute best feature is Matcher and the ability to easily define your own. I would lean into this by making it even easier and more uniform. The implicit coercions are cute, but not particularly reliable and honestly not as helpful as a nicer set of Matcher companion constructors would be. Literally just an apply on the Matcher companion object which takes a function would probably be enough.

With that said, there are some annoying warts that you run into often when defining custom matchers. The biggest one is that it's very difficult to define a custom matcher which itself delegates to some other matcher. You end up decomposing Result by hand, which is a pain to say the least. As a note, this kind of delegation often comes up when you're trying to get access to nicer error messages from the delegate matcher, particularly with beLike (which is absurdly useful).

I'm not sure exactly what to do here or how to improve this situation, but it's probably the single most significant place where the Specs API needs a revamp.

Enforced Matcher Uniformity

I use mustEqual all the time. I don't think that I should. I think I should be forced to use must equal(value). The magic associated with must and must be and all that is really complicated, and I think it would be a lot saner if Specs abandoned the literate DSL concept and just stuck with "result must matcher".

First-Class Support for Async Tests

Doing stuff like this is very unpleasant. Tests which run in Future should be the default mode of operation, with eager tests being a special case of this. This is obviously exactly how the Fragments framework behaves behind the scenes, but it should be a little easier to exploit this directly in user-space.

Also I didn't know about AsFragments until after I wrote that code; don't at me. ;-)

Ideally, I'd like to be able to write a really seamless Cats Effect integration which makes it extremely convenient to write Specs tests in IO such that they run across both JVM and JS, and with the same performance characteristics as frameworks like Weaver.

Async ScalaCheck

This is a rough one, since ScalaCheck is ScalaCheck, but this might help: https://github.com/typelevel/scalacheck-effect I'm not sure if this should just stay third-party or be pulled in first-party, but it's important.

Better Support for things like Discipline

Discipline defines a function, checkAll, which takes a law set (which is fundamentally a set of scalacheck properties) and introduces it to Specs so that it can be run as a set of fragments. The definition is here.

In and of itself, this function doesn't behave too poorly, but it does end up being more annoying that working with specs2 directly in some subtle ways. It's surprisingly difficult to get it to respect the Parameters, for example (you generally have to pass it explicitly), meaning that Seed integration is relatively weak. I'm not sure why that is. It also seems to run all of the laws strictly sequentially in most cases, though I say "seems to" because I've seen circumstantial hints in both directions. Either way it's weird. Output for law sets appears to be buffered when Test / fork := false, though unbuffered when it's true (meaning you get incremental output as each law passes), so I don't really know.

Maybe there's nothing to be done here. It's worth thinking about a bit, I think.

Better Support for Test Abstraction

I realize this is entirely at odds with the "I like the mutable DSL" bit. :-P Sometimes (like with Discipline), you really need to be able to generate fragments programatically and have them behave as proper fragments, with appropriate line numbers and everything. This is somewhat challenging to do right now. Yes, I realize that a lot of that is because I'm using the mutable DSL. :-)

The situation doesn't come up that often, but just as matchers allow abstraction at the assertion level, it would be nice if there were a similarly powerful and easy-to-use abstraction for abstraction at the test level.

Slightly Saner pendingUntilFixed/etc Combinators

I seriously still have to look up the syntax for pending, pendingUntilFixed, skipped, etc. They aren't uniform, which makes it a lot more complicated. Also the fact that pendingUntilFixed works in the mutable DSL if you use a . to attach it to the curly brace block, but not if you omit said . and allow it to associate as a postfix method. It's very odd.

I'd really just like a uniform syntax for all of them. Something that allows me to easily wrap/unwrap a fragment in the marker.

Integrated Lifecycle Management

Before/After and friends are really complicated and I can never remember how they work. They're also very imperative. They're also completely in opposition to things like Cats Effect's Resource, making it very difficult to even implement a nice integration framework between the two.

I feel like being more explicit about the types here would probably help. Like if you define a setup, it must return something of type R, which is then optionally available as a parameter to all fragments. Similarly for something like setupEach. I'm not sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment