org.scalatest.Suite.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.8.0 Show documentation
/* * Copyright 2001-2008 Artima, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.scalatest import java.awt.AWTError import java.lang.annotation._ import java.io.Serializable import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.lang.reflect.Modifier import java.nio.charset.CoderMalfunctionError import javax.xml.parsers.FactoryConfigurationError import javax.xml.transform.TransformerFactoryConfigurationError import Suite.simpleNameForTest import Suite.parseSimpleName import Suite.stripDollars import Suite.formatterForSuiteStarting import Suite.formatterForSuiteCompleted import Suite.formatterForSuiteAborted import Suite.anErrorThatShouldCauseAnAbort import Suite.getSimpleNameOfAnObjectsClass import scala.collection.immutable.TreeSet import org.scalatest.events._ import org.scalatest.tools.StandardOutReporter /** * A suite of tests. A* will be run. However, ifSuiteinstance encapsulates a conceptual * suite (i.e., a collection) of tests. * ** This trait provides an interface that allows suites of tests to be run. * Its implementation enables a default way of writing and executing tests. Subtraits and subclasses can * override
* *Suite's methods to enable other ways of writing and executing tests. * This trait's default approach allows tests to be defined as methods whose name starts with "test." * This approach is easy to understand, and a good way for Scala beginners to start writing tests. * More advanced Scala programmers may prefer to mix together otherSuitesubtraits defined in ScalaTest, * or create their own, to write tests in the way they feel makes them most productive. Here's a quick overview * of some of the options to help you get started: ** For JUnit 3 users *
* ** If you are using JUnit 3 (version 3.8 or earlier releases) and you want to write JUnit 3 tests in Scala, look at *
* *AssertionsForJUnit, *ShouldMatchersForJUnit, and *JUnit3Suite. ** For JUnit 4 users *
* ** If you are using JUnit 4 and you want to write JUnit 4 tests in Scala, look at *
* *JUnitSuite, and *JUnitRunner. WithJUnitRunner, * you can use any of the traits described here and still run your tests with JUnit 4. ** For TestNG users *
* ** If you are using TestNG and you want to write TestNG tests in Scala, look at *
* *TestNGSuite. ** For high-level testing *
* ** If you want to write tests at a higher level than unit tests, such as integration tests, acceptance tests, * or functional tests, check out
* *FeatureSpec. ** For unit testing *
* ** If you prefer a behavior-driven development (BDD) style, in which tests are combined with text that * specifies the behavior being tested, look at *
* *Spec, *FlatSpec, and *WordSpec. Otherwise, if you just want to write tests * and don't want to combine testing with specifying, look at *FunSuiteor read on to learn how to write * tests using this base trait,Suite. ** To use this trait's approach to writing tests, simply create classes that * extend
* *Suiteand define test methods. Test methods have names of the formtestX, * whereXis some unique, hopefully meaningful, string. A test method must be public and * can have any result type, but the most common result type isUnit. Here's an example: ** import org.scalatest.Suite * * class MySuite extends Suite { * * def testAddition() { * val sum = 1 + 1 * assert(sum === 2) * assert(sum + 2 === 4) * } * * def testSubtraction() { * val diff = 4 - 1 * assert(diff === 3) * assert(diff - 2 === 1) * } * } ** ** You can run a
* *Suiteby invoking on it one of four overloadedexecute* methods. These methods, which print test results to the * standard output, are intended to serve as a * convenient way to run tests from within the Scala interpreter. For example, * to runMySuitefrom within the Scala interpreter, you could write: ** scala> (new MySuite).execute() ** ** And you would see: *
* ** Test Starting - MySuite: testAddition * Test Succeeded - MySuite: testAddition * Test Starting - MySuite: testSubtraction * Test Succeeded - MySuite: testSubtraction ** ** Or, to run just the
* *testAdditionmethod, you could write: ** scala> (new MySuite).execute("testAddition") ** ** And you would see: *
* ** Test Starting - MySuite: testAddition * Test Succeeded - MySuite: testAddition ** ** Two other
* *executemethods that are intended to be run from the interpreter accept a "config" map of key-value * pairs (see Config map, below). Each of theseexecutemethods invokes arunmethod takes seven * parameters. Thisrunmethod, which actually executes the suite, will usually be invoked by a test runner, such * asorg.scalatest.tools.Runneror an IDE. See the documentation * forRunnerfor more detail. *Assertions and ===
* ** Inside test methods in a
* *Suite, you can write assertions by invokingassertand passing in aBooleanexpression, * such as: ** val left = 2 * val right = 1 * assert(left == right) ** ** If the passed expression is
* *true,assertwill return normally. Iffalse, *assertwill complete abruptly with aTestFailedException. This exception is usually not caught * by the test method, which means the test method itself will complete abruptly by throwing theTestFailedException. Any * test method that completes abruptly with aTestFailedExceptionor anyExceptionis considered a failed * test. A test method that returns normally is considered a successful test. ** If you pass a
* *Booleanexpression toassert, a failed assertion will be reported, but without * reporting the left and right values. You can alternatively encode these values in aStringpassed as * a second argument toassert, as in: ** val left = 2 * val right = 1 * assert(left == right, left + " did not equal " + right) ** ** Using this form of
* *assert, the failure report will include the left and right values, thereby * helping you debug the problem. However, ScalaTest provides the===operator to make this easier. * (The===operator is defined in traitAssertionswhich traitSuiteextends.) * You use it like this: ** val left = 2 * val right = 1 * assert(left === right) ** ** Because you use
* *===here instead of==, the failure report will include the left * and right values. For example, the detail message in the thrownTestFailedExceptionfrom theassert* shown previously will include, "2 did not equal 1". * From this message you will know that the operand on the left had the value 2, and the operand on the right had the value 1. ** If you're familiar with JUnit, you would use
* *===* in a ScalaTestSuitewhere you'd useassertEqualsin a JUnitTestCase. * The===operator is made possible by an implicit conversion fromAny* toEqualizer. If you're curious to understand the mechanics, see the documentation for *Equalizerand theconvertToEqualizermethod. *Expected results
* * Although===provides a natural, readable extension to Scala'sassertmechanism, * as the operands become lengthy, the code becomes less readable. In addition, the===comparison * doesn't distinguish between actual and expected values. The operands are just calledleftandright, * because if one were namedexpectedand the otheractual, it would be difficult for people to * remember which was which. To help with these limitations of assertions,Suiteincludes a method calledexpectthat * can be used as an alternative toassertwith===. To useexpect, you place * the expected value in parentheses afterexpect, followed by curly braces containing code * that should result in the expected value. For example: * ** val a = 5 * val b = 2 * expect(2) { * a - b * } ** ** In this case, the expected value is
* *2, and the code being tested isa - b. This expectation will fail, and * the detail message in theTestFailedExceptionwill read, "Expected 2, but got 3." *Intercepted exceptions
* ** Sometimes you need to test whether a method throws an expected exception under certain circumstances, such * as when invalid arguments are passed to the method. You can do this in the JUnit style, like this: *
* ** val s = "hi" * try { * s.charAt(-1) * fail() * } * catch { * case _: IndexOutOfBoundsException => // Expected, so continue * } ** ** If
* *charAtthrowsIndexOutOfBoundsExceptionas expected, control will transfer * to the catch case, which does nothing. If, however,charAtfails to throw an exception, * the next statement,fail(), will be executed. Thefailmethod always completes abruptly with * aTestFailedException, thereby signaling a failed test. ** To make this common use case easier to express and read, ScalaTest provides an
* *intercept* method. You use it like this: ** val s = "hi" * intercept[IndexOutOfBoundsException] { * s.charAt(-1) * } ** ** This code behaves much like the previous example. If
* *charAtthrows an instance ofIndexOutOfBoundsException, *interceptwill return that exception. But ifcharAtcompletes normally, or throws a different * exception,interceptwill complete abruptly with aTestFailedException. Theinterceptmethod returns the * caught exception so that you can inspect it further if you wish, for example, to ensure that data contained inside * the exception has the expected values. Here's an example: ** val s = "hi" * val caught = * intercept[IndexOutOfBoundsException] { * s.charAt(-1) * } * assert(caught.getMessage === "String index out of range: -1") ** *Using other assertions
* ** ScalaTest also supports another style of assertions via its matchers DSL. By mixing in * trait
* *ShouldMatchers, you can * write suites that look like: ** import org.scalatest.Suite * import org.scalatest.matchers.ShouldMatchers * * class MySuite extends Suite with ShouldMatchers { * * def testAddition() { * val sum = 1 + 1 * sum should equal (2) * sum + 2 should equal (4) * } * * def testSubtraction() { * val diff = 4 - 1 * diff should equal (3) * diff - 2 should equal (1) * } * } ** *If you prefer the word "
* *must" to the word "should," you can alternatively mix in * traitMustMatchers. ** If you are comfortable with assertion mechanisms from other test frameworks, chances * are you can use them with ScalaTest. Any assertion mechanism that indicates a failure with an exception * can be used as is with ScalaTest. For example, to use the
* *assertEquals* methods provided by JUnit or TestNG, simply import them and use them. (You will of course need * to include the relevant JAR file for the framework whose assertions you want to use on either the * classpath or runpath when you run your tests.) Here's an example in which JUnit's assertions are * imported, then used within a ScalaTest suite: ** import org.scalatest.Suite * import org.junit.Assert._ * * class MySuite extends Suite { * * def testAddition() { * val sum = 1 + 1 * assertEquals(2, sum) * assertEquals(4, sum + 2) * } * * def testSubtraction() { * val diff = 4 - 1 * assertEquals(3, diff) * assertEquals(1, diff - 2) * } * } ** *Nested suites
* ** A
* *Suitecan refer to a collection of otherSuites, * which are called nestedSuites. Those nestedSuites can in turn have * their own nestedSuites, and so on. Large test suites can be organized, therefore, as a tree of * nestedSuites. * This trait'srunmethod, in addition to invoking its * test methods, invokesrunon each of its nestedSuites. ** A
* *Listof aSuite's nestedSuites can be obtained by invoking its *nestedSuitesmethod. If you wish to create aSuitethat serves as a * container for nestedSuites, whether or not it has test methods of its own, simply overridenestedSuites* to return aListof the nestedSuites. Because this is a common use case, ScalaTest provides * a convenienceSuperSuiteclass, which takes aListof nestedSuites as a constructor * parameter. Here's an example: ** import org.scalatest.Suite * * class ASuite extends Suite * class BSuite extends Suite * class CSuite extends Suite * * class AlphabetSuite extends SuperSuite( * List( * new ASuite, * new BSuite, * new CSuite * ) * ) ** ** If you now run
* *AlphabetSuite, for example from the interpreter: ** scala> (new AlphabetSuite).run() ** ** You will see reports printed to the standard output that indicate nested * suites—
* *ASuite,BSuite, and *CSuite—were run. ** Note that
* *Runnercan discoverSuites automatically, so you need not * necessarily specifySuperSuites explicitly. See the documentation * forRunnerfor more information. *Shared fixtures
* ** A test fixture is objects or other artifacts (such as files, sockets, database * connections, etc.) used by tests to do their work. * If a fixture is used by only one test method, then the definitions of the fixture objects can * be local to the method, such as the objects assigned to
* *sumanddiffin the * previousMySuiteexamples. If multiple methods need to share an immutable fixture, one approach * is to assign them to instance variables. Here's a (very contrived) example, in which the object assigned * tosharedis used by multiple test methods: ** import org.scalatest.Suite * * class MySuite extends Suite { * * // Sharing immutable fixture objects via instance variables * val shared = 5 * * def testAddition() { * val sum = 2 + 3 * assert(sum === shared) * } * * def testSubtraction() { * val diff = 7 - 2 * assert(diff === shared) * } * } ** ** In some cases, however, shared mutable fixture objects may be changed by test methods such that * they need to be recreated or reinitialized before each test. Shared resources such * as files or database connections may also need to * be created and initialized before, and cleaned up after, each test. JUnit 3 offers methods
* *setUpand *tearDownfor this purpose. In ScalaTest, you can use theBeforeAndAfterEachtrait, * which will be described later, to implement an approach similar to JUnit'ssetUp* andtearDown, however, this approach usually involves reassigningvars * between tests. Before going that route, you may wish to consider some approaches that * avoidvars. One approach is to write one or more create-fixture methods * that return a new instance of a needed object (or a tuple or case class holding new instances of * multiple objects) each time it is called. You can then call a create-fixture method at the beginning of each * test method that needs the fixture, storing the fixture object or objects in local variables. Here's an example: ** import org.scalatest.Suite * import scala.collection.mutable.ListBuffer * * class MySuite extends Suite { * * // create objects needed by tests and return as a tuple * def createFixture = ( * new StringBuilder("ScalaTest is "), * new ListBuffer[String] * ) * * def testEasy() { * val (builder, lbuf) = createFixture * builder.append("easy!") * assert(builder.toString === "ScalaTest is easy!") * assert(lbuf.isEmpty) * lbuf += "sweet" * } * * def testFun() { * val (builder, lbuf) = createFixture * builder.append("fun!") * assert(builder.toString === "ScalaTest is fun!") * assert(lbuf.isEmpty) * } * } ** ** If different tests in the same
* *Suiterequire different fixtures, you can create multiple create-fixture methods and * call the method (or methods) needed by each test at the begining of the test. If every test method requires the same set of * mutable fixture objects, one other approach you can take is make them simplyvals and mix in trait *OneInstancePerTest. If you mix inOneInstancePerTest, each test * will be run in its own instance of theSuite, similar to the way JUnit tests are executed. ** Although the create-fixture and
* *OneInstancePerTestapproaches take care of setting up a fixture before each * test, they don't address the problem of cleaning up a fixture after the test completes. In this situation, * one option is to mix in theBeforeAndAfterEachtrait. *BeforeAndAfterEach'sbeforeEachmethod will be run before, and itsafterEach* method after, each test (like JUnit'ssetUpandtearDown* methods, respectively). * For example, you could create a temporary file before each test, and delete it afterwords, like this: ** import org.scalatest.Suite * import org.scalatest.BeforeAndAfterEach * import java.io.FileReader * import java.io.FileWriter * import java.io.File * * class MySuite extends Suite with BeforeAndAfterEach { * * private val FileName = "TempFile.txt" * private var reader: FileReader = _ * * // Set up the temp file needed by the test * override def beforeEach() { * val writer = new FileWriter(FileName) * try { * writer.write("Hello, test!") * } * finally { * writer.close() * } * * // Create the reader needed by the test * reader = new FileReader(FileName) * } * * // Close and delete the temp file * override def afterEach() { * reader.close() * val file = new File(FileName) * file.delete() * } * * def testReadingFromTheTempFile() { * var builder = new StringBuilder * var c = reader.read() * while (c != -1) { * builder.append(c.toChar) * c = reader.read() * } * assert(builder.toString === "Hello, test!") * } * * def testFirstCharOfTheTempFile() { * assert(reader.read() === 'H') * } * * def testWithoutAFixture() { * assert(1 + 1 === 2) * } * } ** ** In this example, the instance variable
* *readeris avar, so * it can be reinitialized between tests by thebeforeEachmethod. ** Although the
* *BeforeAndAfterEachapproach should be familiar to the users of most * test other frameworks, ScalaTest provides another alternative that also allows you to perform cleanup * after each test: overridingwithFixture(NoArgTest). * To execute each test,Suite's implementation of therunTestmethod wraps an invocation * of the appropriate test method in a no-arg function.runTestpasses that test function to thewithFixture(NoArgTest)* method, which is responsible for actually running the test by invoking the function.Suite's * implementation ofwithFixture(NoArgTest)simply invokes the function, like this: ** // Default implementation * protected def withFixture(test: NoArgTest) { * test() * } ** ** The
* *withFixture(NoArgTest)method exists so that you can override it and set a fixture up before, and clean it up after, each test. * Thus, the previous temp file example could also be implemented without mixing inBeforeAndAfterEach, like this: ** import org.scalatest.Suite * import java.io.FileReader * import java.io.FileWriter * import java.io.File * * class MySuite extends Suite { * * private var reader: FileReader = _ * * override def withFixture(test: NoArgTest) { * * val FileName = "TempFile.txt" * * // Set up the temp file needed by the test * val writer = new FileWriter(FileName) * try { * writer.write("Hello, test!") * } * finally { * writer.close() * } * * // Create the reader needed by the test * reader = new FileReader(FileName) * * try { * test() // Invoke the test function * } * finally { * // Close and delete the temp file * reader.close() * val file = new File(FileName) * file.delete() * } * } * * def testReadingFromTheTempFile() { * var builder = new StringBuilder * var c = reader.read() * while (c != -1) { * builder.append(c.toChar) * c = reader.read() * } * assert(builder.toString === "Hello, test!") * } * * def testFirstCharOfTheTempFile() { * assert(reader.read() === 'H') * } * * def testWithoutAFixture() { * assert(1 + 1 === 2) * } * } ** ** If you prefer to keep your test classes immutable, one final variation is to use the *
* *FixtureSuitetrait from the *org.scalatest.fixturepackage. Tests in anorg.scalatest.fixture.FixtureSuitecan have a fixture * object passed in as a parameter. You must indicate the type of the fixture object * by defining theFixturetype member and define awithFixturemethod that takes a one-arg test function. * (AFixtureSuitehas two overloadedwithFixturemethods, therefore, one that takes aOneArgTest* and the other, inherited fromSuite, that takes aNoArgTest.) * Inside thewithFixture(OneArgTest)method, you create the fixture, pass it into the test function, then perform any * necessary cleanup after the test function returns. Instead of invoking each test directly, aFixtureSuitewill * pass a function that invokes the code of a test towithFixture(OneArgTest). YourwithFixture(OneArgTest)method, therefore, * is responsible for actually running the code of the test by invoking the test function. * For example, you could pass the temp file reader fixture to each test that needs it * by overriding thewithFixture(OneArgTest)method of aFixtureSuite, like this: ** import org.scalatest.fixture.FixtureSuite * import java.io.FileReader * import java.io.FileWriter * import java.io.File * * class MySuite extends FixtureSuite { * * // No vars needed in this one * * type FixtureParam = FileReader * * def withFixture(test: OneArgTest) { * * val FileName = "TempFile.txt" * * // Set up the temp file needed by the test * val writer = new FileWriter(FileName) * try { * writer.write("Hello, test!") * } * finally { * writer.close() * } * * // Create the reader needed by the test * val reader = new FileReader(FileName) * * try { * // Run the test, passing in the temp file reader * test(reader) * } * finally { * // Close and delete the temp file * reader.close() * val file = new File(FileName) * file.delete() * } * } * * def testReadingFromTheTempFile(reader: FileReader) { * var builder = new StringBuilder * var c = reader.read() * while (c != -1) { * builder.append(c.toChar) * c = reader.read() * } * assert(builder.toString === "Hello, test!") * } * * def testFirstCharOfTheTempFile(reader: FileReader) { * assert(reader.read() === 'H') * } * * def testWithoutAFixture() { * assert(1 + 1 === 2) * } * } ** ** It is worth noting that the only difference in the test code between the mutable *
* *BeforeAndAfterEachapproach shown previously and the immutableFixtureSuite* approach shown here is that two of theFixtureSuite's test methods take aFileReaderas * a parameter. Otherwise the test code is identical. One benefit of the explicit parameter is that, as demonstrated * by thetestWithoutAFixturemethod, aFixtureSuite* test method need not take the fixture. (Tests that don't take a fixture as a parameter are passed to thewithFixture* that takes aNoArgTest, shown previously.) So you can have some tests that take a fixture, and others that don't. * In this case, theFixtureSuiteprovides documentation indicating which * test methods use the fixture and which don't, whereas theBeforeAndAfterEachapproach does not. ** If you want to execute code before and after all tests (and nested suites) in a suite, such * as you could do with
* *@BeforeClassand@AfterClass* annotations in JUnit 4, you can use thebeforeAllandafterAll* methods ofBeforeAndAfterAll. See the documentation forBeforeAndAfterAllfor * an example. *The config map
* ** In some cases you may need to pass information to a suite of tests. * For example, perhaps a suite of tests needs to grab information from a file, and you want * to be able to specify a different filename during different runs. You can accomplish this in ScalaTest by passing * the filename in the config map of key-value pairs, which is passed to
* *runas aMap[String, Any]. * The values in the config map are called "config objects," because they can be used to configure * suites, reporters, and tests. ** You can specify a string config object is via the ScalaTest
* *Runner, either via the command line * or ScalaTest's ant task. * (See the documentation for Runner for information on how to specify * config objects on the command line.) * The config map is passed torun,runNestedSuites,runTests, andrunTest, * so one way to access it in your suite is to override one of those methods. If you need to use the config map inside your tests, you * can use one of the traits in theorg.scalatest.fixturepackage. (See the * documentation forFixtureSuite* for instructions on how to access the config map in tests.) *Tagging tests
* ** A
* *Suite's tests may be classified into groups by tagging them with string names. When executing * aSuite, groups of tests can optionally be included and/or excluded. In this * trait's implementation, tags are indicated by annotations attached to the test method. To * create a new tag type to use inSuites, simply define a new Java annotation that itself is annotated with theorg.scalatest.TagAnnotationannotation. * (Currently, for annotations to be * visible in Scala programs via Java reflection, the annotations themselves must be written in Java.) For example, * to create a tag namedSlowAsMolasses, to use to mark slow tests, you would * write in Java: *BECAUSE OF A SCALADOC BUG IN SCALA 2.8, I HAD TO PUT A SPACE AFTER THE AT SIGN IN ONE THE TARGET ANNOTATION EXAMPLE BELOW. IF YOU * WANT TO COPY AND PASTE FROM THIS EXAMPLE, YOU'LL NEED TO REMOVE THE SPACE BY HAND, OR COPY FROM * THE SUITE SCALADOC FOR VERSION 1.1 INSTEAD, WHICH IS ALSO VALID FOR 1.3. - Bill Venners
* ** import java.lang.annotation.*; * import org.scalatest.TagAnnotation * * @TagAnnotation * @Retention(RetentionPolicy.RUNTIME) * @ Target({ElementType.METHOD, ElementType.TYPE}) * public @interface SlowAsMolasses {} ** ** Given this new annotation, you could place a
* *Suitetest method into theSlowAsMolassesgroup * (i.e., tag it as beingSlowAsMolasses) like this: ** @SlowAsMolasses * def testSleeping() = sleep(1000000) ** ** The primary
* *runmethod takes aFilter, whose constructor takes an optional *Set[String]s calledtagsToIncludeand aSet[String]called *tagsToExclude. IftagsToIncludeisNone, all tests will be run * except those those belonging to tags listed in the *tagsToExcludeSet. IftagsToIncludeis defined, only tests * belonging to tags mentioned in thetagsToIncludeset, and not mentioned intagsToExclude, * will be run. *Ignored tests
* ** Another common use case is that tests must be “temporarily” disabled, with the * good intention of resurrecting the test at a later time. ScalaTest provides an
* *Ignore* annotation for this purpose. You use it like this: ** import org.scalatest.Suite * import org.scalatest.Ignore * * class MySuite extends Suite { * * def testAddition() { * val sum = 1 + 1 * assert(sum === 2) * assert(sum + 2 === 4) * } * * @Ignore * def testSubtraction() { * val diff = 4 - 1 * assert(diff === 3) * assert(diff - 2 === 1) * } * } ** ** If you run this version of
* *MySuitewith: ** scala> (new MySuite).run() ** ** It will run only
* *testAdditionand report thattestSubtractionwas ignored. You'll see: ** Test Starting - MySuite: testAddition * Test Succeeded - MySuite: testAddition * Test Ignored - MySuite: testSubtraction ** **
* *Ignoreis implemented as a tag. TheFilterclass effectively * addsorg.scalatest.Ignoreto thetagsToExcludeSetif it not already * in thetagsToExcludeset passed to its primary constructor. The only difference between *org.scalatest.Ignoreand the tags you may define and exclude is that ScalaTest reports * ignored tests to theReporter. The reason ScalaTest reports ignored tests is as a feeble * attempt to encourage ignored tests to be eventually fixed and added back into the active suite of tests. *Pending tests
* ** A pending test is one that has been given a name but is not yet implemented. The purpose of * pending tests is to facilitate a style of testing in which documentation of behavior is sketched * out before tests are written to verify that behavior (and often, the before the behavior of * the system being tested is itself implemented). Such sketches form a kind of specification of * what tests and functionality to implement later. *
* ** To support this style of testing, a test can be given a name that specifies one * bit of behavior required by the system being tested. The test can also include some code that * sends more information about the behavior to the reporter when the tests run. At the end of the test, * it can call method
* *pending, which will cause it to complete abruptly withTestPendingException. * Because tests in ScalaTest can be designated as pending withTestPendingException, both the test name and any information * sent to the reporter when running the test can appear in the report of a test run. (In other words, * the code of a pending test is executed just like any other test.) However, because the test completes abruptly * withTestPendingException, the test will be reported as pending, to indicate * the actual test, and possibly the functionality it is intended to test, has not yet been implemented. ** Although pending tests may be used more often in specification-style suites, such as *
* *org.scalatest.Spec, you can also use it inSuite, like this: ** import org.scalatest.Suite * * class MySuite extends Suite { * * def testAddition() { * val sum = 1 + 1 * assert(sum === 2) * assert(sum + 2 === 4) * } * * def testSubtraction() { pending } * } ** ** If you run this version of
* *MySuitewith: ** scala> (new MySuite).run() ** ** It will run both tests but report that
* *testSubtractionis pending. You'll see: ** Test Starting - MySuite: testAddition * Test Succeeded - MySuite: testAddition * Test Starting - MySuite: testSubtraction * Test Pending - MySuite: testSubtraction ** *Informers
* ** One of the parameters to the primary
* *runmethod is anReporter, which * will collect and report information about the running suite of tests. * Information about suites and tests that were run, whether tests succeeded or failed, * and tests that were ignored will be passed to theReporteras the suite runs. * Most often the reporting done by default bySuite's methods will be sufficient, but * occasionally you may wish to provide custom information to theReporterfrom a test method. * For this purpose, you can optionally include anInformerparameter in a test method, and then * pass the extra information to theInformervia itsapplymethod. TheInformer* will then pass the information to theReporterby sending anInfoProvidedevent. * Here's an example: ** import org.scalatest._ * * class MySuite extends Suite { * def testAddition(info: Informer) { * assert(1 + 1 === 2) * info("Addition seems to work") * } * } ** * If you run thisSuitefrom the interpreter, you will see the message * included in the printed report: * ** scala> (new MySuite).run() * Test Starting - MySuite: testAddition(Reporter) * Info Provided - MySuite: testAddition(Reporter) * Addition seems to work * Test Succeeded - MySuite: testAddition(Reporter) ** *Executing suites in parallel
* ** The primary
* *runmethod takes as its last parameter an optionalDistributor. If * aDistributoris passed in, this trait's implementation ofrunputs its nested *Suites into the distributor rather than executing them directly. The caller ofrun* is responsible for ensuring that some entity runs theSuites placed into the * distributor. The-ccommand line parameter toRunner, for example, will cause *Suites put into theDistributorto be run in parallel via a pool of threads. *Treatement of
* *java.lang.Errors* The Javadoc documentation for
* *java.lang.Errorstates: ** An* *Erroris a subclass ofThrowablethat indicates serious problems that a reasonable application should not try to catch. Most * such errors are abnormal conditions. ** Because
Errors are used to denote serious errors, traitSuiteand its subtypes in the ScalaTest API do not always treat a test * that completes abruptly with anErroras a test failure, but sometimes as an indication that serious problems * have arisen that should cause the run to abort. For example, if a test completes abruptly with anOutOfMemoryError, * it will not be reported as a test failure, but will instead cause the run to abort. Because not everyone usesErrors only to represent serious * problems, however, ScalaTest only behaves this way for the following exception types (and their subclasses): ** *
*
* *- *
java.lang.annotation.AnnotationFormatError- *
java.awt.AWTError- *
java.nio.charset.CoderMalfunctionError- *
javax.xml.parsers.FactoryConfigurationError- *
java.lang.LinkageError- *
java.lang.ThreadDeath- *
javax.xml.transform.TransformerFactoryConfigurationError- *
java.lang.VirtualMachineError* The previous list includes all
* *Errors that exist as part of Java 1.5 API, excludingjava.lang.AssertionError. ScalaTest * does treat a thrownAssertionErroras an indication of a test failure. In addition, any otherErrorthat is not an instance of a * type mentioned in the previous list will be caught by theSuitetraits in the ScalaTest API and reported as the cause of a test failure. ** Although trait
* *Suiteand all its subtypes in the ScalaTest API consistently behave this way with regard toErrors, * this behavior is not required by the contract ofSuite. Subclasses and subtraits that you define, for example, may treat all *Errors as test failures, or indicate errors in some other way that has nothing to do with exceptions. *Extensibility
* ** Trait
* *Suiteprovides default implementations of its methods that should * be sufficient for most applications, but many methods can be overridden when desired. Here's * a summary of the methods that are intended to be overridden: **
* *- *
run- override this method to define custom ways to run suites of * tests.- *
runNestedSuites- override this method to define custom ways to run nested suites.- *
runTests- override this method to define custom ways to run a suite's tests.- *
runTest- override this method to define custom ways to run a single named test.- *
testNames- override this method to specify theSuite's test names in a custom way.- *
tags- override this method to specify theSuite's test tags in a custom way.- *
nestedSuites- override this method to specify theSuite's nestedSuites in a custom way.- *
suiteName- override this method to specify theSuite's name in a custom way.- *
expectedTestCount- override this method to count thisSuite's expected tests in a custom way.* For example, this trait's implementation of
* *testNamesperforms reflection to discover methods starting withtest, * and places these in aSetwhose iterator returns the names in alphabetical order. If you wish to run tests in a different * order in a particularSuite, perhaps because a test namedtestAlphacan only succeed after a test named *testBetahas run, you can overridetestNamesso that it returns aSetwhose iterator returns *testBetabeforetestAlpha. (This trait's implementation ofrunwill invoke tests * in the order they come out of thetestNamesSetiterator.) ** Alternatively, you may not like starting your test methods with
* *test, and prefer using@Testannotations in * the style of Java's JUnit 4 or TestNG. If so, you can overridetestNamesto discover tests using either of these two APIs *@Testannotations, or one of your own invention. (This is in fact * howorg.scalatest.junit.JUnitSuiteandorg.scalatest.testng.TestNGSuitework.) ** Moreover, test in ScalaTest does not necessarily mean test method. A test can be anything that can be given a name, * that starts and either succeeds or fails, and can be ignored. In
* *org.scalatest.FunSuite, for example, tests are represented * as function values. This * approach might look foreign to JUnit users, but may feel more natural to programmers with a functional programming background. * To facilitate this style of writing tests,FunSuiteoverridestestNames,runTest, andrunsuch that you can * define tests as function values. ** You can also model existing JUnit 3, JUnit 4, or TestNG tests as suites of tests, thereby incorporating tests written in Java into a ScalaTest suite. * The "wrapper" classes in packages
* * @author Bill Venners */ @serializable trait Suite extends Assertions with AbstractSuite { thisSuite => import Suite.TestMethodPrefix, Suite.InformerInParens, Suite.IgnoreAnnotation /* * @param nestedSuites Aorg.scalatest.junitandorg.scalatest.testngexist to make this easy. * No matter what legacy tests you may have, it is likely you can create or use an existingSuitesubclass that allows you to model those tests * as ScalaTest suites and tests and incorporate them into a ScalaTest suite. You can then write new tests in Scala and continue supporting * older tests in Java. *ListofSuite* objects. The specifiedListmust be non-empty. Each element must be non-nulland an instance * oforg.scalatest.Suite. * * @throws NullPointerException ifnestedSuites* isnullor any element ofnestedSuites* set isnull. */ /** * A test function taking no arguments, which also provides a test name and config map. * **
*/ protected trait NoArgTest extends (() => Unit) { /** * The name of this test. */ def name: String /** * Runs the code of the test. */ def apply() /** * ASuite's implementation ofrunTestpasses instances of this trait * towithFixturefor every test method it executes. It invokeswithFixture* for every test, including test methods that take anInformer. For the latter case, * theInformerto pass to the test method is already contained inside the *NoArgTestinstance passed towithFixture. *Map[String, Any]containing objects that can be used * to configure the fixture and test. */ def configMap: Map[String, Any] } // should nestedSuites return a Set[String] instead? /** * AListof thisSuiteobject's nestedSuites. If thisSuitecontains no nestedSuites, * this method returns an emptyList. This trait's implementation of this method returns an emptyList. */ def nestedSuites: List[Suite] = Nil /** * Executes thisSuite, printing results to the standard output. * ** This method implementation calls
* *runon thisSuite, passing in: **
* *- *
testName-None- *
reporter- a reporter that prints to the standard output- *
stopper- aStopperwhoseapplymethod always returnsfalse- *
filter- aFilterconstructed withNonefortagsToIncludeandSet()* fortagsToExclude- *
configMap- an emptyMap[String, Any]- *
distributor-None- *
tracker- a newTracker* This method serves as a convenient way to execute a
* *Suite, especially from * within the Scala interpreter. ** Note: In ScalaTest, the terms "execute" and "run" basically mean the same thing and * can be used interchangably. The reason this convenience method and its three overloaded forms * aren't named
*/ final def execute() { run(None, new StandardOutReporter, new Stopper {}, Filter(), Map(), None, new Tracker) } /** * Executes thisrun* is becausejunit.framework.TestCasedeclares arunmethod * that takes no arguments but returns ajunit.framework.TestResult. That *runmethod would not overload with this method if it were namedrun, * because it would have the same parameters but a different return type than the one * defined inTestCase. To facilitate integration with JUnit 3, therefore, * these convenience "run" methods are namedexecute. In particular, this allows trait *org.scalatest.junit.JUnit3Suiteto extend bothorg.scalatest.Suiteand *junit.framework.TestCase, which enables the creating of classes that * can be run with either ScalaTest or JUnit 3. *Suitewith the specifiedconfigMap, printing results to the standard output. * ** This method implementation calls
* *runon thisSuite, passing in: **
* *- *
testName-None- *
reporter- a reporter that prints to the standard output- *
stopper- aStopperwhoseapplymethod always returnsfalse- *
filter- aFilterconstructed withNonefortagsToIncludeandSet()* fortagsToExclude- *
configMap- the specifiedconfigMapMap[String, Any]- *
distributor-None- *
tracker- a newTracker* This method serves as a convenient way to execute a
* *Suite, passing in some objects via theconfigMap, especially from within the Scala interpreter. ** Note: In ScalaTest, the terms "execute" and "run" basically mean the same thing and * can be used interchangably. The reason this convenience method and its three overloaded forms * aren't named
* * @param configMap arunis described the documentation of the overloaded form that * takes no parameters: execute(). *Mapof key-value pairs that can be used by the executingSuiteof tests. * * @throws NullPointerException if the passedconfigMapparameter isnull. */ final def execute(configMap: Map[String, Any]) { run(None, new StandardOutReporter, new Stopper {}, Filter(), configMap, None, new Tracker) } /** * Executes the test specified astestNamein thisSuite, printing results to the standard output. * ** This method implementation calls
* *runon thisSuite, passing in: **
* *- *
testName-Some(testName)- *
reporter- a reporter that prints to the standard output- *
stopper- aStopperwhoseapplymethod always returnsfalse- *
filter- aFilterconstructed withNonefortagsToIncludeandSet()* fortagsToExclude- *
configMap- an emptyMap[String, Any]- *
distributor-None- *
tracker- a newTracker* This method serves as a convenient way to run a single test, especially from within the Scala interpreter. *
* ** Note: In ScalaTest, the terms "execute" and "run" basically mean the same thing and * can be used interchangably. The reason this convenience method and its three overloaded forms * aren't named
* * @param testName the name of one test to run. * * @throws NullPointerException if the passedrunis described the documentation of the overloaded form that * takes no parameters: execute(). *testNameparameter isnull. * @throws IllegalArgumentException iftestNameis defined, but no test with the specified test name * exists in thisSuite*/ final def execute(testName: String) { run(Some(testName), new StandardOutReporter, new Stopper {}, Filter(), Map(), None, new Tracker) } /** * Executes the test specified astestNamein thisSuitewith the specifiedconfigMap, printing * results to the standard output. * ** This method implementation calls
* *runon thisSuite, passing in: **
* *- *
testName-Some(testName)- *
reporter- a reporter that prints to the standard output- *
stopper- aStopperwhoseapplymethod always returnsfalse- *
filter- aFilterconstructed withNonefortagsToIncludeandSet()* fortagsToExclude- *
configMap- the specifiedconfigMapMap[String, Any]- *
distributor-None- *
tracker- a newTracker* This method serves as a convenient way to execute a single test, passing in some objects via the
* *configMap, especially from * within the Scala interpreter. ** Note: In ScalaTest, the terms "execute" and "run" basically mean the same thing and * can be used interchangably. The reason this convenience method and its three overloaded forms * aren't named
* * @param testName the name of one test to run. * @param configMap arunis described the documentation of the overloaded form that * takes no parameters: execute(). *Mapof key-value pairs that can be used by the executingSuiteof tests. * * @throws NullPointerException if either of the passedtestNameorconfigMapparameters isnull. * @throws IllegalArgumentException iftestNameis defined, but no test with the specified test name * exists in thisSuite*/ final def execute(testName: String, configMap: Map[String, Any]) { run(Some(testName), new StandardOutReporter, new Stopper {}, Filter(), configMap, None, new Tracker) } /** * AMapwhose keys areStringtag names with which tests in thisSuiteare marked, and * whose values are theSetof test names marked with each tag. If thisSuitecontains no tags, this * method returns an emptyMap. * ** This trait's implementation of this method uses Java reflection to discover any Java annotations attached to its test methods. The * fully qualified name of each unique annotation that extends
* *TagAnnotationis considered a tag. This trait's * implementation of this method, therefore, places one key/value pair into to the *Mapfor each unique tag annotation name discovered through reflection. The mapped value for each tag name key will contain * the test method name, as provided via thetestNamesmethod. ** Subclasses may override this method to define and/or discover tags in a custom manner, but overriding method implementations * should never return an empty
* *Setas a value. If a tag has no tests, its name should not appear as a key in the * returnedMap. ** Note, the
*/ def tags: Map[String, Set[String]] = { def getTags(testName: String) = /* AFTER THE DEPRECATION CYCLE FOR GROUPS TO TAGS (1.2), REPLACE THE FOLLOWING FOR LOOP WITH THIS COMMENTED OUT ONE THAT MAKES SURE ANNOTATIONS ARE TAGGED WITH TagAnnotation. for { a <- getMethodForTestName(testName).getDeclaredAnnotations annotationClass = a.annotationType if annotationClass.isAnnotationPresent(classOf[TagAnnotation]) } yield annotationClass.getName */ for (a <- getMethodForTestName(testName).getDeclaredAnnotations) yield a.annotationType.getName val elements = for (testName <- testNames; if !getTags(testName).isEmpty) yield testName -> (Set() ++ getTags(testName)) Map() ++ elements } /** * TheTagAnnotationannotation was introduced in ScalaTest 1.0, when "groups" were renamed * to "tags." In 1.0 and 1.1, theTagAnnotationwill continue to not be required by an annotation on aSuite* method. Any annotation on aSuitemethod will be considered a tag until 1.2, to give users time to add *TagAnnotations on any tag annotations they made prior to the 1.0 release. From 1.2 onward, only annotations * themselves annotated byTagAnnotationwill be considered tag annotations. *groupsmethods has been deprecated and will be removed in a future version of ScalaTest. * Please call (and override)tagsinstead. */ @deprecated // deprecated in 1.0, so remove in 1.4 final def groups: Map[String, Set[String]] = tags /** * AnSetof test names. If thisSuitecontains no tests, this method returns an emptySet. * ** This trait's implementation of this method uses Java reflection to discover all public methods whose name starts with
* *"test", * which take either nothing or a singleInformeras parameters. For each discovered test method, it assigns a test name * comprised of just the method name if the method takes no parameters, or the method name plus(Informer)if the * method takes aInformer. Here are a few method signatures and the names that this trait's implementation assigns them: ** def testCat() {} // test name: "testCat" * def testCat(Informer) {} // test name: "testCat(Informer)" * def testDog() {} // test name: "testDog" * def testDog(Informer) {} // test name: "testDog(Informer)" * def test() {} // test name: "test" * def test(Informer) {} // test name: "test(Informer)" ** ** This trait's implementation of this method returns an immutable
* *Setof all such names, excluding the name *testNames. The iterator obtained by invokingelementson this * returnedSetwill produce the test names in their natural order, as determined byString's *compareTomethod. ** This trait's implementation of
* *runTestsinvokes this method * and callsrunTestfor each test name in the order they appear in the returnedSet's iterator. * Although this trait's implementation of this method returns aSetwhose iterator producesString* test names in a well-defined order, the contract of this method does not required a defined order. Subclasses are free to * override this method and return test names in an undefined order, or in a defined order that's different fromString's * natural order. ** Subclasses may override this method to produce test names in a custom manner. One potential reason to override
*/ def testNames: Set[String] = { def takesInformer(m: Method) = { val paramTypes = m.getParameterTypes paramTypes.length == 1 && classOf[Informer].isAssignableFrom(paramTypes(0)) } def isTestMethod(m: Method) = { val isInstanceMethod = !Modifier.isStatic(m.getModifiers()) // name must have at least 4 chars (minimum is "test") val simpleName = m.getName val firstFour = if (simpleName.length >= 4) simpleName.substring(0, 4) else "" val paramTypes = m.getParameterTypes val hasNoParams = paramTypes.length == 0 // Discover testNames(Informer) because if we didn't it might be confusing when someone // actually wrote a testNames(Informer) method and it was silently ignored. val isTestNames = simpleName == "testNames" isInstanceMethod && (firstFour == "test") && ((hasNoParams && !isTestNames) || takesInformer(m)) } val testNameArray = for (m <- getClass.getMethods; if isTestMethod(m)) yield if (takesInformer(m)) m.getName + InformerInParens else m.getName TreeSet[String]() ++ testNameArray } private def testMethodTakesInformer(testName: String) = testName.endsWith(InformerInParens) private def getMethodForTestName(testName: String) = getClass.getMethod( simpleNameForTest(testName), (if (testMethodTakesInformer(testName)) Array(classOf[Informer]) else new Array[Class[_]](0)): _* ) /** * Run the passed test function in the context of a fixture established by this method. * *testNamesis * to run tests in a different order, for example, to ensure that tests that depend on other tests are run after those other tests. * Another potential reason to override is allow tests to be defined in a different manner, such as methods annotated@Testannotations * (as is done inJUnitSuiteandTestNGSuite) or test functions registered during construction (as is * done inFunSuiteandSpec). ** This method should set up the fixture needed by the tests of the * current suite, invoke the test function, and if needed, perform any clean * up needed after the test completes. Because the
* *NoArgTestfunction * passed to this method takes no parameters, preparing the fixture will require * side effects, such as reassigning instancevars in thisSuiteor initializing * a globally accessible external database. If you want to avoid reassigning instancevars * you can use FixtureSuite. ** This trait's implementation of
* *runTestinvokes this method for each test, passing * in aNoArgTestwhoseapplymethod will execute the code of the test. ** This trait's implementation of this method simply invokes the passed
* * @param test the no-arg test function to run with a fixture */ protected def withFixture(test: NoArgTest) { test() } /** * Run a test. * *NoArgTestfunction. ** This trait's implementation uses Java reflection to invoke on this object the test method identified by the passed
* *testName. ** Implementations of this method are responsible for ensuring a
* * @param testName the name of one test to run. * @param reporter theTestStartingevent * is fired to theReporterbefore executing any test, and eitherTestSucceeded, *TestFailed, orTestPendingafter executing any nested *Suite. (If a test is marked with theorg.scalatest.Ignoretag, the *runTestsmethod is responsible for ensuring aTestIgnoredevent is fired and that * thisrunTestmethod is not invoked for that ignored test.) *Reporterto which results will be reported * @param stopper theStopperthat will be consulted to determine whether to stop execution early. * @param configMap aMapof key-value pairs that can be used by the executingSuiteof tests. * @param tracker aTrackertrackingOrdinals being fired by the current thread. * @throws NullPointerException if any oftestName,reporter,stopper,configMap* ortrackerisnull. * @throws IllegalArgumentException iftestNameis defined, but no test with the specified test name * exists in thisSuite*/ protected def runTest(testName: String, reporter: Reporter, stopper: Stopper, configMap: Map[String, Any], tracker: Tracker) { if (testName == null || reporter == null || stopper == null || configMap == null || tracker == null) throw new NullPointerException val stopRequested = stopper val report = wrapReporterIfNecessary(reporter) val method = try { getMethodForTestName(testName) } catch { case e: NoSuchMethodException => throw new IllegalArgumentException(Resources("testNotFound", testName)) case e => throw e } // Create a Rerunner if the Suite has a no-arg constructor val hasPublicNoArgConstructor = Suite.checkForPublicNoArgConstructor(getClass) val rerunnable = if (hasPublicNoArgConstructor) Some(new TestRerunner(getClass.getName, testName)) else None val testStartTime = System.currentTimeMillis report(TestStarting(tracker.nextOrdinal(), thisSuite.suiteName, Some(thisSuite.getClass.getName), testName, None, rerunnable)) val args: Array[Object] = if (testMethodTakesInformer(testName)) { val informer = new Informer { def apply(message: String) { if (message == null) throw new NullPointerException report(InfoProvided(tracker.nextOrdinal(), message, Some(NameInfo(thisSuite.suiteName, Some(thisSuite.getClass.getName), Some(testName))))) } } Array(informer) } else Array() try { val theConfigMap = configMap withFixture( new NoArgTest { def name = testName def apply() { method.invoke(thisSuite, args: _*) } def configMap = theConfigMap } ) val duration = System.currentTimeMillis - testStartTime report(TestSucceeded(tracker.nextOrdinal(), thisSuite.suiteName, Some(thisSuite.getClass.getName), testName, Some(duration), None, rerunnable)) } catch { case ite: InvocationTargetException => val t = ite.getTargetException t match { case _: TestPendingException => report(TestPending(tracker.nextOrdinal(), thisSuite.suiteName, Some(thisSuite.getClass.getName), testName)) case e if !anErrorThatShouldCauseAnAbort(e) => val duration = System.currentTimeMillis - testStartTime handleFailedTest(t, hasPublicNoArgConstructor, testName, rerunnable, report, tracker, duration) case e => throw e } case e if !anErrorThatShouldCauseAnAbort(e) => val duration = System.currentTimeMillis - testStartTime handleFailedTest(e, hasPublicNoArgConstructor, testName, rerunnable, report, tracker, duration) case e => throw e } } /** * Run zero to many of thisSuite's tests. * ** This method takes a
* *testNameparameter that optionally specifies a test to invoke. * IftestNameis defined, this trait's implementation of this method * invokesrunTeston this object, passing in: **
* *- *
testName- theStringvalue of thetestNameOptionpassed * to this method- *
reporter- theReporterpassed to this method, or one that wraps and delegates to it- *
stopper- theStopperpassed to this method, or one that wraps and delegates to it- *
configMap- theconfigMapMappassed to this method, or one that wraps and delegates to it* This method takes a
Filter, which encapsulates an optionalSetof tag names that should be included * (tagsToInclude) and aSetthat should be excluded (tagsToExclude), when deciding which * of thisSuite's tests to run. * IftagsToIncludeisNone, all tests will be run * except those those belonging to tags listed in thetagsToExcludeSet. IftagsToIncludeis defined, only tests * belonging to tags mentioned in thetagsToIncludeSet, and not mentioned in thetagsToExcludeSettestNameis defined,tagsToIncludeandtagsToExcludeare essentially ignored. * Only iftestNameisNonewilltagsToIncludeandtagsToExcludebe consulted to * determine which of the tests named in thetestNamesSetshould be run. This trait's implementation * behaves this way, and it is part of the general contract of this method, so all overridden forms of this method should behave * this way as well. For more information on test tags, see the main documentation for this trait and for classFilter. * Note that this means that even if a test is marked as ignored, for example a test method in aSuiteannotated with *org.scalatest.Ignore, if that test name is passed astestNametorunTest, it will be invoked * despite theIgnoreannotation. * * ** If
* *testNameisNone, this trait's implementation of this method * invokestestNameson thisSuiteto get aSetof names of tests to potentially run. * (AtestNamesvalue ofNoneessentially acts as a wildcard that means all tests in * thisSuitethat are selected bytagsToIncludeandtagsToExcludeshould be run.) * For each test in thetestNameSet, in the order * they appear in the iterator obtained by invoking theelementsmethod on theSet, this trait's implementation * of this method checks whether the test should be run based on theFilter. * If so, this implementation invokesrunTest, passing in: *
-
*
testName- theStringname of the test to run (which will be one of the names in thetestNamesSet)
* reporter- theReporterpassed to this method, or one that wraps and delegates to it
* stopper- theStopperpassed to this method, or one that wraps and delegates to it
* configMap- theconfigMappassed to this method, or one that wraps and delegates to it
*
* If a test is marked with the org.scalatest.Ignore tag, implementations
* of this method are responsible for ensuring a TestIgnored event is fired for that test
* and that runTest is not called for that test.
*
None, all relevant tests should be run.
* I.e., None acts like a wildcard that means run all relevant tests in this Suite.
* @param reporter the Reporter to which results will be reported
* @param stopper the Stopper that will be consulted to determine whether to stop execution early.
* @param filter a Filter with which to filter tests based on their tags
* @param configMap a Map of key-value pairs that can be used by the executing Suite of tests.
* @param distributor an optional Distributor, into which to put nested Suites to be run
* by another entity, such as concurrently by a pool of threads. If None, nested Suites will be run sequentially.
* @param tracker a Tracker tracking Ordinals being fired by the current thread.
* @throws NullPointerException if any of the passed parameters is null.
* @throws IllegalArgumentException if testName is defined, but no test with the specified test name
* exists in this Suite
*/
protected def runTests(testName: Option[String], reporter: Reporter, stopper: Stopper, filter: Filter,
configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {
if (testName == null)
throw new NullPointerException("testName was null")
if (reporter == null)
throw new NullPointerException("reporter was null")
if (stopper == null)
throw new NullPointerException("stopper was null")
if (filter == null)
throw new NullPointerException("filter was null")
if (configMap == null)
throw new NullPointerException("configMap was null")
if (distributor == null)
throw new NullPointerException("distributor was null")
if (tracker == null)
throw new NullPointerException("tracker was null")
val stopRequested = stopper
// Wrap any non-DispatchReporter, non-CatchReporter in a CatchReporter,
// so that exceptions are caught and transformed
// into error messages on the standard error stream.
val report = wrapReporterIfNecessary(reporter)
// If a testName is passed to run, just run that, else run the tests returned
// by testNames.
testName match {
case Some(tn) => runTest(tn, report, stopRequested, configMap, tracker)
case None =>
for ((tn, ignoreTest) <- filter(testNames, tags))
if (ignoreTest)
report(TestIgnored(tracker.nextOrdinal(), thisSuite.suiteName, Some(thisSuite.getClass.getName), tn))
else
runTest(tn, report, stopRequested, configMap, tracker)
}
}
/**
* Runs this suite of tests.
*
* If testName is None, this trait's implementation of this method
* calls these two methods on this object in this order:
-
*
runNestedSuites(report, stopper, tagsToInclude, tagsToExclude, configMap, distributor)
* runTests(testName, report, stopper, tagsToInclude, tagsToExclude, configMap)
*
* If testName is defined, then this trait's implementation of this method
* calls runTests, but does not call runNestedSuites. This behavior
* is part of the contract of this method. Subclasses that override run must take
* care not to call runNestedSuites if testName is defined. (The
* OneInstancePerTest trait depends on this behavior, for example.)
*
* Subclasses and subtraits that override this run method can implement them without
* invoking either the runTests or runNestedSuites methods, which
* are invoked by this trait's implementation of this method. It is recommended, but not required,
* that subclasses and subtraits that override run in a way that does not
* invoke runNestedSuites also override runNestedSuites and make it
* final. Similarly it is recommended, but not required,
* that subclasses and subtraits that override run in a way that does not
* invoke runTests also override runTests (and runTest,
* which this trait's implementation of runTests calls) and make it
* final. The implementation of these final methods can either invoke the superclass implementation
* of the method, or throw an UnsupportedOperationException if appropriate. The
* reason for this recommendation is that ScalaTest includes several traits that override
* these methods to allow behavior to be mixed into a Suite. For example, trait
* BeforeAndAfterEach overrides runTestss. In a Suite
* subclass that no longer invokes runTests from run, the
* BeforeAndAfterEach trait is not applicable. Mixing it in would have no effect.
* By making runTests final in such a Suite subtrait, you make
* the attempt to mix BeforeAndAfterEach into a subclass of your subtrait
* a compiler error. (It would fail to compile with a complaint that BeforeAndAfterEach
* is trying to override runTests, which is a final method in your trait.)
*
None, all relevant tests should be run.
* I.e., None acts like a wildcard that means run all relevant tests in this Suite.
* @param reporter the Reporter to which results will be reported
* @param stopper the Stopper that will be consulted to determine whether to stop execution early.
* @param filter a Filter with which to filter tests based on their tags
* @param configMap a Map of key-value pairs that can be used by the executing Suite of tests.
* @param distributor an optional Distributor, into which to put nested Suites to be run
* by another entity, such as concurrently by a pool of threads. If None, nested Suites will be run sequentially.
* @param tracker a Tracker tracking Ordinals being fired by the current thread.
*
* @throws NullPointerException if any passed parameter is null.
* @throws IllegalArgumentException if testName is defined, but no test with the specified test name
* exists in this Suite
*/
def run(testName: Option[String], reporter: Reporter, stopper: Stopper, filter: Filter,
configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {
if (testName == null)
throw new NullPointerException("testName was null")
if (reporter == null)
throw new NullPointerException("reporter was null")
if (stopper == null)
throw new NullPointerException("stopper was null")
if (filter == null)
throw new NullPointerException("filter was null")
if (configMap == null)
throw new NullPointerException("configMap was null")
if (distributor == null)
throw new NullPointerException("distributor was null")
if (tracker == null)
throw new NullPointerException("tracker was null")
val stopRequested = stopper
val report = wrapReporterIfNecessary(reporter)
testName match {
case None => runNestedSuites(report, stopRequested, filter, configMap, distributor, tracker)
case Some(_) =>
}
runTests(testName, report, stopRequested, filter, configMap, distributor, tracker)
if (stopRequested()) {
val rawString = Resources("executeStopping")
report(InfoProvided(tracker.nextOrdinal(), rawString, Some(NameInfo(thisSuite.suiteName, Some(thisSuite.getClass.getName), testName))))
}
}
private def handleFailedTest(throwable: Throwable, hasPublicNoArgConstructor: Boolean, testName: String,
rerunnable: Option[Rerunner], report: Reporter, tracker: Tracker, duration: Long) {
val message =
if (throwable.getMessage != null) // [bv: this could be factored out into a helper method]
throwable.getMessage
else
throwable.toString
report(TestFailed(tracker.nextOrdinal(), message, thisSuite.suiteName, Some(thisSuite.getClass.getName), testName, Some(throwable), Some(duration), None, rerunnable))
}
/**
*
* Run zero to many of this Suite's nested Suites.
*
*
* If the passed distributor is None, this trait's
* implementation of this method invokes run on each
* nested Suite in the List obtained by invoking nestedSuites.
* If a nested Suite's run
* method completes abruptly with an exception, this trait's implementation of this
* method reports that the Suite aborted and attempts to run the
* next nested Suite.
* If the passed distributor is defined, this trait's implementation
* puts each nested Suite
* into the Distributor contained in the Some, in the order in which the
* Suites appear in the List returned by nestedSuites, passing
* in a new Tracker obtained by invoking nextTracker on the Tracker
* passed to this method.
*
* Implementations of this method are responsible for ensuring SuiteStarting events
* are fired to the Reporter before executing any nested Suite, and either SuiteCompleted
* or SuiteAborted after executing any nested Suite.
*
Reporter to which results will be reported
* @param stopper the Stopper that will be consulted to determine whether to stop execution early.
* @param filter a Filter with which to filter tests based on their tags
* @param configMap a Map of key-value pairs that can be used by the executing Suite of tests.
* @param distributor an optional Distributor, into which to put nested Suites to be run
* by another entity, such as concurrently by a pool of threads. If None, nested Suites will be run sequentially.
* @param tracker a Tracker tracking Ordinals being fired by the current thread.
*
* @throws NullPointerException if any passed parameter is null.
*/
protected def runNestedSuites(reporter: Reporter, stopper: Stopper, filter: Filter,
configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {
if (reporter == null)
throw new NullPointerException("reporter was null")
if (stopper == null)
throw new NullPointerException("stopper was null")
if (filter == null)
throw new NullPointerException("filter was null")
if (configMap == null)
throw new NullPointerException("configMap was null")
if (distributor == null)
throw new NullPointerException("distributor was null")
if (tracker == null)
throw new NullPointerException("tracker was null")
val stopRequested = stopper
val report = wrapReporterIfNecessary(reporter)
def callExecuteOnSuite(nestedSuite: Suite) {
if (!stopRequested()) {
// Create a Rerunner if the Suite has a no-arg constructor
val hasPublicNoArgConstructor = Suite.checkForPublicNoArgConstructor(nestedSuite.getClass)
val rerunnable =
if (hasPublicNoArgConstructor)
Some(new SuiteRerunner(nestedSuite.getClass.getName))
else
None
val rawString = Resources("suiteExecutionStarting")
val formatter = formatterForSuiteStarting(nestedSuite)
val suiteStartTime = System.currentTimeMillis
report(SuiteStarting(tracker.nextOrdinal(), nestedSuite.suiteName, Some(nestedSuite.getClass.getName), formatter, rerunnable))
try {
// Same thread, so OK to send same tracker
nestedSuite.run(None, report, stopRequested, filter, configMap, distributor, tracker)
val rawString = Resources("suiteCompletedNormally")
val formatter = formatterForSuiteCompleted(nestedSuite)
val duration = System.currentTimeMillis - suiteStartTime
report(SuiteCompleted(tracker.nextOrdinal(), nestedSuite.suiteName, Some(thisSuite.getClass.getName), Some(duration), formatter, rerunnable))
}
catch {
case e: RuntimeException => {
val rawString = Resources("executeException")
val formatter = formatterForSuiteAborted(nestedSuite, rawString)
val duration = System.currentTimeMillis - suiteStartTime
report(SuiteAborted(tracker.nextOrdinal(), rawString, nestedSuite.suiteName, Some(thisSuite.getClass.getName), Some(e), Some(duration), formatter, rerunnable))
}
}
}
}
distributor match {
case None => nestedSuites.foreach(callExecuteOnSuite)
case Some(distribute) =>
for (nestedSuite <- nestedSuites)
distribute(nestedSuite, tracker.nextTracker())
}
}
/**
* A user-friendly suite name for this Suite.
*
*
* This trait's
* implementation of this method returns the simple name of this object's class. This
* trait's implementation of runNestedSuites calls this method to obtain a
* name for Reports to pass to the suiteStarting, suiteCompleted,
* and suiteAborted methods of the Reporter.
*
Suite object's suite name.
*/
def suiteName = getSimpleNameOfAnObjectsClass(thisSuite)
/**
* Throws TestPendingException to indicate a test is pending.
*
* * A pending test is one that has been given a name but is not yet implemented. The purpose of * pending tests is to facilitate a style of testing in which documentation of behavior is sketched * out before tests are written to verify that behavior (and often, the before the behavior of * the system being tested is itself implemented). Such sketches form a kind of specification of * what tests and functionality to implement later. *
* *
* To support this style of testing, a test can be given a name that specifies one
* bit of behavior required by the system being tested. The test can also include some code that
* sends more information about the behavior to the reporter when the tests run. At the end of the test,
* it can call method pending, which will cause it to complete abruptly with TestPendingException.
* Because tests in ScalaTest can be designated as pending with TestPendingException, both the test name and any information
* sent to the reporter when running the test can appear in the report of a test run. (In other words,
* the code of a pending test is executed just like any other test.) However, because the test completes abruptly
* with TestPendingException, the test will be reported as pending, to indicate
* the actual test, and possibly the functionality it is intended to test, has not yet been implemented.
*
* Note: This method always completes abruptly with a TestPendingException. Thus it always has a side
* effect. Methods with side effects are usually invoked with parentheses, as in pending(). This
* method is defined as a parameterless method, in flagrant contradiction to recommended Scala style, because it
* forms a kind of DSL for pending tests. It enables tests in suites such as FunSuite or Spec
* to be denoted by placing "(pending)" after the test name, as in:
*
* test("that style rules are not laws") (pending)
*
*
*
* Readers of the code see "pending" in parentheses, which looks like a little note attached to the test name to indicate
* it is pending. Whereas "(pending()) looks more like a method call, "(pending)" lets readers
* stay at a higher level, forgetting how it is implemented and just focusing on the intent of the programmer who wrote the code.
*
TestPendingException, else
* throw TestFailedException.
*
*
* This method can be used to temporarily change a failing test into a pending test in such a way that it will
* automatically turn back into a failing test once the problem originally causing the test to fail has been fixed.
* At that point, you need only remove the pendingUntilFixed call. In other words, a
* pendingUntilFixed surrounding a block of code that isn't broken is treated as a test failure.
* The motivation for this behavior is to encourage people to remove pendingUntilFixed calls when
* there are no longer needed.
*
* This method facilitates a style of testing in which tests are written before the code they test. Sometimes you may
* encounter a test failure that requires more functionality than you want to tackle without writing more tests. In this
* case you can mark the bit of test code causing the failure with pendingUntilFixed. You can then write more
* tests and functionality that eventually will get your production code to a point where the original test won't fail anymore.
* At this point the code block marked with pendingUntilFixed will no longer throw an exception (because the
* problem has been fixed). This will in turn cause pendingUntilFixed to throw TestFailedException
* with a detail message explaining you need to go back and remove the pendingUntilFixed call as the problem orginally
* causing your test code to fail has been fixed.
*
TestPendingException
* @throws TestPendingException if the passed block of code completes abruptly with an Exception or AssertionError
*/
def pendingUntilFixed(f: => Unit) {
val isPending =
try {
f
false
}
catch {
case _: Exception => true
case _: AssertionError => true
}
if (isPending)
throw new TestPendingException
else
throw new TestFailedException(Resources("pendingUntilFixed"), 2)
}
/**
* The total number of tests that are expected to run when this Suite's run method is invoked.
*
* * This trait's implementation of this method returns the sum of: *
* *-
*
- the size of the
testNamesList, minus the number of tests marked as ignored * - the sum of the values obtained by invoking
*
expectedTestCounton every nestedSuitecontained in *nestedSuites*
Filter with which to filter tests to count based on their tags
*/
def expectedTestCount(filter: Filter): Int = {
// [bv: here was another tricky refactor. How to increment a counter in a loop]
def countNestedSuiteTests(nestedSuites: List[Suite], filter: Filter): Int =
nestedSuites match {
case List() => 0
case nestedSuite :: nestedSuites => nestedSuite.expectedTestCount(filter) +
countNestedSuiteTests(nestedSuites, filter)
}
filter.runnableTestCount(testNames, tags) + countNestedSuiteTests(nestedSuites, filter)
}
// Wrap any non-DispatchReporter, non-CatchReporter in a CatchReporter,
// so that exceptions are caught and transformed
// into error messages on the standard error stream.
private[scalatest] def wrapReporterIfNecessary(reporter: Reporter) = reporter match {
case dr: DispatchReporter => dr
case cr: CatchReporter => cr
case _ => new CatchReporter(reporter)
}
}
private[scalatest] object Suite {
private[scalatest] val TestMethodPrefix = "test"
private[scalatest] val InformerInParens = "(Informer)"
private[scalatest] val IgnoreAnnotation = "org.scalatest.Ignore"
private[scalatest] def getSimpleNameOfAnObjectsClass(o: AnyRef) = stripDollars(parseSimpleName(o.getClass().getName()))
// [bv: this is a good example of the expression type refactor. I moved this from SuiteClassNameListCellRenderer]
// this will be needed by the GUI classes, etc.
private[scalatest] def parseSimpleName(fullyQualifiedName: String) = {
val dotPos = fullyQualifiedName.lastIndexOf('.')
// [bv: need to check the dotPos != fullyQualifiedName.length]
if (dotPos != -1 && dotPos != fullyQualifiedName.length)
fullyQualifiedName.substring(dotPos + 1)
else
fullyQualifiedName
}
private[scalatest] def checkForPublicNoArgConstructor(clazz: java.lang.Class[_]) = {
try {
val constructor = clazz.getConstructor(new Array[java.lang.Class[T] forSome { type T }](0): _*)
Modifier.isPublic(constructor.getModifiers)
}
catch {
case nsme: NoSuchMethodException => false
}
}
private[scalatest] def stripDollars(s: String): String = {
val lastDollarIndex = s.lastIndexOf('$')
if (lastDollarIndex < s.length - 1)
if (lastDollarIndex == -1 || !s.startsWith("line")) s else s.substring(lastDollarIndex + 1)
else {
// The last char is a dollar sign
val lastNonDollarChar = s.reverse.find(_ != '$')
lastNonDollarChar match {
case None => s
case Some(c) => {
val lastNonDollarIndex = s.lastIndexOf(c)
if (lastNonDollarIndex == -1) s
else stripDollars(s.substring(0, lastNonDollarIndex + 1))
}
}
}
}
private[scalatest] def diffStrings(s: String, t: String): Tuple2[String, String] = {
def findCommonPrefixLength(s: String, t: String): Int = {
val max = s.length.min(t.length) // the maximum potential size of the prefix
var i = 0
var found = false
while (i < max & !found) {
found = (s.charAt(i) != t.charAt(i))
if (!found)
i = i + 1
}
i
}
def findCommonSuffixLength(s: String, t: String): Int = {
val max = s.length.min(t.length) // the maximum potential size of the suffix
var i = 0
var found = false
while (i < max & !found) {
found = (s.charAt(s.length - 1 - i) != t.charAt(t.length - 1 - i))
if (!found)
i = i + 1
}
i
}
val commonPrefixLength = findCommonPrefixLength(s, t)
val commonSuffixLength = findCommonSuffixLength(s.substring(commonPrefixLength), t.substring(commonPrefixLength))
val prefix = s.substring(0, commonPrefixLength)
val suffix = if (s.length - commonSuffixLength < 0) "" else s.substring(s.length - commonSuffixLength)
val sMiddleEnd = s.length - commonSuffixLength
val tMiddleEnd = t.length - commonSuffixLength
val sMiddle = s.substring(commonPrefixLength, sMiddleEnd)
val tMiddle = t.substring(commonPrefixLength, tMiddleEnd)
val MaxContext = 20
val shortPrefix = if (commonPrefixLength > MaxContext) "..." + prefix.substring(prefix.length - MaxContext) else prefix
val shortSuffix = if (commonSuffixLength > MaxContext) suffix.substring(0, MaxContext) + "..." else suffix
(shortPrefix + "[" + sMiddle + "]" + shortSuffix, shortPrefix + "[" + tMiddle + "]" + shortSuffix)
}
// If the objects are two strings, replace them with whatever is returned by diffStrings.
// Otherwise, use the same objects.
private[scalatest] def getObjectsForFailureMessage(a: Any, b: Any) =
a match {
case aStr: String => {
b match {
case bStr: String => {
Suite.diffStrings(aStr, bStr)
}
case _ => (a, b)
}
}
case _ => (a, b)
}
private[scalatest] def formatterForSuiteStarting(suite: Suite): Option[Formatter] =
suite match {
case spec: Spec => Some(IndentedText(suite.suiteName + ":", suite.suiteName, 0))
case spec: FlatSpec => Some(IndentedText(suite.suiteName + ":", suite.suiteName, 0))
case spec: WordSpec => Some(IndentedText(suite.suiteName + ":", suite.suiteName, 0))
case spec: FeatureSpec => Some(IndentedText(suite.suiteName + ":", suite.suiteName, 0))
case _ => None
}
private[scalatest] def formatterForSuiteCompleted(suite: Suite): Option[Formatter] =
suite match {
case spec: Spec => Some(MotionToSuppress)
case spec: FlatSpec => Some(MotionToSuppress)
case spec: WordSpec => Some(MotionToSuppress)
case spec: FeatureSpec => Some(MotionToSuppress)
case _ => None
}
private[scalatest] def formatterForSuiteAborted(suite: Suite, message: String): Option[Formatter] = {
suite match {
case spec: Spec => Some(IndentedText(message, message, 0))
case spec: FlatSpec => Some(IndentedText(message, message, 0))
case spec: WordSpec => Some(IndentedText(message, message, 0))
case spec: FeatureSpec => Some(IndentedText(message, message, 0))
case _ => None
}
}
private def simpleNameForTest(testName: String) =
if (testName.endsWith(InformerInParens))
testName.substring(0, testName.length - InformerInParens.length)
else
testName
private[scalatest] def anErrorThatShouldCauseAnAbort(throwable: Throwable) =
throwable match {
case _: AnnotationFormatError => true
case _: AWTError => true
case _: CoderMalfunctionError => true
case _: FactoryConfigurationError => true
case _: LinkageError => true
case _: ThreadDeath => true
case _: TransformerFactoryConfigurationError => true
case _: VirtualMachineError => true
case _ => false
}
}