-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scala unit testing library proposal #641
Comments
I really like the idea of a standard testing framework for Scala. Though I think such a tool must be really extensible and flexible. My team has some specific needs which cannot be easily satisfied by any existing tool. Our flow is:
Most of the test frameworks do not allow to hook into discovery and initialization logic. For example, it's hardcoded in And in fact we are enforced to choose between implementing our own toolkit or rewriting a lot in an existing framework (most likely without a chance for the patch to be accepted). Please consider this problem. My team will be happy to help to design and implement. |
If you want this to help Scala.js, an absolute requirement is support for asynchronous tests, i.e., tests returning a Also it will be necessary that test classes extend a given class or trait. Annotations won't cut it. How are you going to test the testing framework on JS and Native? |
Since we're talking about minimal testing kits, I think it's fair to mention |
I am just going to chime here on the recent popularity of minimalist testing frameworks, I think they micro test frameworks work different with specific types of tests compared to the more fully fledged test frameworks (i.e. the BDD style ones). Micro test frameworks seem to work best when you are testing libraries and compilers, you want minimal bootstrap and most of the time you are just testing assertions. On the other handle when testing applications the microtesting frameworks tend to be sub-optimal. You basically end up re-implementing the functionality of matchers and all of this "bloat" which people complain about is stuff that is actually required (i.e. I need to match that in some specific circumstance a specific exception is thrown). Having diffs is another thing, i.e. when you are comparing one I guess my personal stance on this is I have zero issue with creating a minimalist dependency free test framework to deal with the chicken and egg dependency problems we have with bootstrapping Scala as well as critical libraries however I would wary about making it a "defacto" testing framework. I think that these larger test frameworks have their merit, its just in different areas which aren't always visible to other people. |
Not just |
Before we consider the minutiae of technical questions listed under Consider the 245 commits that have gone into minitest and 471 commits that have gone into uTest, the two smallest widely-used testing libraries in the ecosystem: is the core Scala team willing to put in similar amounts of effort (i.e. more than a summer!) to bring their library up to a similar level of quality? Or do we have reason to believe it will take less effort than it has taken in the past? How much effort are we thinking about investing anyway? A week of full-time work? A month? A quarter? A year? Someone's ongoing job forever? Each of these constraints has a vastly different solution space! Do we have any consensus on what testing library the core Scala folks like? Do we have numbers on what testing libraries are popular in the community, i.e. what everyone else people likes? It honestly doesn't make sense writing your own testing library unless you are a testing-library-connoisseur that has tried several libraries and decided none of them are satisfactory, and you really (really (really)) know what you want Have we considered simply touching-up and upstreaming uTest or minitest? Or, if we would like to keep them independent and able to evolve, vendoring one of them the same way OpenJDK vendors org.ow2.asm? These libraries already support literally all the technical requirements listed above. |
@lihaoyi-databricks it's an offtopic question, but would you accept a patch into uTest addressing rigidity of test initialization logic? I like uTest but as I said in the comment above it doesn't allow us to do what we need. |
@pshirshov that is off topic and something that can be discussed elsewhere |
@lihaoyi-databricks offtopic, but not completely. I would vote agains uTest in it's current state - it's not flexible enough and it may be an issue if it becomes a part of scala standard library and currently existing APIs will freeze for years. The same applies to all the other test frameworks, unfortunately. They are too restrictive and too opinionated and it really kills productivity in some use cases. |
The proposal says "work with the community to introduce a basic unit testing library to the Scala project". Of course it will build on / profit from all the existing work.
That's indeed one of the possible outcomes!
We really hope to find a common ground. There will always be special needs and therefore reasons to build/use other libraries. But in many cases, a solid standard will do. |
Haha I thought I was going to profit from all the existing work, and that's probably what alex thought he was doing too, and yet hundreds of commits later here we are still work-in-progress 😛 I'm not asking time/effort questions rhetorically just to be a downer, but to really try and bring attention to them: "time", "effort", "build vs buy" and other project-management-y things are the high order bit here. The answers to these questions quickly limit the scope of what is technically possible, and constraints like "our budget is 1/5 of a person on this for the next 3 months" or "our strategy is to provide a good testing experience for projects {X,Y,Z} before attempting to branch out" really helps focus a discussion that otherwise can easily get lost in the weeds of matchers and monads and DAGs. |
This is sort of a public works, not unlike making a small public transportation around a city. Once it's part of scala/scala repo, we are hoping that there will be many contributors who can chip in their time to keep it up to date, and improve it over time. |
Don't say that "monads and DAGs" are not important. As well our team would be happy invest time and money into working on a tool which will save us a lot in future, so the answer to these budget questions really depends on the outcome of the discussion. For now the lack of a test framework which may support our flow with "monads, DAGs" and GC is one of the biggest productivity killers, so we would be happy to invest up to several months to finally close the issue :) |
It's not clear yet how much time/effort will be able to go into this, partially because we don't know who is happy and willing to work on this. So let's take uTest. You maintain uTest and let's say the consensus is that uTest is what we want. How would you feel about upstreaming uTest into scala/scala and maintain it there (where you'd probably receive other maintenance help)? (The idea being that uTest users would migrate, and external uTest wouldn't evolve and therefore wouldn't also need maintenance.) |
I don't have a horse in this race (aside from a personal longstanding preference for just "the way that specs works"), but it's important to understand that the "minimal" in "minimal test framework" is an immensely complicated thing, and it means very different things to different people. All of the questions to resolve in the OP are a good example of this, but there are so many more that weren't raised (such as exceptional-vs-value assertions, meta-initialization structure, extensibility and transparent abstraction mechanisms, etc). Hell, just bikeshedding "must" vs "should" vs "assert" vs "===" is going to be a massive time sink. Let's also remember that a relatively minimal test framework is not going to meet the needs of a massive chunk of the ecosystem. Anything in the Cats ecosystem is going to need something like Discipline, which is pretty dependent on ScalaCheck (or something equivalent), and its framework integration takes advantage of some of the "not very minimal" features in both ScalaTest and Specs2. A lot of commercial projects I've seen depend on complex initialization staging logic which is only available in richer frameworks. I think it's fair to say that OP's goal of standardizing testing in Scala is very much out of reach for any such minimal framework, even leaving aside the subjective preferences issues. There's also the problem of solving the bootstrap, as @sjrd mentioned, and that problem doesn't just go away by upstreaming the library. I feel like this issue is solvable, but only with tooling similar to what bootstraps scalac itself. This in turn implies that upstreaming a testing library might be necessary, but it can't be part of scala-library itself, since it needs to be snapshotted and bootstrapped independently. |
(it's there as "test results with exceptions or with values?" :P) I purposely descoped property-based testing (though it's there in the notes), because I felt like it was another large, opinionated debate that I didn't want to tackle immediately. But, of course, it's very important.
The proposal is absolutely NOT to add this to scala-library, but to add another library/jar. Like scala-reflect is a library/jar that's co-versioned and co-released with scala-library and compiler. |
I think overall there are two main approaches we can take:
The limitation of (1.) is that whatever you build would by necessity be very rudimentary: usable, but perhaps without a lot of the fancier features people may be used to in uTest or Scalatest and others. This may be enough, depending on what the goals are. Basically JUnit, but scalafied, and free of needing to chase the JUnit upstream. I don't think building something both sophisticated and novel is feasible: too much uncertainty, ambiguity, and it's doubtful we'd reach an acceptable level of generality/quality to be worthy of inclusion in the standard library. The solution space is too big and people's opinions and styles are too heterogenous. (2.) gives you a solid base of a known quantity to build upon: something we already know works, and we know what people like or do not like about it. We'd be taking in a library with well-known limitations and well-known flaws, and accepting them It also gives you a chance to fix/cleanup things, and maybe sand off the more idiosyncratic parts of the library to try and appeal to a broader consensus, while still maintaining the core essence that made the library popular in the first place. There's then the question of whether development will continue externally or not. I don't have an answer to that. From what I can see, the big choice is between novel-and-simple, or unoriginal-but-sophisticated. In either case, I don't think there's much room for exploratory work: IMO anything going into the standard library should be a known quantity, either due to simplicity or due to age. If we want experimentation, that can happen outside the std lib. I don't have a strong opinion in which one I prefer, and both seem they could conceivably satisfy the stated goals, but maybe others have opinions on this framing |
Everything what comes into stdlib defines workflows for thousands of people for years. I think it's a very good idea to take into consideration the state of modern scala ecosystem and current developer needs. Otherwise we may just incorporate JUnit - it's definitely simple and mature :) |
That's absolutely the intent. |
I think this proposal might have gone off the rails from the very first comment. I think that anything that aims at being a general purpose, extensible, blah blah, testing framework for the entire ecosystem is doomed to failure and will waste a lot of people's time and energy. A cross-platform, zero-dependency, minimal, testing framework for bootstrapping the toolchain and a small handful of foundational third-party libraries has a chance of success however. I don't think such a library should aspire to being attractive to anyone outside that very small set of people. |
I'm strongly aligned with @milessabin on this issue. Extensibility and flexibility are fine, but it should be extremely minimal and focus on what the core tools need, while providing as much value and flexibility as possible for extension and use by others, but not actually providing them with all the rich and varied features found in even the more minimal unit testing frameworks. If something this limited isn't actually useful, then we should observe that Scala already has a good variety of unit testing frameworks and the status quo should be seriously considered as perhaps the best alternative. |
I agree as well on the design direction. We should focus on what we can remove instead of what more feature we can add. |
The way tests are detected and run by the test framework has a high impact on the friendliness/usability of the library. I’m not only talking about the fact that Scala.js does not support annotations at runtime, but about how painful it can be for the developer to just ask the test runner to do what he wants. The problem often comes from the fact that the test framework has one way of running things (e.g., in the case of JUnit, any parameterless method annotated with The solution to this problem consits in removing the inversion of control. Instead of having the environment call the tests, let the user run the tests and report their execution to the environment. (We can still provide boilerplate-free helpers to handle the happy path, so that no syntactic overhead is added in such a case) If you do that, suddenly a lot of features of the test framework are not anymore needed (e.g., no need for an The lihaoyi/utest library looks really good to me in terms of ergonomics and features, except that it does not address this problem. The semantics of this test suite deeply confuses me because it is not how variables work in normal programs. Similarly, the fact that it is not possible to define a collection of tests by iterating on a collection of values strikes me. I’m scared by this implicit |
Yes, precisely. |
It would be nice if the compiler projects could get off of JUnit and use a simple Scala test framework instead, but I wonder how much work that would take compared to how much work it will take to keep using JUnit from Scala.js and Scala Native. To get to something actually useful might be more work than you are imagining. I certainly didn't realize how much work it would take until it was too late to go back! If the focus of the project is to create a simple Scala test framework primarily for compiler projects and other upstream projects like scala-xml, that could be worth exploring. I wonder if one could make a Scala-friendly extension of JUnit for this purpose. Also I'm curious about this background:
I'm curious where the conclusion that "there's been a swing-back movement towards minimalistic test frameworks that do not employ should-DSLs" comes from. My experience is that throughout our history some Scala users have prefered should/must DSL-like test code whereas others have preferred traditional test structures and assertions. |
Subjectively, I'm very much in the "should/must" camp. To the point where I find it immensely awkward to write tests without them. I know it's psychological and I know it's mostly pointless, but at the same time I think that writing good tests is at least partially about comfort, so I've made no effort to coldly strip away my preferences. I'm also not a particularly representative sample of the broader Scala community. |
I wrote the background section. I tried to phrase thing as neutral as possible, but I'd admit it's totally subjective. From my perspective there were some period in Scala like 2.7 ~ 2.10 where ecosystem (language designers, library authors, etc) were exploring various power that the language provides. And around since Scala 2.10~ when people started moving away from symbolic methods and SIP-18 discussion were happening I'd say the swing back towards simplicity started to happen overall. In the testing arena, uTest and Minitest were both announced in late 2017, so it took a quite a bit for the effect to reach. And I feel like I've been seeing upward trend of support towards minimalism among core users. But to clarify, I am not claiming that it's diminishing the importance of BDD or property-based testing, but more like adding a third wave of testing libraries. I didn't mention there, but there's also a bunch of other things happening like linting, benchmark / perf tools, coverage, and integration testing related to this area. |
Not sure where that number is coming from, but to clarify, uTest has been in heavy usage for more than 5 years now since the beginning of 2014. It's not exactly some new kid on the block
|
I stand corrected. I went by http://www.lihaoyi.com/post/uTesttheEssentialTestFrameworkforScala.html, but I should've looked at the repo. |
By the way, at least in ScalaTest, tests register during construction via a side effect (in styles other than |
In case you're not aware, this is how ScalaTest works. ScalaTest runs a |
💯 |
For this Summer of Usability campaign, let's focus on the first part and leave out any suggestion about "common path" or "standard unit testing library". |
@Ichoran :
Actually, a basic solution can provide a couple of extension points and become extensible. In case of
Yeah, I hope that such a basic solution may be designed the way it would be possible to alter discovery/init/scheduling and easily write polymorphic tests.
It's lot easier to consider previous mistakes when designing something from scratch. I'm considering modifying |
There is that 400 lines-long method with funny name
In my case rigid code of discovery and scheduling which is hardcoded in that method is a fatal flaw. I need global knowledge, but I can't do anything. doRunRunRunDaDoRunRun. |
Hi all, A few thoughts on this thread. It is still hard for me to find the best way to structure them so bear with me! I see (like others) at least 2 issues we need to address:
About issue n.1I think it is ok to have many testing libraries as long as they are zero-dependencies. This is what I did with Other "foundational" libraries like
(Damn, that's not so tiny) One interesting design choice is to decide between pure assertions vs exceptions. Probably exceptions is the simplest mental model for the user but not necessarily for the implementer. I would be tempted to explore a monadic design like in We can also consider something entirely different! Instead of creating a common test library with support for assertions, data generation, etc... we can take some inspiration from
which I don't think we need for foundational libraries but this is crucial for application testing. If those 2 aspects could be left out and delegated to plugins/extensions that would be great (actually I don't know that much about testing Scala itself, maybe dealing with resources is quite important there too). Coming back to the "dependencies problem" it feels weird to build a whole testing library to be faster at releasing just the first layer of the ecosystem. Many other libraries depend on that first layer, and applications depend on them. So maybe we need to get better at releasing cascading dependencies in general, optimising the first layer at the expense of having to maintain a new testing library might not be worth it. About issue n.2Now let's say there is a testing library for the foundational libraries, including
Then new users can decide to switch to other libraries once they know Scala and the ecosystem well enough. We would need to make it clear that this is just a starting point and resist the temptation to add features (maybe call it In conclusionI am not opposed to exploring:
|
It has been interesting how difficult it is to communicate what the design of Scalatest actually is. The Github codebase is not the interface of ScalaTest. We very carefully designed a public interface that provides extension points, mainly the lifecycle methods of trait
You can see their signatures by looking at SuiteMixin. These lifecycle are public and designed for overriding by users. ScalaTest uses these itself to enable different testing styles and to alter how tests are run when you mix in Moreover, as I mentioned earlier, ScalaTest does not force you to use Now, it sounds like you (@pshirshov) want to do discovery in a custom manner. That's interesting. I have not heard that from a user before, and I'm curious what your use case is. You are correct that ScalaTest does not offer an extension point for discovery. I'd be very interested in hearing what your underlying need and goal is. Lastly, the https://twitter.com/search?q=dorunrunrundadorunrun%20scalatest&src=typd Testing can a bit tedious, so I figured why not lighten it up a bit here and there? To summarize, Scalatest is not primarily a BDD framework as it was portrayed earlier in this issue. It is primarily about extensibility. The intent is to address the reality that different people want and need different things. You can see that reality in just the short discussion here. The handful of people in this discussion want different things. The ScalaTest approach is not to try and shoehorn everyone into one way, but make it easy for different people and teams to mix together a few traits to get what they are after. |
Actually I didn't realise I talk with the author. So let me try to explain why I'm so frustrated. Also I should say that public interface of ScalaTest is fine. And the But ScalaTest is not flexible enough and private code is very complicated and hard to read/patch/maintain. The code we test has high variance and a lot of configurable aspects (for example we may wish to run our business logic against postgres repositories or in-memory repositories, real payment providers or mocks etc) We have a big integration test suite. The following things are essential for us:
We have our own DI framework (distage) which plans the job first, then executes. It allows us to trace which components are required (reachable), we call it "garbage collection". So, essentially our tests work the following way:
This is very convenient but this is not efficient then you have even hundreds, not thousands, of integration tests. So, we decided to reuse some heavyweight/stateful components which may be easily shared, like thread pools, database drivers, etc. Now the process should look this way:
You may find an example of our application entrypoint which executes a very similar flow here and in the code around. Also you may find our test logic here and around. Maybe I'm stupid but I don't see any way to fit this flow into Right now we are using ScalaTest but our implementation is flawed because:
The other reason why we need custom discovery is that we don't always want to scan classpath. The delays may also be annoying and in many cases it may be easier to register all the suites manually.
Scalatest is great and thank you for it. I'm using it for years (ten years, I guess?..) But there are a lot of things which may be significantly improved. |
@pshirshov That's a very interesting use case. We should move this to scalatest-users, probably, but in short I would hope you could implement one subclass of Given your process:
ScalaTest does prefer if a Suite can give an accurate count of how many tests are expected once it is constructed, which is a bit of an impedance mismatch for your use case I think. For you to do give an accurate test count, you'd need to do 1 to 6 before Then when Anyway, I'm not sure I fully understand your use case, but we should take this to scalatest-users if you want to discuss further. I would like to. It is an interesting use case that sounds like the kind of thing that ScalaTest's extensibility points were intended to support. |
Here are the some of the highlights I picked out related to the zero-dependency, minimalist line of discussion. Target audience should be core library authors
use uTest or Minitest as a starting point
power assertions?
Scala.js
nameFor the name, what do you think about resurrecting the name SUnit? next stepI think we can take these as general direction, and maybe move to another forum to focus on technical details. (Maybe a new repo + GitHub issues?) |
I think we should stay as far away from the word |
I think we should remove the |
On power assertions, I would think we'd want to avoid macros to keep it simple, just have plain old assertions, at least until only Scala 3 is supported. Until then you'll need to write it twice, once for Scala 2 and again for Scala 3. What build tool or tools do all the compiler projects use when they want to run their tests? Do they use sbt, maven, something else? |
Yea. This is something that can be omitted in v1.
As far as I know, the compiler and Scala modules all use sbt. So integration with sbt/test-interface is a must. So it's one-dependency, not zero-. |
@eed3si9n Ok, good. sbt makes it simpler, actually, especially if that's the only build tool that this would need to work with. |
|
I believe testz follows every single requirement mentioned thus far. From what I remember it's smaller than minitest or uTest, zero dependency, and fully inverts control. You can copy the core of it if you don't want the scalaz association; you should have enough to get up and running in less than 300 lines. |
I'm still around with my issues. Had a nice chat with @bvenners, though seems like I cannot fit my needs into scalatest properly. Seems like I would have to write my own tool anyway and in case you interested I may try to make a lightweight prototype which will work for my needs but will not depend on our workflows. |
I think we should acknowledge that this initiative has stalled. the reasons for that include:
the Scala team is not currently motivated to really drive this forward, so we're closing this ticket in our own tracker |
Authors: @dwijnand, @eed3si9n
This proposal seeks to work with the community, particularly the authors and maintainers of testing libraries, in order to introduce a basic, zero-dependency unit testing library to the Scala project. Such a library would live in the scala/scala repo and be a part of the Scala distribution, sharing the same Maven organisation id (
org.scala-lang
), version, release cadence, and bidirection binary compatibility as the rest of the Scala distribution.This library would, therefore, be able to be used in projects that currently must fallback on using JUnit, such as the Scala project itself and the Scala.js project.
Additionally, we hope that by working with the community we may find a common path forward to overcome some of the fragmentation in testing styles and libraries that is currently present in the Scala community.
Background
When Scala started gaining popularity there was a heavy emphasis on its extensibility via DSL. As such, it attracted a cultural import from other language communities, including the notion of behavior-driven development (BDD). The two most used Scala test frameworks today, ScalaTest and specs2, were created under this trend creating English-like DSLs.
As another cultural import, the notion of property-based testing has been gaining traction over the years, with ScalaCheck as the front runner. Most projects that use ScalaCheck use it through ScalaTest or specs2, possibly via Discipline, with a small number of projects using ScalaCheck alone or ScalaCheck with Claimant. There are also scalaprops and Hedgehog which are alternatives to ScalaCheck, but not as popular.
In recent years, Scala has started to shed some of these older influences and begun to form its own culture, and there's been a swing-back movement towards minimalistic unit test frameworks that do not employ "should"-DSLs. This demand is partly due to our diverse need to cross-build across multiple Scala versions, as well as
multiple platforms (JVM, Scala.JS, and Scala Native). Some projects (particularly the Scala, Scala.js and Scala Native projects themselves) use JUnit for this purpose. uTest and Minitest, as well as the revival of Expecty for power asserts, are examples from this post-BDD, neo-unit-test era, however these aren't as popular.
Conform how Scala code is unit tested
One of the hopes of this proposal is that a standard unit testing library would conform how Scala code is tested, both in documentation (on the official docs, books, blogs/articles/forums/websites) and in projects.
Where books (or other forms of eduction) teaching Scala previously had to either (a) choose which testing library to document and/or use, (b) document and/or use multiple options, or maybe (c) chosen to avoid the topic, with this proposal they could choose to document and/or use the official unit testing library, and perhaps just mention the other options in the ecosystem.
Also, projects that previously had limited choices (e.g. only JUnit) would be able to use the same library the rest of the ecosystem uses.
Provide a unit testing library to zero dependency libraries and for every Scala version
When bootstrapping the Scala ecosystem there is a problem of inter-dependencies between core Scala projects and testing libraries, with ScalaCheck requiring Scala.js and Scala Native, and ScalaTest and specs2 requring ScalaCheck. This puts a few projects into the critical path (the ecosystem depends on the release of Scala.js, Scala Native, and ScalaCheck before it can get its first, non-JUnit, unit testing library) and in awkward positions (for example scala-xml cannot test itself with ScalaTest because its a dependency of ScalaTest). So to avoid cyclical dependencies when bootstrapping some projects choose to use JUnit.
But the use of JUnit for unit testing isn't free, as both Scala.js and Scala Native had to provide special support for it in order for it to actually work, a support that will need maintaince as JUnit continues to evolve.
Some libraries, often to avoid problems when bootstrapping, choose to (or must) have zero dependencies, and that means that they either choose to use JUnit or they create their own testing library, such as the Scala, Scala.js and Scala Native projects using JUnit, which isn't what most of the rest of the ecosystem uses.
The inter-dependency between Scala projects also means that some may consider every non-release, published version of Scala for upcoming major versions to be effectively unusable as there are no non-JUnit unit testing libraries available for it. Specifically this includes every artifact publish from branch builds and pull
request validation. This is why the Scala, Scala.js, and Scala Native (and Dotty) projects use JUnit (and partest) for their testing needs.
Remove any remaining blockers for not testing Scala code
The availability of such a library would also remove any remaining blockers a user might have for not testing their Scala code. These include:
Design Decisions
Here are a few design decisions that I think should discussed and actioned:
scala.testing.UnitTest
andscala.testing.SUnit
, a short history:scala.testing.UnitTest
(initiallyscala.UnitTest
) scala/scala@f6ca275scala.testing.SUnit
scala/scala@6c23d94#[cfg(test)]
Participants
I propose we involve the creators and maintainers of existing testing libraries in the community, so we can get buy-in and consensus on this proposal and the design decisions in them. So a working group.
Lead: Eugene Yokota (Scala Team, sbt maintainer, and maintainer of the revived Expecty)
Here are the people I think it would be lovely if they were able to participate:
The text was updated successfully, but these errors were encountered: