org.scalatest.OneInstancePerTest.scala Maven / Gradle / Ivy
/*
* Copyright 2001-2013 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
/**
* Trait that facilitates a style of testing in which each test is run in its own instance
* of the suite class to isolate each test from the side effects of the other tests in the
* suite.
*
*
* Recommended Usage: Trait OneInstancePerTest is intended primarily to serve as a supertrait for
* ParallelTestExecution and the path traits, to
* facilitate porting JUnit tests to ScalaTest, and to make it easy for users who prefer JUnit's approach to isolation to obtain similar
* behavior in ScalaTest.
*
*
*
* If you mix this trait into a Suite, you can initialize shared reassignable
* fixture variables as well as shared mutable fixture objects in the constructor of the
* class. Because each test will run in its own instance of the class, each test will
* get a fresh copy of the instance variables. This is the approach to test isolation taken,
* for example, by the JUnit framework. OneInstancePerTest can, therefore,
* be handy when porting JUnit tests to ScalaTest.
*
*
*
* Here's an example of OneInstancePerTest being used in a FunSuite:
*
*
*
* import org.scalatest.FunSuite
* import org.scalatest.OneInstancePerTest
* import collection.mutable.ListBuffer
*
* class MySuite extends FunSuite with OneInstancePerTest {
*
* val builder = new StringBuilder("ScalaTest is ")
* val buffer = new ListBuffer[String]
*
* test("easy") {
* builder.append("easy!")
* assert(builder.toString === "ScalaTest is easy!")
* assert(buffer.isEmpty)
* buffer += "sweet"
* }
*
* test("fun") {
* builder.append("fun!")
* assert(builder.toString === "ScalaTest is fun!")
* assert(buffer.isEmpty)
* }
* }
*
*
*
* OneInstancePerTest is supertrait to ParallelTestExecution, in which
* running each test in its own instance is intended to make it easier to write suites of tests that run in parallel (by reducing the likelihood
* of concurrency bugs in those suites.) OneInstancePerTest is also supertrait to the path traits,
* path.FunSpec and path.FreeSpec, to make it obvious
* these traits run each test in a new, isolated instance.
*
*
*
* For the details on how OneInstancePerTest works, see the documentation for methods runTests and runTest,
* which this trait overrides.
*
*
* @author Bill Venners
*/
trait OneInstancePerTest extends SuiteMixin {
this: Suite =>
/**
* Modifies the behavior of super.runTest to facilitate running each test in its
* own instance of this Suite's class.
*
*
* This trait's implementation of runTest
* uses the runTestInNewInstance flag of the passed Args object to determine whether this instance is the general instance responsible
* for running all tests in the suite (runTestInNewInstance is true), or a test-specific instance
* responsible for running just one test (runTestInNewInstance is false).
* Note that these Boolean values are reverse those used by runTests, because runTests always inverts the Boolean value
* of runTestInNewInstance when invoking runTest.
*
*
*
* If runTestInNewInstance is true, this trait's implementation of this method creates a new instance of this class (by
* invoking newInstance on itself), then invokes run on the new instance,
* passing in testName, wrapped in a Some, and args unchanged.
* (I.e., the Args object passed to runTest is forwarded as is to run
* on the new instance, including with runTestInNewInstance set.)
* If the invocation of either newInstance on this
* Suite or run on a newly created instance of this Suite
* completes abruptly with an exception, then this runTests method will complete
* abruptly with the same exception.
*
*
*
* If runTestInNewInstance is false, this trait's implementation of this method simply invokes super.runTest,
* passing along the same testName and args objects.
*
*
* @param testName the name of one test to execute.
* @param args the Args for this run
* @return a Status object that indicates when the test started by this method has completed, and whether or not it failed .
*/
protected abstract override def runTest(testName: String, args: Args): Status = {
if (args.runTestInNewInstance) {
// In initial instance, so create a new test-specific instance for this test and invoke run on it.
val oneInstance = newInstance
oneInstance.run(Some(testName), args)
}
else // Therefore, in test-specific instance, so run the test.
super.runTest(testName, args)
}
/**
* Modifies the behavior of super.runTests to facilitate running each test in its
* own instance of this Suite's class.
*
*
* This trait's implementation of runTest
* uses the runTestInNewInstance flag of the passed Args object to determine whether this instance is the general instance responsible
* for running all tests in the suite (runTestInNewInstance is false), or a test-specific instance
* responsible for running just one test (runTestInNewInstance is true). Note that these Boolean values are
* reverse those used by runTest, because runTests always inverts the Boolean value of
* runTestInNewInstance when invoking runTest.
*
*
*
* If runTestInNewInstance is false, this trait's implementation of this method will invoke
* super.runTests, passing along testName and args, but with the
* runTestInNewInstance flag set to true. By setting runTestInNewInstance to
* true, runTests is telling runTest to create a new instance to run each test.
*
*
*
* If runTestInNewInstance is true, this trait's implementation of this method will invoke
* runTest directly, passing in testName.get and the args object, with
* the runTestInNewInstance flag set to false. By setting runTestInNewInstance to
* false, runTests is telling runTest that this is the test-specific instance,
* so it should just run the specified test.
*
*
* @param testName an optional name of one test to run. If None, all relevant tests should be run.
* I.e., None acts like a wildcard that means run all relevant tests in this Suite.
* @param args the Args for this run
* @return a Status object that indicates when all tests started by this method have completed, and whether or not a failure occurred.
*
* @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, or if runTestInNewInstance is true, but testName
* is empty.
*/
protected abstract override def runTests(testName: Option[String], args: Args): Status = {
if (args.runTestInNewInstance) {
if (testName.isEmpty)
throw new IllegalArgumentException("args.runTestInNewInstance was true, but testName was not defined")
// In test-specific instance, so run the test. (We are removing RTINI
// so that runTest will realize it is in the test-specific instance.)
runTest(testName.get, args.copy(runTestInNewInstance = false))
}
else {
// In initial instance, so set the RTINI flag and call super.runTests, which
// will go through any scopes and call runTest as usual. If this method was called
// via super.runTests from PTE, the TestSortingReporter and WrappedDistributor
// will already be in place.
super.runTests(testName, args.copy(runTestInNewInstance = true))
}
}
/**
* Construct a new instance of this Suite.
*
*
* This trait's implementation of runTests invokes this method to create
* a new instance of this Suite for each test. This trait's implementation
* of this method uses reflection to call this.getClass.newInstance. This
* approach will succeed only if this Suite's class has a public, no-arg
* constructor. In most cases this is likely to be true, because to be instantiated
* by ScalaTest's Runner a Suite needs a public, no-arg
* constructor. However, this will not be true of any Suite defined as
* an inner class of another class or trait, because every constructor of an inner
* class type takes a reference to the enclosing instance. In such cases, and in
* cases where a Suite class is explicitly defined without a public,
* no-arg constructor, you will need to override this method to construct a new
* instance of the Suite in some other way.
*
*
*
* Here's an example of how you could override newInstance to construct
* a new instance of an inner class:
*
*
*
* import org.scalatest.Suite
*
* class Outer {
* class InnerSuite extends Suite with OneInstancePerTest {
* def testOne() {}
* def testTwo() {}
* override def newInstance = new InnerSuite
* }
* }
*
*/
def newInstance: Suite with OneInstancePerTest = this.getClass.newInstance.asInstanceOf[Suite with OneInstancePerTest]
}