Summary
The ScalaTest Shell is a new DSL coming in ScalaTest 1.5 that aims to make ScalaTest easier to use from the Scala interpreter. This post gives preview.
Advertisement
ScalaTest, like Scala, is designed to grow with the demands of its users. The main way that ScalaTest does that is by defining clearly specified extension points, such as the lifecycle methods in trait Suite: run, runNestedSuites, nestedSuites, runTests, testNames, tags, runTest, and withFixture. These methods are designed to be overridden by users to allow them to more easily build custom solutions for their own specific needs.
But Scala is "scalable" not only by allowing its users to morph it into a language suitable for specific tasks, but also because it is useful for small tasks as well as large ones. One area in which I had always wanted to make ScalaTest more useful was inside the Scala interpreter (also called "the REPL," for Read-Evaluate-Print-Loop), a tool used primarily to do "small tasks." In the early days of ScalaTest, I wasn't sure how people would want to use a test framework in the Scala interpreter, so I did nothing. I then waited to see what users tried to do with ScalaTest in the Scala interpreter, and what they said they wanted.
I finally got a spark of inspiration a few months ago from a post to the JUnit mailing list by Joakim Ohlrogge. Joakim had done some experimenting to see how he could make it easier to use JUnit from the Scala interpreter, which he released as poju4s.
This user feedback pointed me in a design direction, and the ScalaTest Shell was born.
The main command of the ScalaTest shell is run, which you can use to run a suite of tests.
The shell also provides several commands for configuring a call to run:
color - display results in color (green for success; red for failure; yellow for warning; blue for statistics)
nocolor - display results without color
durations - display durations of (i.e., how long it took to run) tests and suites
nodurations - do not display durations of tests and suites
shortstacks - display short (i.e., truncated to show just the most useful portion) stack traces for all exceptions
fullstacks - display full stack trackes for all exceptions
nostacks - display no stack trace for StackDepth exceptions and a short stack trace for non-StackDepth
exceptions
stats - display statistics before and after the run, such as expected test count before the run and tests succeeded, failed, pending,
etc., counts after the run
nostats do not display statistics before or after the run
The default configuration is color, nodurations, nostacks, and nostats.
All of these commands are methods of class org.scalatest.Shell. Each configuration command is a method that produces
another Shell instance with every configuration parameter
the same except for the one you've asked to change. For example, when you invoke durations, you'll get back a
Shell instance that has every parameter configured the same way, except with durations enabled. When you invoke
run on that, you get a run with durations enabled and every other configuration parameter at its default value.
Two other useful "commands"
to know about, though not technically part of the shell, are the apply factory methods in the Suites and Specs
singleton objects. These allow you to easily create composite suites out of nested suites, which you can then pass to run. This
will be demonstrated later in this documentation.
Using the ScalaTest shell
The package object of the org.scalatest package extends Shell with all its parameters set to their default values. A
good way to use the ScalaTest shell, therefore, is to import the members of package org.scalatest:
One thing importing org.scalatest._ allows you to do is access any of ScalaTest's classes and traits by shorter
names, for example:
scala> class ArithmeticSuite extends FunSuite with matchers.ShouldMatchers {
| test("addition works") {
| 1 + 1 should equal (2)
| }
| ignore("subtraction works") {
| 1 - 1 should equal (0)
| }
| test("multiplication works") {
| 1 * 1 should equal (2)
| }
| test("division works") (pending)
| }
defined class ArithmeticSuite
But importing org.scalatest._ also brings into scope the commands of the Shell, so you can, for
example, invoke run without qualification:
scala> run(new ArithmeticSuite)ArithmeticSuite:
- addition works- subtraction works !!! IGNORED !!!- multiplication works *** FAILED ***
1 did not equal 2 (<console>:16)- division works (pending)
Configuring a single run
To configure a single run, you can prefix run by one or more configuration commands, separated by dots. For example, to enable
durations during a single run, you would write:
scala> durations.run(new ArithmeticSuite)ArithmeticSuite:
- addition works (102 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (36 milliseconds)
1 did not equal 2 (<console>:16)- division works (pending)
To enable statistics during a single run, you would write:
scala> stats.run(new ArithmeticSuite)Run starting. Expected test count is: 3ArithmeticSuite:
- addition works- subtraction works !!! IGNORED !!!- multiplication works *** FAILED ***
1 did not equal 2 (<console>:16)- division works (pending)Run completed in 386 milliseconds.
Total number of tests run: 2
Suites: completed 1, aborted 0
Tests: succeeded 1, failed 1, ignored 1, pending 1*** 1 TEST FAILED ***
And to enable both durations and statistics during a single run, you could write:
scala> durations.stats.run(new ArithmeticSuite)Run starting. Expected test count is: 3ArithmeticSuite:
- addition works (102 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED (36 milliseconds)***
1 did not equal 2 (<console>:16)- division works (pending)Run completed in 386 milliseconds.
Total number of tests run: 2
Suites: completed 1, aborted 0
Tests: succeeded 1, failed 1, ignored 1, pending 1*** 1 TEST FAILED ***
The order doesn't matter when you are chaining multiple configuration commands. You'll get the same
result whether you write durations.stats.run or stats.durations.run.
To disable color, use nocolor:
scala> nocolor.run(new ArithmeticSuite)
ArithmeticSuite:
- addition works
- subtraction works !!! IGNORED !!!
- multiplication works *** FAILED ***
1 did not equal 2 (<console>:16)
- division works (pending)
To enable short stack traces during a single run, use shortstacks:
scala> shortstacks.run(new ArithmeticSuite)ArithmeticSuite:
- addition works (101 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (33 milliseconds)
1 did not equal 2 (<console>:16)
org.scalatest.TestFailedException:
...
at line2$object$$iw$$iw$$iw$$iw$ArithmeticSuite$$anonfun$3.apply$mcV$sp(<console>:16)
at line2$object$$iw$$iw$$iw$$iw$ArithmeticSuite$$anonfun$3.apply(<console>:16)
at line2$object$$iw$$iw$$iw$$iw$ArithmeticSuite$$anonfun$3.apply(<console>:16)
at org.scalatest.FunSuite$$anon$1.apply(FunSuite.scala:992)
at org.scalatest.Suite$class.withFixture(Suite.scala:1661)
at line2$object$$iw$$iw$$iw$$iw$ArithmeticSuite.withFixture(<console>:8)
at org.scalatest.FunSuite$class.invokeWithFixture$1(FunSuite.scala:989)
...- division works (pending)
Changing the default configuration
If you want to change the default for multiple runs, you can import the members of your favorite Shell configuration. For example,
if you always like to run with durations and statistics enabled, you could write:
Now anytime you simply call run, statistics and durations will be enabled:
scala> run(new ArithmeticSuite)Run starting. Expected test count is: 3ArithmeticSuite:
- addition works (9 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (10 milliseconds)
1 did not equal 2 (<console>:18)- division works (pending)Run completed in 56 milliseconds.
Total number of tests run: 2
Suites: completed 1, aborted 0
Tests: succeeded 1, failed 1, ignored 1, pending 1*** 1 TEST FAILED ***
Running multiple suites
If you want to run multiple suites, you can use the factory methods in either the Suites or
Specs
singleton objects. If you wrap a comma-separated list of suite instances inside Suites(...), for example,
you'll get a suite instance that contains no tests, but whose nested suites includes the suite instances you placed between
the parentheses. You can place Suites inside Suites to any level of depth, creating a tree of
suites to pass to run. Here's a (contrived) example in which ArithmeticSuite is executed four times:
scala> run(Suites(new ArithmeticSuite, new ArithmeticSuite, Suites(new ArithmeticSuite, new ArithmeticSuite)))Run starting. Expected test count is: 12Suites:
ArithmeticSuite:
- addition works (0 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (1 millisecond)
1 did not equal 2 (<console>:16)- division works (pending)ArithmeticSuite:
- addition works (1 millisecond)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (0 milliseconds)
1 did not equal 2 (<console>:16)- division works (pending)Suites:
ArithmeticSuite:
- addition works (0 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (0 milliseconds)
1 did not equal 2 (<console>:16)- division works (pending)ArithmeticSuite:
- addition works (0 milliseconds)- subtraction works !!! IGNORED !!!- multiplication works *** FAILED *** (0 milliseconds)
1 did not equal 2 (<console>:16)- division works (pending)Run completed in 144 milliseconds.
Total number of tests run: 8
Suites: completed 6, aborted 0
Tests: succeeded 4, failed 4, ignored 4, pending 4*** 4 TESTS FAILED ***
Running a single test
The run command also allows you to specify the name of a test to run and/or a config map. You can run
a particular test in a suite, for example, by specifying the test name after the suite instance in your call to run, like this:
scala> run(new ArithmeticSuite, "addition works")ArithmeticSuite:
- addition works
Give it a try
I released an updated ScalaTest-1.5-SNAPSHOT today that includes the new ScalaTest shell. The snapshot works with Scala 2.8. You can download the snapshot release via the scala-tools.org Maven repository with:
These enhancements will be released as part of ScalaTest 1.5 within the next few weeks. I'm posting this preview now because I want to get feedback in general on the API and find if there are any bugs to fix or any code-breakages. (I expect no source code to break with any of this enhancement, so let me know if you have a problem.) So please give it a try and either post feedback to the discussion forum for this blog post, or email the scalatest-users mailing list.
> This looks very interesting. > > My first thought on seeing this post was that I want to be > able to use the ScalaTest Shell within Kojo > (http://www.kogics.net/sf:kojo). > > A little browsing through the ScalaTest source code > revealed the seemingly perfect method for me to override: >
> > Alas, StringReporter is > private[scalatest]. > > Any thoughts on the best way for me to proceed? > I need to take a look at kojo, but I suspect the answer to your question is most likely that you probably want to make a custom reporter. ScalaTest is designed to let users make custom Reporters that do custom things with the events fired during testing. StringReporter is an internal class I use to implement several of the built-in reporters. If it looks like something you'd like to use, what you can do is simply make a copy of it (it's open source, free as in free beer as well as free speech), make whatever changes you want to do for your application, then tell ScalaTest to use your Reporter class. Info is here:
There are typos in the Extensibility section of that page that I just now noticed and fixed in the trunk. There's nothing called ReportFunction. I was thinking of calling it that at one point I believe. The apply method is abstract in Reporter, and that's all you need to implement. To tell the Runner to use a customer reporter, you use -r, as explained in the "Specifying reporters" section on this page:
> StringReporter is an internal > class I use to implement several of the built-in > reporters. If it looks like something you'd like to use, > what you can do is simply make a copy of it
That's the first thing I tried, but after running into dependencies on other private code within that package, I figured I'd just ask you before moving any further. I'll now go ahead and do what you're suggesting.
> Can you tell me more about how you'd imagine using > ScalaTest in kojo?
- Kids would use it to write simple tests for their code. This would be especially useful for Math related code (e.g. - a function to determine the prime factors of a number, and a ScalaTest test for that) - Programmers using Kojo as a REPL would have a quick and easy way to write tests for their experimental code. These tests could then easily migrate to the production version of their code.
I'm very new to ScalaTest, but based on your post, I imagine that people (in both the above scenarios) would just write a Suite, and then run it with the run method (like you described).
I will need to tell the ScalaTest shell to use my custom reporter. Can I do this by putting something into the config map when I call the shell run method?
> > StringReporter is an internal > > class I use to implement several of the built-in > > reporters. If it looks like something you'd like to > use, > > what you can do is simply make a copy of it > > That's the first thing I tried, but after running into > dependencies on other private code within that package, I > figured I'd just ask you before moving any further. I'll > now go ahead and do what you're suggesting. > > > Can you tell me more about how you'd imagine using > > ScalaTest in kojo? > > - Kids would use it to write simple tests for their code. > This would be especially useful for Math related code > (e.g. - a function to determine the prime factors of a > number, and a ScalaTest test for that) > - Programmers using Kojo as a REPL would have a quick and > easy way to write tests for their experimental code. These > tests could then easily migrate to the production version > of their code. > Sounds cool. I'll download kojo this week and try it.
> I'm very new to ScalaTest, but based on your post, I > imagine that people (in both the above scenarios) would > just write a Suite, and then run it with the > run method (like you described). > A Suite or maybe a FunSuite might be good for starters.
> > To tell the Runner to use a customer reporter, > > you use -r, as explained in the "Specifying reporters" > > section on this page: > > > http://www.artima.com/docs-scalatest-1.5-SNAPSHOT-5-May-201 > 1/#org.scalatest.tools.Runner$ > > I will need to tell the ScalaTest shell to use my custom > reporter. Can I do this by putting something into the > config map when I call the shell run method? > The Shell doesn't currently allow you to specify a different Reporter, because Suite.execute doesn't (simply because I didn't imagine someone would want to do that). But that wouldn't be difficult to add at all. Can you give me more details about what you really need to do in your custom reporter?
To run this code within Kojo: - Download the latest version of Kojo (Version 080511-1 or later), install, and launch Kojo. - Go into Help->About, and locate your Userdir (this shows up right at the bottom of the dialog) - Create a dir called libk under your Userdir, and copy the ScalaTest jar in there. - Restart Kojo - Paste the code above into the Kojo Script Editor. - Run the code, to see raw ScalaTest output in the Kojo Output Window.
As you can see in the code above, I have plugged into the FunSuite to get ScalaTest to use my reporter. Telling the Shell to use the custom reporter would, of course, be a much better solution.
> To run this code within Kojo: > - Download the latest version of Kojo (Version 080511-1 or > later), install, and launch Kojo. > - Go into Help->About, and locate your Userdir (this shows > up right at the bottom of the dialog) > - Create a dir called libk under your Userdir, and copy > the ScalaTest jar in there. > - Restart Kojo > - Paste the code above into the Kojo Script Editor. > - Run the code, to see raw ScalaTest output in the Kojo > Output Window. > > As you can see in the code above, I have plugged into the > FunSuite to get ScalaTest to use my reporter. Telling the > Shell to use the custom reporter would, of course, be a > much better solution. > I tried this. Kojo is really nice. It looks like what you'd like to do is call your own println and possibly put an extra dashed line. That's definitely the realm of a custom reporter. I'll add that to execute and the Shell prior to the 1.5 release. Have a guest this weekend so can't do it today, but I'll try and release that as a snapshot Monday if possible.
Also, do you have an output stream handy as well? I see you have your own println, but is there an output stream or a reader you could pass into a reporter? What might make things easier for people in your kind of situation is an OutputStreamReporter or WriterReporter that takes an output stream or Writer and then just prints StringReporter's output to that stream or writer. If so, would you prefer an OutputStream or a Writer? I think a Writer is probably better if you have no preference.
> It looks like what you'd like to do is call your own > println
Right. I'll maybe have another version of println where you can specify a text color, to allow for green/red etc test output.
> custom reporter. I'll add that to execute and the Shell prior to the 1.5 release.
That sounds great.
> If so, would you prefer an OutputStream or a Writer?
Yes, I have a Writer in there. A WriterReporter would be great, although I'm not sure how we would handle colors with that (the output would probably come in interspersed with the color information)?
> > It looks like what you'd like to do is call your own > > println > > Right. I'll maybe have another version of println where > you can specify a text color, to allow for green/red etc > test output. > > > custom reporter. I'll add that to execute and the Shell > prior to the 1.5 release. > > That sounds great. > > > If so, would you prefer an OutputStream or a Writer? > > Yes, I have a Writer in there. > A WriterReporter would be great, although I'm not sure how > we would handle colors with that (the output would > probably come in interspersed with the color > information)? > Hi Lalit,
I've been thinking about this. One way that's possible is to define a KojoShell that is identical to org.scalatest.Shell, except instead of calling execute on Suite, which writes to the standard output, it calls run directly. Alternatively I could add a PrintWriter parameter to execute whose default value wraps java.io.System.out. The trouble is that they'd have to do a second import.
import org.scalatest._ import kojoshell._
Another way I considered is to have run take an implicit parameter that holds onto a PrintWriter, but in that case I'd have to define that implicit parameter in the org.scalatest package object, and they'd have to do another import again. I also would really like to not have an implicit.
Another way is to define a krun command or something, but that's ugly. And it would again require another import, unless you included ScalaTest as part of Kojo. If you think that might be possible, there are lots of ways to do it. Is that something you would consider?
> unless you included ScalaTest as part of Kojo. If you think that might be possible, there are lots of ways to do it. Is that something you would consider?
I almost have to, to make this work out of the box at schools. The problem, if I can call it that, is ScalaTest's size (~2.5 Megs). Bundling it would result in a ~20% increase in Kojo's size. I'm hoping that I can bundle in a stripped down version of ScalaTest, with the possibility that those who know what they are doing - can download the full version and plop it into the libk dir to get full ScalaTest functionality.
Is ScalaTest amenable to this kind of splitting (into a core portion, and the bells and whistles)?
In terms of how this might work out, I really like your WriterReporter idea. So if:
- Suite.execute (and Shell.run) took a Reporter (which could default to a StandardOutputReporter) - and we had a WriterReporter available within ScalaTest
...Kojo could just call Shell.run with a Writer (pointing to its output window) inside a WriterReporter.
Would that work from a ScalaTest perspective?
I also checked and saw that the Kojo Output Window, which is a Netbeans Platform Output Window, is able to handle ansi color codes - so the Writer idea also works fine with colors.
> > unless you included ScalaTest as part of Kojo. If you > think that might be possible, there are lots of ways to do > it. Is that something you would consider? > > I almost have to, to make this work out of the box at > schools. The problem, if I can call it that, is > ScalaTest's size (~2.5 Megs). Bundling it would result in > a ~20% increase in Kojo's size. I'm hoping that I can > bundle in a stripped down version of ScalaTest, with the > possibility that those who know what they are doing - can > download the full version and plop it into the libk dir to > get full ScalaTest functionality. > > Is ScalaTest amenable to this kind of splitting (into a > core portion, and the bells and whistles)? > Yes. What I think would make sense is to pick and chose what you think belongs in Kojo, fork ScalaTest, get rid of everything else, and then probably make a KojoSuite trait that mixes in everything you think they should have. For example if you define KojoSuite like this:
trait KojoSuite extends FunSuite with BeforeAndAfterEachFunctions
One thing Dick Wall and I do in our Scala courses is mix in a trait that stops the run after the first test fails. Dick calls these Koans after a similar concept in the Ruby community. We basically pass out problems in the form of tests that fail one at a time, leading the students through the exercises. Everybody seems to really like that. Not sure if you're providing a learning Scala path in there, but that might be something to think about as well.
> In terms of how this might work out, I really like your > WriterReporter idea. So if: > > - Suite.execute (and Shell.run) took a Reporter (which > could default to a StandardOutputReporter) > - and we had a WriterReporter available within ScalaTest > > ...Kojo could just call Shell.run with a Writer (pointing > to its output window) inside a WriterReporter. > > Would that work from a ScalaTest perspective? > > I also checked and saw that the Kojo Output Window, which > is a Netbeans Platform Output Window, is able to handle > ansi color codes - so the Writer idea also works fine with > colors. > That's good news on color. I'm wondering if you do anything with setting the Console output stream at this point. That's probably one way to get the existing snapshot's Shell output right into your console window. If not, is there a reason you couldn't do that?
> I'm wondering if you do anything with setting the Console output stream at this point. That's probably one way to get the existing snapshot's Shell output right into your console window.
I tried redirecting stdout, and it works pretty well.
I have just put out a new version of Kojo (which can be downloaded from http://www.kogics.net/sf:kojo-download) which supports ScalaTest based on this approach.
There's a story within Kojo (available via the Stories -> Tools -> Setup ScalaTest menuitem) that guides users through the process of setting up ScalaTest for Kojo (this involves downloading ScalaTest, putting it in the right location for Kojo, and then defining some helper functions).
After ScalaTest is setup for use within Kojo, you can start writing simple tests like this:
def sum(n1: Int, n2: Int) = {
n1 + n2
}
test("sum of positives") {
sum(1,1) should equal(2)
}
// ignore test for now
notest("sum of negatives") {
sum(-1,-1) should equal(-2)
}
You can also, of course, do all of the things that you would do with ScalaTest in an IDE environment.