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

org.scalatest.prop.TableDrivenPropertyChecks.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2001-2014 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
package prop

/**
 * Trait containing methods that faciliate property checks against tables of data.
 *
 * 

* This trait contains one forAll method for each TableForN class, TableFor1 * through TableFor22, which allow properties to be checked against the rows of a table. It also * contains a wherever method that can be used to indicate a property need only hold whenever some * condition is true. *

* *

* For an example of trait TableDrivenPropertyChecks in action, imagine you want to test this Fraction class: *

* *
 * class Fraction(n: Int, d: Int) {
 *
 *   require(d != 0)
 *   require(d != Integer.MIN_VALUE)
 *   require(n != Integer.MIN_VALUE)
 *
 *   val numer = if (d < 0) -1 * n else n
 *   val denom = d.abs
 *
 *   override def toString = numer + " / " + denom
 * }
 * 
* *

* TableDrivenPropertyChecks allows you to create tables with * between 1 and 22 columns and any number of rows. You create a table by passing * tuples to one of the factory methods of object Table. Each tuple must have the * same arity (number of members). The first tuple you pass must all be strings, because * it define names for the columns. Subsequent tuples define the data. After the initial tuple * that contains string column names, all tuples must have the same type. For example, * if the first tuple after the column names contains two Ints, all subsequent * tuples must contain two Int (i.e., have type * Tuple2[Int, Int]). *

* *

* To test the behavior of Fraction, you could create a table * of numerators and denominators to pass to the constructor of the * Fraction class using one of the apply factory methods declared * in Table, like this: *

* *
 * import org.scalatest.prop.TableDrivenPropertyChecks._
 *
 * val fractions =
 *   Table(
 *     ("n", "d"),  // First tuple defines column names
 *     (  1,   2),  // Subsequent tuples define the data
 *     ( -1,   2),
 *     (  1,  -2),
 *     ( -1,  -2),
 *     (  3,   1),
 *     ( -3,   1),
 *     ( -3,   0),
 *     (  3,  -1),
 *     (  3,  Integer.MIN_VALUE),
 *     (Integer.MIN_VALUE, 3),
 *     ( -3,  -1)
 *   )
 * 
* *

* You could then check a property against each row of the table using a forAll method, like this: *

* *
 * import org.scalatest.matchers.ShouldMatchers._
 *
 * forAll (fractions) { (n: Int, d: Int) =>
 *
 *   whenever (d != 0 && d != Integer.MIN_VALUE
 *       && n != Integer.MIN_VALUE) {
 *
 *     val f = new Fraction(n, d)
 *
 *     if (n < 0 && d < 0 || n > 0 && d > 0)
 *       f.numer should be > 0
 *     else if (n != 0)
 *       f.numer should be < 0
 *     else
 *       f.numer should be === 0
 *
 *     f.denom should be > 0
 *   }
 * }
 * 
* *

* Trait TableDrivenPropertyChecks provides 22 overloaded forAll methods * that allow you to check properties using the data provided by a table. Each forAll * method takes two parameter lists. The first parameter list is a table. The second parameter list * is a function whose argument types and number matches that of the tuples in the table. For * example, if the tuples in the table supplied to forAll each contain an * Int, a String, and a List[Char], then the function supplied * to forAll must take 3 parameters, an Int, a String, * and a List[Char]. The forAll method will pass each row of data to * the function, and generate a TableDrivenPropertyCheckFailedException if the function * completes abruptly for any row of data with any exception that would normally cause a test to * fail in ScalaTest other than DiscardedEvaluationException. An * DiscardedEvaluationException, * which is thrown by the whenever method (also defined in this trait) to indicate * a condition required by the property function is not met by a row * of passed data, will simply cause forAll to skip that row of data. *

* *

Testing stateful functions

* *

* One way to use a table with one column is to test subsequent return values * of a stateful function. Imagine, for example, you had an object named FiboGen * whose next method returned the next fibonacci number, where next * means the next number in the series following the number previously returned by next. * So the first time next was called, it would return 0. The next time it was called * it would return 1. Then 1. Then 2. Then 3, and so on. FiboGen would need to * maintain state, because it has to remember where it is in the series. In such a situation, * you could create a TableFor1 (a table with one column, which you could alternatively * think of as one row), in which each row represents * the next value you expect. *

* *
 * val first14FiboNums =
 *   Table("n", 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233)
 * 
* *

* Then in your forAll simply call the function and compare it with the * expected return value, like this: *

* *
 *  forAll (first14FiboNums) { n =>
 *    FiboGen.next should equal (n)
 *  }
 * 
* *

Testing mutable objects

* *

* If you need to test a mutable object, one way you can use tables is to specify * state transitions in a table. For example, imagine you wanted to test this mutable * Counter class: * *

      class Counter {
        private var c = 0
        def reset() { c = 0 }
        def click() { c += 1 }
        def enter(n: Int) { c = n }
        def count = c
      }
 * 
* *

* A Counter keeps track of how many times its click method * is called. The count starts out at zero and increments with each click * invocation. You can also set the count to a specific value by calling enter * and passing the value in. And the reset method returns the count back to * zero. You could define the actions that initiate state transitions with case classes, like this: *

* *
      abstract class Action
      case object Start extends Action
      case object Click extends Action
      case class Enter(n: Int) extends Action
 * 
* *

* Given these actions, you could define a state-transition table like this: *

* *
      val stateTransitions =
        Table(
          ("action", "expectedCount"),
          (Start,    0),
          (Click,    1),
          (Click,    2),
          (Click,    3),
          (Enter(5), 5),
          (Click,    6),
          (Enter(1), 1),
          (Click,    2),
          (Click,    3)
        )
 * 
* *

* To use this in a test, simply do a pattern match inside the function you pass * to forAll. Make a pattern for each action, and have the body perform that * action when there's a match. Then check that the actual value equals the expected value: *

* *
      val counter = new Counter
      forAll (stateTransitions) { (action, expectedCount) =>
        action match {
          case Start => counter.reset()
          case Click => counter.click()
          case Enter(n) => counter.enter(n)
        }
        counter.count should equal (expectedCount)
      }
 * 
* *

Testing invalid argument combinations

* *

* A table-driven property check can also be helpful to ensure that the proper exception is thrown when invalid data is * passed to a method or constructor. For example, the Fraction constructor shown above should throw IllegalArgumentException * if Integer.MIN_VALUE is passed for either the numerator or denominator, or zero is passed for the denominator. This yields the * following five combinations of invalid data: *

* * * * * * * * *
nd
Integer.MIN_VALUEInteger.MIN_VALUE
a valid valueInteger.MIN_VALUE
Integer.MIN_VALUEa valid value
Integer.MIN_VALUEzero
a valid valuezero
* *

* You can express these combinations in a table: *

* *
 * val invalidCombos =
 *   Table(
 *     ("n",               "d"),
 *     (Integer.MIN_VALUE, Integer.MIN_VALUE),
 *     (1,                 Integer.MIN_VALUE),
 *     (Integer.MIN_VALUE, 1),
 *     (Integer.MIN_VALUE, 0),
 *     (1,                 0)
 *   )
 * 
* *

* Given this table, you could check that all invalid combinations produce IllegalArgumentException, like this: *

* *
 * forAll (invalidCombos) { (n: Int, d: Int) =>
 *   evaluating {
 *     new Fraction(n, d)
 *   } should produce [IllegalArgumentException]
 * }
 * 
* *

* @author Bill Venners */ trait TableDrivenPropertyChecks extends Whenever with Tables { /* * Evaluates the passed code block if the passed boolean condition is true, else throws DiscardedEvaluationException. * *

* The whenever method can be used inside property check functions to skip invocations of the function with * data for which it is known the property would fail. For example, given the following Fraction class: *

* *
   * class Fraction(n: Int, d: Int) {
   *
   *   require(d != 0)
   *   require(d != Integer.MIN_VALUE)
   *   require(n != Integer.MIN_VALUE)
   *
   *   val numer = if (d < 0) -1 * n else n
   *   val denom = d.abs
   *
   *   override def toString = numer + " / " + denom
   * }
   * 
* *

* You could create a table of numerators and denominators to pass to the constructor of the * Fraction class like this: *

* *
   * import org.scalatest.prop.TableDrivenPropertyChecks._
   *
   * val fractions =
   *   Table(
   *     ("n", "d"),
   *     (  1,   2),
   *     ( -1,   2),
   *     (  1,  -2),
   *     ( -1,  -2),
   *     (  3,   1),
   *     ( -3,   1),
   *     ( -3,   0),
   *     (  3,  -1),
   *     (  3,  Integer.MIN_VALUE),
   *     (Integer.MIN_VALUE, 3),
   *     ( -3,  -1)
   *   )
   * 
* *

* Imagine you wanted to check a property against this class with data that includes some * value that are rejected by the constructor, such as a denominator of zero, which should * result in an IllegalArgumentException. You could use whenever * to skip any rows in the fraction that represent illegal arguments, like this: *

* *
   * import org.scalatest.matchers.ShouldMatchers._
   *
   * forAll (fractions) { (n: Int, d: Int) =>
   *
   *   whenever (d != 0 && d != Integer.MIN_VALUE
   *       && n != Integer.MIN_VALUE) {
   *
   *     val f = new Fraction(n, d)
   *
   *     if (n < 0 && d < 0 || n > 0 && d > 0)
   *       f.numer should be > 0
   *     else if (n != 0)
   *       f.numer should be < 0
   *     else
   *       f.numer should be === 0
   *
   *     f.denom should be > 0
   *   }
   * }
   * 
* *

* In this example, rows 6, 8, and 9 have values that would cause a false to be passed * to whenever. (For example, in row 6, d is 0, which means d != 0 * will be false.) For those rows, whenever will throw DiscardedEvaluationException, * which will cause the forAll method to skip that row. *

* * @param condition the boolean condition that determines whether whenever will evaluate the * fun function (condition is true) or throws DiscardedEvaluationException (condition is false) * @param fun the function to evaluate if the specified condition is true */ /* def whenever(condition: Boolean)(fun: => Unit) { if (!condition) throw new DiscardedEvaluationException fun } */ /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor1. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A](table: TableFor1[A])(fun: (A) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor2. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B](table: TableFor2[A, B])(fun: (A, B) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor3. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C](table: TableFor3[A, B, C])(fun: (A, B, C) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor4. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D](table: TableFor4[A, B, C, D])(fun: (A, B, C, D) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor5. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E](table: TableFor5[A, B, C, D, E])(fun: (A, B, C, D, E) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor6. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F](table: TableFor6[A, B, C, D, E, F])(fun: (A, B, C, D, E, F) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor7. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G](table: TableFor7[A, B, C, D, E, F, G])(fun: (A, B, C, D, E, F, G) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor8. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H](table: TableFor8[A, B, C, D, E, F, G, H])(fun: (A, B, C, D, E, F, G, H) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor9. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I](table: TableFor9[A, B, C, D, E, F, G, H, I])(fun: (A, B, C, D, E, F, G, H, I) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor10. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J](table: TableFor10[A, B, C, D, E, F, G, H, I, J])(fun: (A, B, C, D, E, F, G, H, I, J) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor11. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K](table: TableFor11[A, B, C, D, E, F, G, H, I, J, K])(fun: (A, B, C, D, E, F, G, H, I, J, K) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor12. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L](table: TableFor12[A, B, C, D, E, F, G, H, I, J, K, L])(fun: (A, B, C, D, E, F, G, H, I, J, K, L) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor13. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M](table: TableFor13[A, B, C, D, E, F, G, H, I, J, K, L, M])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor14. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N](table: TableFor14[A, B, C, D, E, F, G, H, I, J, K, L, M, N])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor15. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O](table: TableFor15[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor16. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P](table: TableFor16[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor17. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q](table: TableFor17[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor18. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R](table: TableFor18[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor19. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S](table: TableFor19[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor20. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T](table: TableFor20[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor21. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U](table: TableFor21[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U) => Unit) { table(fun) } /** * Performs a property check by applying the specified property check function to each row * of the specified TableFor22. * * @param table the table of data with which to perform the property check * @param fun the property check function to apply to each row of data in the table */ def forAll[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V](table: TableFor22[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V])(fun: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) => Unit) { table(fun) } } /* * Companion object that facilitates the importing of TableDrivenPropertyChecks members as * an alternative to mixing it in. One use case is to import TableDrivenPropertyChecks members so you can use * them in the Scala interpreter: * *
 * Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_22).
 * Type in expressions to have them evaluated.
 * Type :help for more information.
 *
 * scala> import org.scalatest.prop.TableDrivenPropertyChecks._
 * import org.scalatest.prop.TableDrivenPropertyChecks._
 *
 * scala> val examples =                                       
 *   |   Table(                                             
 *   |     ("a", "b"),                                      
 *   |     (  1,   2),                                      
 *   |     (  3,   4)                                       
 *   |   )
 * examples: org.scalatest.prop.TableFor2[Int,Int] = TableFor2((1,2), (3,4))
 *
 * scala> import org.scalatest.matchers.ShouldMatchers._
 * import org.scalatest.matchers.ShouldMatchers._
 *
 * scala> forAll (examples) { (a, b) => a should be < b }
 * 
 * scala> forAll (examples) { (a, b) => a should be > b }
 * org.scalatest.prop.TableDrivenPropertyCheckFailedException: TestFailedException (included as this exception's cause) was thrown during property evaluation.
 * Message: 1 was not greater than 2
 * Location: :13
 * Occurred at table row 0 (zero based, not counting headings), which had values (
 *   a = 1,
 *   b = 2
 * )
 * at org.scalatest.prop.TableFor2$$anonfun$apply$4.apply(Table.scala:355)
 * at org.scalatest.prop.TableFor2$$anonfun$apply$4.apply(Table.scala:346)
 * at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:57)
 * at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:43)
 * at org.scalatest.prop.TableFor2.apply(Table.scala:346)
 * at org.scalatest.prop.TableDrivenPropertyChecks$class.forAll(TableDrivenPropertyChecks.scala:133)
 * ...
 * 
* * @author Bill Venners */ object TableDrivenPropertyChecks extends TableDrivenPropertyChecks