All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatest.Spec.scala Maven / Gradle / Ivy

Go to download

ScalaTest is a free, open-source testing toolkit for Scala and Java programmers.

There is a newer version: 2.0.M6-SNAP4
Show newest version
/*
 * 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 scala.collection.immutable.ListSet
import Suite._
import Spec.isTestMethod
import Spec.equalIfRequiredCompactify
import org.scalatest.events._
import scala.reflect.NameTransformer._
import java.lang.reflect.{Method, Modifier, InvocationTargetException}

/**
 * Trait that facilitates a “behavior-driven” style of development (BDD), in which tests
 * are methods, optionally nested inside singleton objects defining textual scopes.
 *
 * 
* Recommended Usage: * Trait Spec allows you to define tests as methods, which saves one generated class file per test compared to style traits that represent tests as functions. * As a result, using Spec can be a good choice in large projects where class file generation is a concern as well as when generating tests programatically * via a static code generator. *
* *

* Here's an example Spec: *

* *
 * package org.scalatest.examples.spec
 * 
 * import org.scalatest.Spec
 * 
 * class SetSpec extends Spec {
 * 
 *   object `A Set` {
 *     object `when empty` {
 *       def `should have size 0` {
 *         assert(Set.empty.size === 0)
 *       }
 *     
 *       def `should produce NoSuchElementException when head is invoked` {
 *         intercept[NoSuchElementException] {
 *           Set.empty.head
 *         }
 *       }
 *     }
 *   }
 * }
 * 
* *

* A Spec can contain scopes and tests. You define a scope * with a nested singleton object, and a test with a method. The names of both scope objects and test methods * must be expressed in back ticks and contain at least one space character. *

* *

* A space placed in backticks is encoded by the Scala compiler as $u0020, as * illustrated here: *

* *
 * scala> def `an example` = ()
 * an$u0020example: Unit
 * 
* *

* Spec uses reflection to discover scope objects and test methods. * During discovery, Spec will consider any nested singleton object whose name * includes $u0020 a scope object, and any method whose name includes $u0020 a test method. * It will ignore any singleton objects or methods that do not include a $u0020 character. Thus, Spec would * not consider the following singleton object a scope object: *

* *
 * object `Set` { // Not discovered, because no space character
 * }
 * 
* *

* You can make such a scope discoverable by placing a space at the end, like this: *

* *
 * object `Set ` { // Discovered, because of the trailing space character
 * }
 * 
* *

* Rather than performing this discovery during construction, when instance variables used by scope objects may as yet be uninitialized, * Spec performs discovery lazily, the first time a method needing the results of discovery is invoked. * For example, methods run, runTests, tags, expectedTestCount, * runTest, and testNames all ensure that scopes and tests have already been discovered prior to doing anything * else. Discovery is performed, and the results recorded, only once for each Spec instance. *

* *

* A scope names, or gives more information about, the subject (class or other entity) you are specifying * and testing. In the previous example, `A Set` * is the subject under specification and test. With each test name you provide a string (the test text) that specifies * one bit of behavior of the subject, and a block of code (the body of the test method) that verifies that behavior. *

* *

* When you execute a Spec, it will send Formatters in the events it sends to the * Reporter. ScalaTest's built-in reporters will report these events in such a way * that the output is easy to read as an informal specification of the subject being tested. * For example, were you to run SetSpec from within the Scala interpreter: *

* *
 * scala> new SetSpec execute
 * 
* *

* You would see: *

* *
 * A Set
 *   when empty
 *   - should have size 0
 *   - should produce NoSuchElementException when head is invoked
 * 
* *

* Or, to run just the test named A Set when empty should have size 0, you could pass that test's name, or any unique substring of the * name, such as "size 0" or even just "0". Here's an example: *

* *
 * scala> new SetSuite execute "size 0"
 * A Set
 *   when empty
 *   - should have size 0
 * 
* *

* You can also pass to execute a config map of key-value * pairs, which will be passed down into suites and tests, as well as other parameters that configure the run itself. * For more information on running in the Scala interpreter, see the documentation for Suite's execute method and the * ScalaTest shell. *

* *

* The execute method invokes a run method that takes two * parameters. This run method, which actually executes the suite, will usually be invoked by a test runner, such * as run, tools.Runner, a build tool, or an IDE. *

* *

* The test methods shown in this example are parameterless. This is recommended even for test methods with obvious side effects. In production code * you would normally declare no-arg, side-effecting methods as empty-paren methods, and call them with * empty parentheses, to make it more obvious to readers of the code that they have a side effect. Whether or not a test method has * a side effect, however, is a less important distinction than it is for methods in production code. Moreover, test methods are not * normally invoked directly by client code, but rather through reflection by running the Suite that contains them, so a * lack of parentheses on an invocation of a side-effecting test method would not normally appear in any client code. Given the empty * parentheses do not add much value in the test methods case, the recommended style is to simply always leave them off. *

* *

* Note: The approach of using backticks around test method names to make it easier to write descriptive test names was * inspired by the SimpleSpec test framework, originally created by Coda Hale. *

* *

Ignored tests

* *

* To support the common use case of temporarily disabling a test in a Spec, with the * good intention of resurrecting the test at a later time, you can annotate the test method with @Ignore. * For example, to temporarily disable the test method with the name `should have size zero", just annotate * it with @Ignore, like this: *

* *
 * package org.scalatest.examples.spec.ignore
 * 
 * import org.scalatest._
 * 
 * class SetSpec extends Spec {
 *   
 *   object `A Set` {
 *     object `when empty` {
 *       @Ignore def `should have size 0` {
 *         assert(Set.empty.size === 0)
 *       }
 *       
 *       def `should produce NoSuchElementException when head is invoked` {
 *         intercept[NoSuchElementException] {
 *           Set.empty.head
 *         }
 *       }
 *     }
 *   }
 * }
 * 
* *

* If you run this version of SetSpec with: *

* *
 * scala> new SetSpec execute
 * 
* *

* It will run only the second test and report that the first test was ignored: *

* *
 * A Set
 *   when empty
 *   - should have size 0 !!! IGNORED !!!
 *   - should produce NoSuchElementException when head is invoked
 * 
* *

* If you wish to temporarily ignore an entire suite of tests, you can annotate the test class with @Ignore, like this: *

* *
 * package org.scalatest.examples.spec.ignoreall
 * 
 * import org.scalatest._
 *
 * @Ignore
 * class SetSpec extends Spec {
 *   
 *   object `A Set` {
 *     object `when empty` {
 *       def `should have size 0` {
 *         assert(Set.empty.size === 0)
 *       }
 *       
 *       def `should produce NoSuchElementException when head is invoked` {
 *         intercept[NoSuchElementException] {
 *           Set.empty.head
 *         }
 *       }
 *     }
 *   }
 * }
 * 
* *

* When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. * Thus, marking the SetSpec in the above example with the @Ignore tag annotation means that both tests * in the class will be ignored. If you run the above SetSpec in the Scala interpreter, you'll see: *

* *
 * scala> new SetSpec execute
 * SetSpec:
 * A Set
 *   when empty
 *   - should have size 0 !!! IGNORED !!!
 *   - should produce NoSuchElementException when head is invoked !!! IGNORED !!!
 * 
* *

* Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes * will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored * class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to * prevent a class from being discovered at all, use the DoNotDiscover annotation instead. *

* * *

Informers

* *

* One of the objects to Spec's run method is a Reporter, 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 the Reporter as the suite runs. * Most often the reporting done by default by Spec's methods will be sufficient, but * occasionally you may wish to provide custom information to the Reporter from a test. * For this purpose, an Informer that will forward information to the current Reporter * is provided via the info parameterless method. * You can pass the extra information to the Informer via one of its apply methods. * The Informer will then pass the information to the Reporter via an InfoProvided event. * Here's an example in which the Informer returned by info is used implicitly by the * Given, When, and Then methods of trait GivenWhenThen: *

* *
 * package org.scalatest.examples.spec.info
 * 
 * import collection.mutable
 * import org.scalatest._
 * 
 * class SetSpec extends Spec with GivenWhenThen {
 *   
 *   object `A mutable Set` {
 *     def `should allow an element to be added` {
 *       Given("an empty mutable Set")
 *       val set = mutable.Set.empty[String]
 * 
 *       When("an element is added")
 *       set += "clarity"
 * 
 *       Then("the Set should have size 1")
 *       assert(set.size === 1)
 * 
 *       And("the Set should contain the added element")
 *       assert(set.contains("clarity"))
 * 
 *       info("That's all folks!")
 *     }
 *   }
 * }
 * 
* * If you run this Spec from the interpreter, you will see the following output: * *
 * scala> new SetSpec execute
 * A mutable Set
 * - should allow an element to be added
 *   + Given an empty mutable Set 
 *   + When an element is added 
 *   + Then the Set should have size 1 
 *   + And the Set should contain the added element 
 *   + That's all folks!  
 * 
* *

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, 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, has not yet been implemented. *

* *

* You can mark a test as pending in Spec by using "{ pending }" as the body of the test method, * like this: *

* *
 * package org.scalatest.examples.spec.pending
 * 
 * import org.scalatest._
 * 
 * class SetSpec extends Spec {
 * 
 *   object `A Set` {
 *     object `when empty` {
 *       def `should have size 0` { pending }
 *       
 *       def `should produce NoSuchElementException when head is invoked` {
 *         intercept[NoSuchElementException] {
 *           Set.empty.head
 *         }
 *       }
 *     }
 *   }
 * }
 * 
* *

* (Note: “pending” is the body of the test. Thus the test contains just one statement, an invocation * of the pending method, which throws TestPendingException.) * If you run this version of SetSpec with: *

* *
 * scala> new SetSpec execute
 * 
* *

* It will run both tests, but report that test "should have size 0" is pending. You'll see: *

* *
 * A Set
 *   when empty
 *   - should have size 0 (pending)
 *   - should produce NoSuchElementException when head is invoked
 * 
* *

Tagging tests

* *

* A Spec's tests may be classified into groups by tagging them with string names. When executing * a Spec, 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 in Specs, simply define a new Java annotation that itself is annotated with * the org.scalatest.TagAnnotation annotation. * (Currently, for annotations to be * visible in Scala programs via Java reflection, the annotations themselves must be written in Java.) For example, * to create tags named SlowTest and DbTest, you would * write in Java: *

* *
 * package org.scalatest.examples.spec.tagging;
 * import java.lang.annotation.*; 
 * import org.scalatest.TagAnnotation;
 * 
 * @TagAnnotation
 * @Retention(RetentionPolicy.RUNTIME)
 * @Target({ElementType.METHOD, ElementType.TYPE})
 * public @interface SlowTest {}
 * 
 * @TagAnnotation
 * @Retention(RetentionPolicy.RUNTIME)
 * @Target({ElementType.METHOD, ElementType.TYPE})
 * public @interface DbTest {}
 * 
* *

* Given these annotations, you could tag Spec tests like this: *

* *
 * package org.scalatest.examples.spec.tagging
 * 
 * import org.scalatest.Spec
 * 
 * class SetSpec extends Spec {
 * 
 *   object `A Set` {
 *     object `when empty` {

 *       @SlowTest
 *       def `should have size 0` {
 *         assert(Set.empty.size === 0)
 *       }
 *       
 *       @SlowTest @DbTest
 *       def `should produce NoSuchElementException when head is invoked` {
 *         intercept[NoSuchElementException] {
 *           Set.empty.head
 *         }
 *       }
 *     }
 *   }
 * }
 * 
* *

* The run method takes a Filter, whose constructor takes an optional * Set[String] called tagsToInclude and a Set[String] called * tagsToExclude. If tagsToInclude is None, all tests will be run * except those those with tags listed in the * tagsToExclude Set. If tagsToInclude is defined, only tests * with tags mentioned in the tagsToInclude set, and not mentioned in tagsToExclude, * will be run. *

* *

* A tag annotation also allows you to tag all the tests of a Spec in * one stroke by annotating the class. For more information and examples, see the * documentation for class Tag. *

* *

Shared fixtures

* *

* A test fixture is composed of the objects and other artifacts (files, sockets, database * connections, etc.) tests use to do their work. * When multiple tests need to work with the same fixtures, it is important to try and avoid * duplicating the fixture code across those tests. The more code duplication you have in your * tests, the greater drag the tests will have on refactoring the actual production code. * ScalaTest recommends several techniques to eliminate such code duplication, and provides several * traits to help. Each technique is geared towards helping you reduce code duplication without introducing * instance vars, shared mutable objects, or other dependencies between tests. Eliminating shared * mutable state across tests will make your test code easier to reason about and more amenable for parallel * test execution. *

* *

* The following sections * describe these techniques, including explaining the recommended usage * for each. But first, here's a table summarizing the options: *

* * * * * * * * * * * *
TechniqueRecommended uses
get-fixture methodsUse when you need the same mutable fixture objects in multiple tests, and don't need to clean up after.
fixture-context objectsUse when you need different combinations of mutable fixture objects in different tests, and don't need to clean up after.
OneInstancePerTestUse when porting JUnit tests to ScalaTest, or if you prefer JUnit's approach to test isolation: running each test in its own instance of the test class.
withFixture(NoArgTest)Use when you need to perform side effects at the beginning and end of all or most tests, or want to stack traits that perform such side-effects.
loan-fixture methodsUse when different tests need different fixtures that must be cleaned up afterwords.
withFixture(OneArgTest)Use when all or most tests need the same fixtures that must be cleaned up afterwords.
BeforeAndAfterUse when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests.
BeforeAndAfterEachUse when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests.
* * *

Calling get-fixture methods

* *

* If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or * more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or an holder object containing * multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each * test that needs the fixture, storing the returned object or objects in local variables. Here's an example: *

* *
 * package org.scalatest.examples.spec.getfixture
 * 
 * import org.scalatest.Spec
 * import collection.mutable.ListBuffer
 * 
 * class ExampleSpec extends Spec {
 * 
 *   def fixture = 
 *     new {
 *       val builder = new StringBuilder("ScalaTest is ")
 *       val buffer = new ListBuffer[String]
 *     }
 *   
 *   object `Testing ` {
 *     def `should be easy` {
 *       val f = fixture
 *       f.builder.append("easy!")
 *       assert(f.builder.toString === "ScalaTest is easy!")
 *       assert(f.buffer.isEmpty)
 *       f.buffer += "sweet"
 *     }
 *   
 *     def `should be fun` {
 *       val f = fixture
 *       f.builder.append("fun!")
 *       assert(f.builder.toString === "ScalaTest is fun!")
 *       assert(f.buffer.isEmpty)
 *     }
 *   }
 * }
 * 
* *

* The “f.” in front of each use of a fixture object provides a visual indication of which objects * are part of the fixture, but if you prefer, you can import the the members with “import f._” and use the names directly. *

* *

* If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, if you could pass * in an initial value for a mutable fixture object as a parameter to the get-fixture method. *

* * *

Instantiating fixture-context objects

* *

* An alternate technique that is especially useful when different tests need different combinations of fixture objects is to define the fixture objects as instance variables * of fixture-context objects whose instantiation forms the body of tests. Like get-fixture methods, fixture-context objects are only * appropriate if you don't need to clean up the fixtures after using them. *

* * To use this technique, you define instance variables intialized with fixture objects in traits and/or classes, then in each test instantiate an object that * contains just the fixture objects needed by the test. Traits allow you to mix together just the fixture objects needed by each test, whereas classes * allow you to pass data in via a constructor to configure the fixture objects. Here's an example in which fixture objects are partitioned into two traits * and each test just mixes together the traits it needs: *

* *
 * package org.scalatest.examples.spec.fixturecontext
 * 
 * import collection.mutable.ListBuffer
 * import org.scalatest.Spec
 * 
 * class ExampleSpec extends Spec {
 * 
 *   trait Builder {
 *     val builder = new StringBuilder("ScalaTest is ")
 *   }
 * 
 *   trait Buffer {
 *     val buffer = ListBuffer("ScalaTest", "is")
 *   }
 * 
 *   object `Testing ` {
 *     // This test needs the StringBuilder fixture
 *     def `should be productive` {
 *       new Builder {
 *         builder.append("productive!")
 *         assert(builder.toString === "ScalaTest is productive!")
 *       }
 *     }
 *   }
 * 
 *   object `Test code` {
 *     // This test needs the ListBuffer[String] fixture
 *     def `should be readable` {
 *       new Buffer {
 *         buffer += ("readable!")
 *         assert(buffer === List("ScalaTest", "is", "readable!"))
 *       }
 *     }
 * 
 *     // This test needs both the StringBuilder and ListBuffer
 *     def `should be clear and concise` {
 *       new Builder with Buffer {
 *         builder.append("clear!")
 *         buffer += ("concise!")
 *         assert(builder.toString === "ScalaTest is clear!")
 *         assert(buffer === List("ScalaTest", "is", "concise!"))
 *       }
 *     }
 *   }
 * }
 * 
* * *

Mixing in OneInstancePerTest

* *

* If every test method requires the same set of * mutable fixture objects, and none require cleanup, one other approach you can take is make them simply vals and mix in trait * OneInstancePerTest. If you mix in OneInstancePerTest, each test * will be run in its own instance of the Suite, similar to the way JUnit tests are executed. Here's an example: *

* *
 * package org.scalatest.examples.spec.oneinstancepertest
 * 
 * import org.scalatest._
 * import collection.mutable.ListBuffer
 * 
 * class ExampleSpec extends Spec with OneInstancePerTest {
 * 
 *   val builder = new StringBuilder("ScalaTest is ")
 *   val buffer = new ListBuffer[String]
 * 
 *   object `Testing ` {
 *     def `should be easy` {
 *       builder.append("easy!")
 *       assert(builder.toString === "ScalaTest is easy!")
 *       assert(buffer.isEmpty)
 *       buffer += "sweet"
 *     }
 * 
 *     def `should be fun` {
 *       builder.append("fun!")
 *       assert(builder.toString === "ScalaTest is fun!")
 *       assert(buffer.isEmpty)
 *     } 
 *   }
 * }
 * 
* *

* One way to think of OneInstancePerTest is that the entire Suite instance is like a fixture-context object, * but with the difference that the test code doesn't run during construction as it does with the real fixture-context object technique. Because this trait emulates JUnit's manner * of running tests, this trait can be helpful when porting JUnit tests to ScalaTest. The primary intended use of OneInstancePerTest is to serve as a supertrait for * ParallelTestExecution and the path traits, but you can also mix it in * directly to help you port JUnit tests to ScalaTest or if you prefer JUnit's approach to test isolation. *

* * *

Overriding withFixture(NoArgTest)

* *

* Although the get-fixture method, fixture-context object, and OneInstancePerTest approaches take care of setting up a fixture at the beginning of each * test, they don't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of * a test, and don't need to actually pass any fixture objects into the test, you can override withFixture(NoArgTest), one of ScalaTest's * lifecycle methods defined in trait Suite. *

* *

* Trait Suite's implementation of runTest passes a no-arg test function to withFixture(NoArgTest). It is withFixture's * responsibility to invoke that test function. Suite's implementation of withFixture simply * invokes the function, like this: *

* *
 * // Default implementation in trait Suite
 * protected def withFixture(test: NoArgTest) {
 *   test()
 * }
 * 
* *

* You can, therefore, override withFixture to perform setup before and/or cleanup after invoking the test function. If * you have cleanup to perform, you should invoke the test function inside a try block and perform the cleanup in * a finally clause, because the exception that causes a test to fail will propagate through withFixture back * to runTest. (In other words, if the test fails, the test function invoked by withFixture will throw an exception.) *

* *

* The withFixture method is designed to be stacked, and to enable this, you should always call the super implementation * of withFixture, and let it invoke the test function rather than invoking the test function directly. In other words, instead of writing * “test()”, you should write “super.withFixture(test)”, like this: *

* *
 * // Your implementation
 * override def withFixture(test: NoArgTest) {
 *   // Perform setup
 *   try super.withFixture(test) // Invoke the test function
 *   finally {
 *     // Perform cleanup
 *   }
 * }
 * 
* *

* Here's an example in which withFixture(NoArgTest) is used to take a snapshot of the working directory if a test fails, and * and send that information to the reporter: *

* *
 * package org.scalatest.examples.spec.noargtest
 * 
 * import java.io.File
 * import org.scalatest.Spec
 * 
 * class ExampleSpec extends Spec {
 * 
 *   override def withFixture(test: NoArgTest) {
 * 
 *     try super.withFixture(test)
 *     catch {
 *       case e: Exception =>
 *         val currDir = new File(".")
 *         val fileNames = currDir.list()
 *         info("Dir snapshot: " + fileNames.mkString(", "))
 *         throw e
 *     }
 *   }
 * 
 *   object `This test` {
 *     def `should succeed` {
 *       assert(1 + 1 === 2)
 *     }
 * 
 *     def `should fail` {
 *       assert(1 + 1 === 3)
 *     }
 *   }
 * }
 * 
* *

* Running this version of ExampleSuite in the interpreter in a directory with two files, hello.txt and world.txt * would give the following output: *

* *
 * scala> new ExampleSuite execute
 * ExampleSuite:
 * This test
 * - should fail *** FAILED ***
 *   2 did not equal 3 (:33)
 *   + Dir snapshot: hello.txt, world.txt 
 * - should succeed
 * 
* *

* Note that the NoArgTest passed to withFixture, in addition to * an apply method that executes the test, also includes the test name and the config * map passed to runTest. Thus you can also use the test name and configuration objects in your withFixture * implementation. *

* * *

Calling loan-fixture methods

* *

* If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. * If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. * A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the * function, then cleans up the fixture after the function returns. *

* *

* The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a * loan-fixture method. (In this example, the database is simulated with a StringBuffer.) *

* *
 * package org.scalatest.examples.spec.loanfixture
 * 
 * import java.util.concurrent.ConcurrentHashMap
 * 
 * object DbServer { // Simulating a database server
 *   type Db = StringBuffer
 *   private val databases = new ConcurrentHashMap[String, Db]
 *   def createDb(name: String): Db = {
 *     val db = new StringBuffer
 *     databases.put(name, db)
 *     db
 *   }
 *   def removeDb(name: String) {
 *     databases.remove(name)
 *   }
 * }
 * 
 * import org.scalatest.Spec
 * import DbServer._
 * import java.util.UUID.randomUUID
 * import java.io._
 * 
 * class ExampleSpec extends Spec {
 * 
 *   def withDatabase(testCode: Db => Any) {
 *     val dbName = randomUUID.toString
 *     val db = createDb(dbName) // create the fixture
 *     try {
 *       db.append("ScalaTest is ") // perform setup
 *       testCode(db) // "loan" the fixture to the test
 *     }
 *     finally removeDb(dbName) // clean up the fixture
 *   }
 * 
 *   def withFile(testCode: (File, FileWriter) => Any) {
 *     val file = File.createTempFile("hello", "world") // create the fixture
 *     val writer = new FileWriter(file)
 *     try {
 *       writer.write("ScalaTest is ") // set up the fixture
 *       testCode(file, writer) // "loan" the fixture to the test
 *     }
 *     finally writer.close() // clean up the fixture
 *   }
 * 
 *   object `Testing ` {
 *     // This test needs the file fixture
 *     def `should be productive` {
 *       withFile { (file, writer) =>
 *         writer.write("productive!")
 *         writer.flush()
 *         assert(file.length === 24)
 *       }
 *     }
 *   }
 *   
 *   object `Test code` {
 *     // This test needs the database fixture
 *     def `should be readable` {
 *       withDatabase { db =>
 *         db.append("readable!")
 *         assert(db.toString === "ScalaTest is readable!")
 *       }
 *     }
 * 
 *     // This test needs both the file and the database
 *     def `should be clear and concise` {
 *       withDatabase { db =>
 *        withFile { (file, writer) => // loan-fixture methods compose
 *           db.append("clear!")
 *           writer.write("concise!")
 *           writer.flush()
 *           assert(db.toString === "ScalaTest is clear!")
 *           assert(file.length === 21)
 *         }
 *       }
 *     }
 *   }
 * }
 * 
* *

* As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to * give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwords. *

* *

* Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures * involve external side-effects, like creating files or databases, it is a good idea to give each file or database a unique name as is * done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired. *

* *
* *

Overriding withFixture(OneArgTest)

* *

* If all or most tests need the same fixture, you can avoid some of the boilerplate of the loan-fixture method approach by using a fixture.Suite * and overriding withFixture(OneArgTest). * Each test in a fixture.Suite takes a fixture as a parameter, allowing you to pass the fixture into * the test. You must indicate the type of the fixture parameter by specifying FixtureParam, and implement a * withFixture method that takes a OneArgTest. This withFixture method is responsible for * invoking the one-arg test function, so you can perform fixture set up before, and clean up after, invoking and passing * the fixture into the test function. *

* *

* To enable the stacking of traits that define withFixture(NoArgTest), it is a good idea to let * withFixture(NoArgTest) invoke the test function instead of invoking the test * function directly. To do so, you'll need to convert the OneArgTest to a NoArgTest. You can do that by passing * the fixture object to the toNoArgTest method of OneArgTest. In other words, instead of * writing “test(theFixture)”, you'd delegate responsibility for * invoking the test function to the withFixture(NoArgTest) method of the same instance by writing: *

* *
 * withFixture(test.toNoArgTest(theFixture))
 * 
* *

* Here's a complete example: *

* *
 * package org.scalatest.examples.spec.oneargtest
 * 
 * import org.scalatest.fixture
 * import java.io._
 * 
 * class ExampleSpec extends fixture.Spec {
 * 
 *   case class F(file: File, writer: FileWriter)
 *   type FixtureParam = F
 * 
 *   def withFixture(test: OneArgTest) {
 *
 *     // create the fixture
 *     val file = File.createTempFile("hello", "world")
 *     val writer = new FileWriter(file)
 *     val theFixture = F(file, writer)
 *
 *     try {
 *       writer.write("ScalaTest is ") // set up the fixture
 *       withFixture(test.toNoArgTest(theFixture)) // "loan" the fixture to the test
 *     }
 *     finally writer.close() // clean up the fixture
 *   }
 *
 *   object `Testing ` {
 *     def `should be easy` { f: F =>
 *       f.writer.write("easy!")
 *       f.writer.flush()
 *       assert(f.file.length === 18)
 *     }
 * 
 *     def `should be fun` { f: F =>
 *       f.writer.write("fun!")
 *       f.writer.flush()
 *       assert(f.file.length === 17)
 *     }
 *   } 
 * }
 * 
* *

* In this example, the tests actually required two fixture objects, a File and a FileWriter. In such situations you can * simply define the FixtureParam type to be a tuple containing the objects, or as is done in this example, a case class containing * the objects. For more information on the withFixture(OneArgTest) technique, see the documentation for fixture.Suite. *

* * *

Mixing in BeforeAndAfter

* *

* In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been * performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. * Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an * exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is * to mix in trait BeforeAndAfter. With this trait you can denote a bit of code to run before each test * with before and/or after each test each test with after, like this: *

* *
 * package org.scalatest.examples.spec.beforeandafter
 * 
 * import org.scalatest.Spec
 * import org.scalatest.BeforeAndAfter
 * import collection.mutable.ListBuffer
 * 
 * class ExampleSpec extends Spec with BeforeAndAfter {
 * 
 *   val builder = new StringBuilder
 *   val buffer = new ListBuffer[String]
 * 
 *   before {
 *     builder.append("ScalaTest is ")
 *   }
 * 
 *   after {
 *     builder.clear()
 *     buffer.clear()
 *   }
 * 
 *   object `Testing ` {
 *     def `should be easy` {
 *       builder.append("easy!")
 *       assert(builder.toString === "ScalaTest is easy!")
 *       assert(buffer.isEmpty)
 *       buffer += "sweet"
 *     }
 * 
 *     def `should be fun` {
 *       builder.append("fun!")
 *       assert(builder.toString === "ScalaTest is fun!")
 *       assert(buffer.isEmpty)
 *     }
 *   }
 * }
 * 
* *

* Note that the only way before and after code can communicate with test code is via some side-effecting mechanism, commonly by * reassigning instance vars or by changing the state of mutable objects held from instance vals (as in this example). If using * instance vars or mutable objects held from instance vals you wouldn't be able to run tests in parallel in the same instance * of the test class unless you synchronized access to the shared, mutable state. This is why ScalaTest's ParallelTestExecution trait extends * OneInstancePerTest. By running each test in its own instance of the class, each test has its own copy of the instance variables, so you * don't need to synchronize. If you mixed ParallelTestExecution into the ExampleSuite above, the tests would run in parallel just fine * without any synchronization needed on the mutable StringBuilder and ListBuffer[String] objects. *

* *

* Although BeforeAndAfter provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable * traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you * should use trait BeforeAndAfterEach instead, as shown later in the next section, * composing fixtures by stacking traits. *

* *

Composing fixtures by stacking traits

* *

* In larger projects, teams often end up with several different fixtures that test classes need in different combinations, * and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual * fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing * withFixture methods in several traits, each of which call super.withFixture. Here's an example in * which the StringBuilder and ListBuffer[String] fixtures used in the previous examples have been * factored out into two stackable fixture traits named Builder and Buffer: *

* *
 * package org.scalatest.examples.spec.composingwithfixture
 * 
 * import org.scalatest._
 * import collection.mutable.ListBuffer
 * 
 * trait Builder extends SuiteMixin { this: Suite =>
 * 
 *   val builder = new StringBuilder
 * 
 *   abstract override def withFixture(test: NoArgTest) {
 *     builder.append("ScalaTest is ")
 *     try super.withFixture(test) // To be stackable, must call super.withFixture
 *     finally builder.clear()
 *   }
 * }
 * 
 * trait Buffer extends SuiteMixin { this: Suite =>
 * 
 *   val buffer = new ListBuffer[String]
 * 
 *   abstract override def withFixture(test: NoArgTest) {
 *     try super.withFixture(test) // To be stackable, must call super.withFixture
 *     finally buffer.clear()
 *   }
 * }
 * 
 * class ExampleSpec extends Spec with Builder with Buffer {
 * 
 *   object `Testing ` {
 *     def `should be easy` {
 *       builder.append("easy!")
 *       assert(builder.toString === "ScalaTest is easy!")
 *       assert(buffer.isEmpty)
 *       buffer += "sweet"
 *     }
 * 
 *     def `should be fun` {
 *       builder.append("fun!")
 *       assert(builder.toString === "ScalaTest is fun!")
 *       assert(buffer.isEmpty)
 *       buffer += "clear"
 *     }
 *   }
 * }
 * 
* *

* By mixing in both the Builder and Buffer traits, ExampleSpec gets both fixtures, which will be * initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. * In this case, Builder is “super” to Buffer. If you wanted Buffer to be “super” * to Builder, you need only switch the order you mix them together, like this: *

* *
 * class Example2Spec extends Spec with Buffer with Builder
 * 
* *

* And if you only need one fixture you mix in only that trait: *

* *
 * class Example3Spec extends Spec with Builder
 * 
* *

* Another way to create stackable fixture traits is by extending the BeforeAndAfterEach * and/or BeforeAndAfterAll traits. * BeforeAndAfterEach has a beforeEach method that will be run before each test (like JUnit's setUp), * and an afterEach method that will be run after (like JUnit's tearDown). * Similarly, BeforeAndAfterAll has a beforeAll method that will be run before all tests, * and an afterAll method that will be run after all tests. Here's what the previously shown example would look like if it * were rewritten to use the BeforeAndAfterEach methods instead of withFixture: *

* *
 * package org.scalatest.examples.spec.composingbeforeandaftereach
 * 
 * import org.scalatest._
 * import org.scalatest.BeforeAndAfterEach
 * import collection.mutable.ListBuffer
 * 
 * trait Builder extends BeforeAndAfterEach { this: Suite =>
 * 
 *   val builder = new StringBuilder
 * 
 *   override def beforeEach() {
 *     builder.append("ScalaTest is ")
 *     super.beforeEach() // To be stackable, must call super.beforeEach
 *   }
 * 
 *   override def afterEach() {
 *     try super.afterEach() // To be stackable, must call super.afterEach
 *     finally builder.clear()
 *   }
 * }
 * 
 * trait Buffer extends BeforeAndAfterEach { this: Suite =>
 * 
 *   val buffer = new ListBuffer[String]
 * 
 *   override def afterEach() {
 *     try super.afterEach() // To be stackable, must call super.afterEach
 *     finally buffer.clear()
 *   }
 * }
 * 
 * class ExampleSpec extends Spec with Builder with Buffer {
 * 
 *   object `Testing ` {
 *     def `should be easy` {
 *       builder.append("easy!")
 *       assert(builder.toString === "ScalaTest is easy!")
 *       assert(buffer.isEmpty)
 *       buffer += "sweet"
 *     }
 * 
 *     def `should be fun` {
 *       builder.append("fun!")
 *       assert(builder.toString === "ScalaTest is fun!")
 *       assert(buffer.isEmpty)
 *       buffer += "clear"
 *     }
 *   }
 * }
 * 
* *

* To get the same ordering as withFixture, place your super.beforeEach call at the end of each * beforeEach method, and the super.afterEach call at the beginning of each afterEach * method, as shown in the previous example. It is a good idea to invoke super.afterEach in a try * block and perform cleanup in a finally clause, as shown in the previous example, because this ensures the * cleanup code is performed even if super.afterEach throws an exception. *

* *

* The difference between stacking traits that extend BeforeAndAfterEach versus traits that implement withFixture is * that setup and cleanup code happens before and after the test in BeforeAndAfterEach, but at the beginning and * end of the test in withFixture. Thus if a withFixture method completes abruptly with an exception, it is * considered a failed test. By contrast, if any of the beforeEach or afterEach methods of BeforeAndAfterEach * complete abruptly, it is considered an aborted suite, which will result in a SuiteAborted event. *

* *

Shared tests

* *

* Because Spec represents tests as methods, you cannot share or otherwise dynamically generate tests. Instead, use static code generation * if you want to generate tests in a Spec. In other words, write a program that statically generates the entire source file of * a Spec subclass. *

* * @author Bill Venners */ @Finders(Array("org.scalatest.finders.SpecFinder")) trait Spec extends Suite { thisSuite => private final val engine = new Engine("concurrentSpecMod", "Spec") import engine._ // Sychronized on thisSuite, only accessed from ensureScopesAndTestsRegistered private var scopesRegistered = false private def ensureScopesAndTestsRegistered() { thisSuite.synchronized { if (!scopesRegistered) { scopesRegistered = true def getMethod(o: AnyRef, methodName: String) = { o.getClass.getMethod( simpleNameForTest(methodName), new Array[Class[_]](0): _* ) } def getMethodTags(o: AnyRef, methodName: String) = for { a <- getMethod(o, methodName).getDeclaredAnnotations annotationClass = a.annotationType if annotationClass.isAnnotationPresent(classOf[TagAnnotation]) } yield annotationClass.getName def getScopeClassName(o: AnyRef): String = { val className = o.getClass.getName if (className.endsWith("$")) className else className + "$" } def isScopeMethod(o: AnyRef, m: Method): Boolean = { val scopeMethodName = getScopeClassName(o)+ m.getName + "$" val returnTypeName = m.getReturnType.getName equalIfRequiredCompactify(scopeMethodName, returnTypeName) } def getScopeDesc(m: Method): String = { val objName = m.getReturnType.getName val objClassName = decode(objName.substring(0, objName.length - 1)) objClassName.substring(objClassName.lastIndexOf("$") + 1) } val testTags = tags object MethodNameEncodedOrdering extends Ordering[Method] { def compare(x: Method, y: Method): Int = { decode(x.getName) compareTo decode(y.getName) } } def register(o: AnyRef) { val testMethods = o.getClass.getMethods.filter(isTestMethod(_)).sorted(MethodNameEncodedOrdering) testMethods.foreach { m => val scope = isScopeMethod(o, m) if (scope) { val scopeDesc = getScopeDesc(m) def scopeFun = try { val scopeObj = m.invoke(o) register(scopeObj) } catch { case ite: InvocationTargetException if ite.getTargetException != null => throw ite.getTargetException } val scopeLocation = TopOfClass(m.getReturnType.getName) registerNestedBranch(scopeDesc, None, scopeFun, "registrationAlreadyClosed", sourceFileName, "ensureScopesAndTestsRegistered", 2, 0, Some(scopeLocation)) } else { val methodName = m.getName val testName = decode(methodName) val methodTags = getMethodTags(o, methodName) val testFun: () => Unit = () => { val argsArray: Array[Object] = Array.empty try m.invoke(o, argsArray: _*) catch { case ite: InvocationTargetException => throw ite.getTargetException } } val testLocation = TopOfMethod(getScopeClassName(o), m.toGenericString) val isIgnore = testTags.get(methodName) match { case Some(tagSet) => tagSet.contains(Suite.IgnoreAnnotation) || methodTags.contains(Suite.IgnoreAnnotation) case None => methodTags.contains(Suite.IgnoreAnnotation) } if (isIgnore) registerIgnoredTest(testName, testFun, "registrationAlreadyClosed", sourceFileName, "ensureScopesAndTestsRegistered", 3, 0, Some(testLocation), methodTags.map(new Tag(_)): _*) else registerTest(testName, testFun, "registrationAlreadyClosed", sourceFileName, "ensureScopesAndTestsRegistered", 2, 1, None, Some(testLocation), None, methodTags.map(new Tag(_)): _*) } } } register(thisSuite) } } } // TODO: Probably make this private final val sourceFileName in a singleton object so it gets compiled in rather than carried around in each instance private[scalatest] val sourceFileName = "Spec.scala" /** * Returns an Informer that during test execution will forward strings (and other objects) passed to its * apply method to the current reporter. If invoked in a constructor, it * will register the passed string for forwarding later during test execution. If invoked while this * Spec is being executed, such as from inside a test function, it will forward the information to * the current reporter immediately. If invoked at any other time, it will * throw an exception. This method can be called safely by any thread. */ implicit protected def info: Informer = atomicInformer.get /** * Returns a Documenter that during test execution will forward strings passed to its * apply method to the current reporter. If invoked in a constructor, it * will register the passed string for forwarding later during test execution. If invoked while this * Spec is being executed, such as from inside a test function, it will forward the information to * the current reporter immediately. If invoked at any other time, it will * throw an exception. This method can be called safely by any thread. */ implicit protected def markup: Documenter = atomicDocumenter.get /** * An immutable Set of test names. If this Spec contains no tests, this method returns an * empty Set. * *

* This trait's implementation of this method will return a set that contains the names of all registered tests. The set's * iterator will return those names in the order in which the tests were registered. Each test's name is composed * of the concatenation of the text of each surrounding describer, in order from outside in, and the text of the * example itself, with all components separated by a space. For example, consider this Spec: *

* *
   * import org.scalatest.Spec
   *
   * class StackSpec extends Spec {
   *   object `A Stack` {
   *     object `(when not empty)` {
   *       def `must allow me to pop` {}
   *     }
   *     object `(when not full)` {
   *       def `must allow me to push` {}
   *     }
   *   }
   * }
   * 
* *

* Invoking testNames on this Spec will yield a set that contains the following * two test name strings: *

* *
   * "A Stack (when not empty) must allow me to pop"
   * "A Stack (when not full) must allow me to push"
   * 
*/ override def testNames: Set[String] = { ensureScopesAndTestsRegistered() // I'm returning a ListSet here so that they tests will be run in registration order ListSet(atomic.get.testNamesList.toArray: _*) } /** * Run a test. This trait's implementation runs the test registered with the name specified by * testName. Each test's name is a concatenation of the text of all describers surrounding a test, * from outside in, and the test's spec text, with one space placed between each item. (See the documenation * for testNames for an example.) * * @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 . * * @throws NullPointerException if any of testName, reporter, stopper, or configMap * is null. */ protected override def runTest(testName: String, args: Args): Status = { ensureScopesAndTestsRegistered() def invokeWithFixture(theTest: TestLeaf) { val theConfigMap = args.configMap val testData = testDataFor(testName, theConfigMap) withFixture( new NoArgTest { val name = testData.name def apply() { theTest.testFun() } val configMap = testData.configMap val scopes = testData.scopes val text = testData.text val tags = testData.tags } ) } runTestImpl(thisSuite, testName, args, true, invokeWithFixture) } final override def expectedTestCount(filter: Filter): Int = { ensureScopesAndTestsRegistered() super.expectedTestCount(filter) } /** * A Map whose keys are String tag names to which tests in this Spec belong, and values * the Set of test names that belong to each tag. If this Spec contains no tags, this method returns an empty Map. * *

* This trait's implementation returns tags that were passed as strings contained in Tag objects passed to * methods test and ignore. *

* *

* In addition, this trait's implementation will also auto-tag tests with class level annotations. * For example, if you annotate @Ignore at the class level, all test methods in the class will be auto-annotated with @Ignore. *

*/ override def tags: Map[String, Set[String]] = { ensureScopesAndTestsRegistered() autoTagClassAnnotations(atomic.get.tagsMap, this) } /** * Run zero to many of this Spec's tests. * * @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 */ protected override def runTests(testName: Option[String], args: Args): Status = { ensureScopesAndTestsRegistered() runTestsImpl(thisSuite, testName, args, info, true, runTest) } override def run(testName: Option[String], args: Args): Status = { ensureScopesAndTestsRegistered() runImpl(thisSuite, testName, args, super.run) } /** * Suite style name. */ final override val styleName: String = "org.scalatest.Spec" override def testDataFor(testName: String, theConfigMap: Map[String, Any] = Map.empty): TestData = createTestDataFor(testName, theConfigMap, this) } private[scalatest] object Spec { def isTestMethod(m: Method): Boolean = { val isInstanceMethod = !Modifier.isStatic(m.getModifiers()) val hasNoParams = m.getParameterTypes.isEmpty // name must have at least one encoded space: "$u0220" val includesEncodedSpace = m.getName.indexOf("$u0020") >= 0 val isOuterMethod = m.getName.endsWith("$$outer") val isNestedMethod = m.getName.matches(".+\\$\\$.+\\$[1-9]+") //val isOuterMethod = m.getName.endsWith("$$$outer") // def maybe(b: Boolean) = if (b) "" else "!" // println("m.getName: " + m.getName + ": " + maybe(isInstanceMethod) + "isInstanceMethod, " + maybe(hasNoParams) + "hasNoParams, " + maybe(includesEncodedSpace) + "includesEncodedSpace") isInstanceMethod && hasNoParams && includesEncodedSpace && !isOuterMethod && !isNestedMethod } import java.security.MessageDigest import scala.io.Codec // The following compactify code is written based on scala compiler source code at:- // https://github.com/scala/scala/blob/master/src/reflect/scala/reflect/internal/StdNames.scala#L47 private val compactifiedMarker = "$$$$" def equalIfRequiredCompactify(value: String, compactified: String): Boolean = { if (compactified.matches(".+\\$\\$\\$\\$.+\\$\\$\\$\\$.+")) { val firstDolarIdx = compactified.indexOf("$$$$") val lastDolarIdx = compactified.lastIndexOf("$$$$") val prefix = compactified.substring(0, firstDolarIdx) val suffix = compactified.substring(lastDolarIdx + 4) val lastIndexOfDot = value.lastIndexOf(".") val toHash = if (lastIndexOfDot >= 0) value.substring(0, value.length - 1).substring(value.lastIndexOf(".") + 1) else value val bytes = Codec.toUTF8(toHash.toArray) val md5 = MessageDigest.getInstance("MD5") md5.update(bytes) val md5chars = (md5.digest() map (b => (b & 0xFF).toHexString)).mkString (prefix + compactifiedMarker + md5chars + compactifiedMarker + suffix) == compactified } else value == compactified } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy