org.scalatest.prop.TableDrivenPropertyChecks.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.11.0-RC2 Show documentation
/*
* 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 Int
s, 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:
*
*
*
* n
d
* Integer.MIN_VALUE
Integer.MIN_VALUE
* a valid value Integer.MIN_VALUE
* Integer.MIN_VALUE
a valid value
* Integer.MIN_VALUE
zero
* a valid value zero
*
*
*
* 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