diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index a223140eb..eaf91e920 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -139,7 +139,7 @@ class Application @Inject() (cc: ControllerComponents) extends AbstractControlle object Application { - val latestVersion = "3.2.19" + val latestVersion = "3.3.0" val latestSuperSafeVersion = "1.1.12" val supersafeScalaVersion = "2.13.14" val milestoneVersion = "3.2.0" @@ -159,13 +159,13 @@ object Application { val latestJUnit5Version = "5-10" val latestMockitoVersion = "5-12" val latestScalaCheckVersion = "1-18" - val latestScalaCheckPlusVersion = "3.2.19.0" + val latestScalaCheckPlusVersion = "3.3.0.0" val latestTestNGVersion = "7-10" val quickStartXmlJar = "https://repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_3/2.3.0/scala-xml_3-2.3.0.jar" - val latestPlusScalaCheckDoc = "plus-scalacheck-1.18/3.2.19.0" - val latestPlusEasyMockDoc = "plus-easymock-5.3/3.2.19.0" - val latestPlusJMockDoc = "plus-jmock-2.13/3.2.19.0" - val latestPlusMockitoDoc = "plus-mockito-5.12/3.2.19.0" + val latestPlusScalaCheckDoc = "plus-scalacheck-1.18/3.3.0.0" + val latestPlusEasyMockDoc = "plus-easymock-5.3/3.3.0.0" + val latestPlusJMockDoc = "plus-jmock-2.13/3.3.0.0" + val latestPlusMockitoDoc = "plus-mockito-5.12/3.3.0.0" def scaladocsPageUrl(file: String, version: String = latestVersion): String = { val oldScaladocStyle30Releases = List("3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4") diff --git a/app/controllers/ReleaseNotes.scala b/app/controllers/ReleaseNotes.scala index 7c6204266..ff70a76c7 100644 --- a/app/controllers/ReleaseNotes.scala +++ b/app/controllers/ReleaseNotes.scala @@ -25,6 +25,9 @@ class ReleaseNotes @Inject() (cc: ControllerComponents) extends AbstractControll Ok(views.html.releaseNotes.releaseNotesIndex()) } + def v330 = Action { + Ok(views.html.releaseNotes.v330()) + } def v3219 = Action { Ok(views.html.releaseNotes.v3219()) } diff --git a/app/controllers/UserGuide.scala b/app/controllers/UserGuide.scala index 13e07950f..2013cec30 100644 --- a/app/controllers/UserGuide.scala +++ b/app/controllers/UserGuide.scala @@ -69,6 +69,10 @@ class UserGuide @Inject() (cc: ControllerComponents) extends AbstractController( Ok(views.html.userGuide.propertyBasedTesting()) } + def scalacheckPropertyBasedTesting = Action { + Ok(views.html.userGuide.scalacheckPropertyBasedTesting()) + } + def otherGoodies = Action { Ok(views.html.userGuide.otherGoodies()) } @@ -97,10 +101,18 @@ class UserGuide @Inject() (cc: ControllerComponents) extends AbstractController( Ok(views.html.userGuide.tableDrivenPropertyChecks()) } + def scalacheckTableDrivenPropertyChecks = Action { + Ok(views.html.userGuide.scalacheckTableDrivenPropertyChecks()) + } + def generatorDrivenPropertyChecks = Action { Ok(views.html.userGuide.generatorDrivenPropertyChecks()) } + def scalacheckDrivenPropertyChecks = Action { + Ok(views.html.userGuide.scalacheckDrivenPropertyChecks()) + } + def writingScalacheckStyleProperties = Action { Ok(views.html.userGuide.writingScalacheckStyleProperties()) } diff --git a/app/views/homePage.scala.html b/app/views/homePage.scala.html index 5a108a585..c79cebb65 100644 --- a/app/views/homePage.scala.html +++ b/app/views/homePage.scala.html @@ -25,7 +25,7 @@ height="200" alt="ScalaTest: Simply Productive"/>
-Latest Release - ScalaTest and Scalactic @latestVersion! +Latest Release - ScalaTest and Scalactic @latestVersion!
} diff --git a/app/views/plus/easymockVersions.scala.html b/app/views/plus/easymockVersions.scala.html index a4d024f7f..ef44e2ebf 100644 --- a/app/views/plus/easymockVersions.scala.html +++ b/app/views/plus/easymockVersions.scala.html @@ -31,6 +31,12 @@ScalaTest + EasyMock Version | ScalaTest Version | EasyMock Versions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +5.3 | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/jmockVersions.scala.html b/app/views/plus/jmockVersions.scala.html index d77648f68..f57d77dc1 100644 --- a/app/views/plus/jmockVersions.scala.html +++ b/app/views/plus/jmockVersions.scala.html @@ -31,6 +31,12 @@
ScalaTest + JMock Version | ScalaTest Version | JMock Versions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +2.13.x | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/junit4Versions.scala.html b/app/views/plus/junit4Versions.scala.html index dc44a2c3a..e02f3b798 100644 --- a/app/views/plus/junit4Versions.scala.html +++ b/app/views/plus/junit4Versions.scala.html @@ -31,6 +31,12 @@
ScalaTest + JUnit 4 Version | ScalaTest Version | JUnit 4 Versions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +4.13 | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/junit5Versions.scala.html b/app/views/plus/junit5Versions.scala.html index ef14c844b..9734d337f 100644 --- a/app/views/plus/junit5Versions.scala.html +++ b/app/views/plus/junit5Versions.scala.html @@ -31,6 +31,12 @@
ScalaTest + JUnit 5 Version | ScalaTest Version | JUnit 5 Versions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +5.10 | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/mockitoVersions.scala.html b/app/views/plus/mockitoVersions.scala.html index 10b224edd..bf9e1617c 100644 --- a/app/views/plus/mockitoVersions.scala.html +++ b/app/views/plus/mockitoVersions.scala.html @@ -31,6 +31,12 @@
ScalaTest + Mockito Version | ScalaTest Version | Mockito Versions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +5.12 | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/scalacheck.scala.html b/app/views/plus/scalacheck.scala.html index 0dce773a9..3f39e7fd4 100644 --- a/app/views/plus/scalacheck.scala.html +++ b/app/views/plus/scalacheck.scala.html @@ -46,7 +46,7 @@
ScalaTest + ScalaCheck Version | ScalaTest Version | ScalaCheck Versions | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +1.18.x | +/tr>||||||||||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/seleniumVersions.scala.html b/app/views/plus/seleniumVersions.scala.html index 4eb63b7b8..d4edff8ae 100644 --- a/app/views/plus/seleniumVersions.scala.html +++ b/app/views/plus/seleniumVersions.scala.html @@ -31,6 +31,12 @@
ScalaTest + Selenium Version | ScalaTest Version | Selenium Versions | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +4.21 | +||||||||||||||||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/plus/testngVersions.scala.html b/app/views/plus/testngVersions.scala.html index 83afc6af1..75040d773 100644 --- a/app/views/plus/testngVersions.scala.html +++ b/app/views/plus/testngVersions.scala.html @@ -31,6 +31,12 @@
ScalaTest + TestNG Version | ScalaTest Version | TestNG Versions | |||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3.3.0.0 | +3.3.0 | +7.10 | +|||||||||||||||||||||||||||
3.2.19.0 | 3.2.19 | diff --git a/app/views/quickStart.scala.html b/app/views/quickStart.scala.html index 2044c590b..043a3ce91 100644 --- a/app/views/quickStart.scala.html +++ b/app/views/quickStart.scala.html @@ -69,14 +69,11 @@||||||||||||||||||||||||||||
-maxDiscarded +maxDiscardedFactor | -50 +5.0 | -the maximum number of discarded property evaluations allowed during a property check +the factor of discarded property evaluations allowed during property evaluation. | |||||||||||||||||||||||||||
+ Configuration Parameter + | ++ Default Value + | ++ Meaning + | +
---|---|---|
+ minSuccessful + | ++ 10 + | ++ the minimum number of successful property evaluations required for the property to pass (Note that this is different from ScalaCheck's default of 100.) + | +
+ maxDiscardedFactor + | ++ 5.0 + | ++ the factor of discarded property evaluations allowed during property evaluation. + | +
+ minSize + | ++ 0 + | ++ the minimum size parameter to provide to ScalaCheck, which it will use when generating objects for which size matters (such as strings or lists) + | +
+ maxSize + | ++ 100 + | ++ the maximum size parameter to provide to ScalaCheck, which it will use when generating objects for which size matters (such as strings or lists) + | +
+ workers + | ++ 1 + | ++ specifies the number of worker threads to use during property evaluation + | +
+ The forAll
methods of trait ScalaCheckDrivenPropertyChecks
each take a PropertyCheckConfiguration
+ object as an implicit parameter. This object provides values for each of the five configuration parameters. Trait Configuration
+ provides an implicit val
named generatorDrivenConfig
with each configuration parameter set to its default value.
+ If you want to set one or more configuration parameters to a different value for all property checks in a suite you can override this
+ val (or hide it, for example, if you are importing the members of the ScalaCheckDrivenPropertyChecks
companion object rather
+ than mixing in the trait.) For example, if
+ you want all parameters at their defaults except for minSize
and maxSize
, you can override
+ generatorDrivenConfig
, like this:
+
+
+implicit override val generatorDrivenConfig = + PropertyCheckConfiguration(minSize = 10, maxSize = 20) ++ +
+ Or, if hide it by declaring a variable of the same name in whatever scope you want the changed values to be in effect: +
+ ++implicit val generatorDrivenConfig = + PropertyCheckConfiguration(minSize = 10, maxSize = 20) ++ +
+ In addition to taking a PropertyCheckConfiguration
object as an implicit parameter, the forAll
methods of trait
+ ScalaCheckDrivenPropertyChecks
also take a variable length argument list of PropertyCheckConfigParam
+ objects that you can use to override the values provided by the implicit PropertyCheckConfiguration
for a single forAll
+ invocation. For example, if you want to set minSuccessful
to 500 for just one particular forAll
invocation,
+ you can do so like this:
+
+forAll (minSuccessful(500)) { (n: Int, d: Int) => ... ++ +
+ This invocation of forAll
will use 500 for minSuccessful
and whatever values are specified by the
+ implicitly passed PropertyCheckConfiguration
object for the other configuration parameters.
+ If you want to set multiple configuration parameters in this way, just list them separated by commas:
+
+forAll (minSuccessful(500), maxDiscardedFactor(30)) { (n: Int, d: Int) => ... ++ +
+ If you are using an overloaded form of forAll
that already takes an initial parameter list, just
+ add the configuration parameters after the list of generators, names, or generator/name pairs, as in:
+
+// If providing argument names +forAll ("n", "d", minSuccessful(500), maxDiscardedFactor(30)) { + (n: Int, d: Int) => ... ++ +
// If providing generators +forAll (validNumers, validDenoms, minSuccessful(500), maxDiscardedFactor(30)) { + (n: Int, d: Int) => ... +
// If providing (<generators>, <name>) pairs +forAll ((validNumers, "n"), (validDenoms, "d"), minSuccessful(500), maxDiscardedFactor(30)) { + (n: Int, d: Int) => ... +
+ For more information, see the documentation for trait Configuration
,
+ a supertrait of ScalaCheckDrivenPropertyChecks
.
+
+ If you want to do table driven property check using ScalaCheck, learn about Table-driven property checks using ScalaCheck. +
+ ++ ScalaTest supports property-based testing using ScalaCheck, where + a property is a high-level specification of behavior that should hold for a range of data points. For example, a property might + state that the size of a list returned from a method should always be greater than or equal to the size of the list passed to + that method. This property should hold no matter what list is passed. +
+ ++ The difference between a traditional test and a property is that tests traditionally verify behavior based on specific data points checked + by the test. A test might pass three or four specific lists of different sizes to a method under test that takes a list, for example, and check the results + are as expected. A property, by contrast, would describe at a high level the preconditions of the method under test and specify some aspect of the + result that should hold no matter what valid list is passed. +
+ ++ In ScalaTest, properties are specified as functions and the data points used to check properties can be supplied by either tables + or generators. Generator-driven property checks are performed via integration with ScalaCheck through + ScalaTest + ScalaCheck library, by including: +
+ ++libraryDependencies += "org.scalatestplus" %% "scalacheck-@{latestScalaCheckVersion}" % "@{latestScalaCheckPlusVersion}" % "test" ++ +
+ in your SBT file, or if you use Maven: +
+ ++<dependency> + <groupId>org.scalatestplus</groupId> + <artifactId>scalacheck-@{latestScalaCheckVersion}_3</artifactId> + <version>@latestScalaCheckPlusVersion</version> + <scope>test</scope> +</dependency> ++ +
+ To use this style of testing, mix in trait org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
(previously known as org.scalatest.props.PropertyChecks
, deprecated in 3.0.6), or import the
+ members of its companion object.
+
+ As an example property-based testing using both table- and generator-driven properties, 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 +} +
+ If you mix in ScalaCheckPropertyChecks
, you could use a generator-driven property check to test that the passed values for numerator and
+ denominator are properly normalized, like this:
+
+forAll { (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 + } +} +
+ The forAll
method takes a function that expresses a property of Fraction
that should hold for any valid values
+ passed as n
and d
. (This property specifies how the numerator and denominator passed to the Fraction
+ constructor should be normalized.) The whenever
clause indicates invalid
+ values for n
and d
.
+ Any other values are valid. When run, ScalaCheck generators will be passed implicitly to forAll
and supply integer values for
+ n
and d
. By default the property will be checked against 10 valid pairs of n
and d
. Note that this is different from ScalaCheck's default of 100.
+
+ You might place the previous property check in its own test whose name describes the property at a high level, such as
+ "Fractions should be normalized"
.
+ In another test you might use a table-driven property check to test that all combinations of invalid values passed to the Fraction
constructor
+ produce the expected IllegalArgumentException
, like this:
+
+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) + ) ++ +
forAll (invalidCombos) { (n: Int, d: Int) => + evaluating { + new Fraction(n, d) + } should produce [IllegalArgumentException] +} +
+ In this example, invalidCombos
is a table of two columns, produced by the Table
factory method, which takes
+ a comma-separated list of tuples. The first tuple contains the headings (the names of the columns, "n"
and "d"
). The remaining
+ tuples represent the rows of data. In this example, each row is one example of how invalid data that could be passed to the Fraction
+ constructor.
+
+ After the declaration of the table, forAll
is invoked. This forAll
method takes the table, invalidCombos
, and
+ a property. forAll
checks to make sure the property holds for each row of the table.
+
+ When you get a failed property check, ScalaTest will report the failure with initial seed so that you can reproduce the failure. You'll need to pass the initial seed value through -S
argument,
+ here's an example you pass it through build.sbt:
+
+testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-S", "123456789") ++ + or if you are using testOnly you can do: + +
+sbt> testOnly MyTest -- -S 123456879 ++ +
+ For more information check out the user guide pages for: +
+ ++ Next, learn about Asynchronous testing. +
+ +
+ To use table-driven property checks, you must mix in trait TableDrivenPropertyChecks
(or import the
+ members of its companion object). If you are also using ScalaCheck generator-driven property checks, you can mix in
+ trait ScalaCheckPropertyChecks
,
+ which extends both TableDrivenPropertyChecks
and ScalaCheckDrivenPropertyChecks
.
+
+ Trait TableDrivenPropertyChecks
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.should.Matchers._ ++ +
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.
+
+ 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) +} ++ +
+ 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) +} ++ +
+ 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) => + an [IllegalArgumentException] should be thrownBy { + new Fraction(n, d) + } +} ++ +
+ If you want to do ScalaCheck generator driven property check, learn about Generator-driven property checks using ScalaCheck. +
+ +-The style you choose dictates only how the declarations of your tests will look. Everything else in ScalaTest—assertions, matchers, +The style you choose dictates only how the declarations of your tests will look. Everything else in ScalaTest—assertions, expectations, matchers, mixin traits, etc.—works consistently the same way no matter what style you chose.
@@ -93,6 +93,30 @@libraryDependencies += "org.scalatest" %% "scalatest-funsuite" % "@{latestVersion}" % "test"+ +
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest.funsuite.FunSuite +import org.scalatest.expectations.Expectations._ ++ +
class SetSuite extends FunSuite { +
test("An empty Set should have size 0") { + expect(Set.empty.size == 0) + } +
test("Invoking head on an empty Set should produce NoSuchElementException") { + expectThrows[NoSuchElementException] { + Set.empty.head + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++
import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.expectations.Expectations._+
class SetSpec extends AnyFlatSpec {
"An empty Set" should "have size 0" in { assert(Set.empty.size == 0) @@ -120,6 +145,29 @@The FlatSpec style
libraryDependencies += "org.scalatest" %% "scalatest-flatspec" % "@{latestVersion}" % "test"
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest.flatspec.FlatSpec +import org.scalatest.expectations.Expectations._ ++ +
class SetSpec extends FlatSpec { +
"An empty Set" should "have size 0" in { + expect(Set.empty.size == 0) + } +
it should "produce NoSuchElementException when head is invoked" in { + expectThrows[NoSuchElementException] { + Set.empty.head + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++ @@ -154,6 +202,33 @@
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest.funspec.FunSpec +import org.scalatest.expectations.Expectations._ ++ +
class SetSpec extends FunSpec { +
describe("A Set") { + describe("when empty") { + it("should have size 0") { + assert(Set.empty.size == 0) + } +
it("should produce NoSuchElementException when head is invoked") { + assertThrows[NoSuchElementException] { + Set.empty.head + } + } + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++ @@ -185,6 +260,34 @@
libraryDependencies += "org.scalatest" %% "scalatest-wordspec" % "@{latestVersion}" % "test"+ +
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest.wordspec.WordSpec +import org.scalatest.expectations.Expectations._ ++ +
class SetSpec extends WordSpec { +
"A Set" when { + "empty" should { + "have size 0" in { + expect(Set.empty.size == 0) + } +
"produce NoSuchElementException when head is invoked" in { + expectThrows[NoSuchElementException] { + Set.empty.head + } + } + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++ @@ -216,6 +319,33 @@
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest.freespec.FreeSpec +import org.scalatest.expectations.Expectations._ ++ +
class SetSpec extends FreeSpec { +
"A Set" - { + "when empty" - { + "should have size 0" in { + expect(Set.empty.size == 0) + } +
"should produce NoSuchElementException when head is invoked" in { + expectThrows[NoSuchElementException] { + Set.empty.head + } + } + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++ @@ -229,10 +359,11 @@
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest._ +import prop._ +import propspec.PropSpec +import org.scalatest.expectations.Expectations._ +import scala.collection.immutable._ ++ +
class SetSpec extends PropSpec with TableDrivenPropertyChecks { +
val examples = + Table[Set[Int]]( + "set", + BitSet.empty, + HashSet.empty[Int], + TreeSet.empty[Int] + ) +
property("an empty Set should have size 0") { + forAll(examples) { set => + expect(set.size == 0) + } + } +
property("invoking head on an empty set should produce NoSuchElementException") { + forAll(examples) { set => + expectThrows[NoSuchElementException] { set.head } + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++ @@ -266,6 +432,7 @@
import org.scalatest._ +import featurespec.AnyFeatureSpec+
class TVSet { private var on: Boolean = false def isOn: Boolean = on @@ -301,12 +468,59 @@The FeatureSpec style
} }
To select just the FeatureSpec
style in an sbt build, include this line:
libraryDependencies += "org.scalatest" %% "scalatest-featurespec" % "@{latestVersion}" % "test"+
If you would like to use expectations
instead of assertions
, you may use the pure style:
+import org.scalatest._ +import featurespec.FeatureSpec +import expectations.Expectations._ ++ +
class TVSet { + private var on: Boolean = false + def isOn: Boolean = on + def pressPowerButton() { + on = !on + } +} +
class TVSetSpec extends FeatureSpec with GivenWhenThen { +
info("As a TV set owner") + info("I want to be able to turn the TV on and off") + info("So I can watch TV when I want") + info("And save energy when I'm not watching TV") +
feature("TV power button") { + scenario("User presses power button when TV is off") { +
Given("a TV set that is switched off") + val tv = new TVSet +
When("the power button is pressed") + tv.pressPowerButton() +
Then("the TV should switch on") + expect(tv.isOn) + } +
scenario("User presses power button when TV is on") { +
Given("a TV set that is switched on") + val tv = new TVSet + tv.pressPowerButton() +
When("the power button is pressed") + tv.pressPowerButton() +
Then("the TV should switch off") + expect(!tv.isOn) + } + } +} +
In order to use expectations
you'll need add the following line in sbt build:
+libraryDependencies += "org.scalatest" %% "scalatest-expectations" % "@{latestVersion}" % "test" ++ @@ -344,6 +558,8 @@
RefSpec
does not support expectations
.
To use table-driven property checks, you must mix in trait TableDrivenPropertyChecks
(or import the
-members of its companion object). If you are also using ScalaCheck generator-driven property checks, you can mix in
-trait ScalaCheckPropertyChecks
,
-which extends both TableDrivenPropertyChecks
and ScalaCheckDrivenPropertyChecks
.
+members of its companion object). If you are also using ScalaTest generator-driven property checks, you can mix in
+trait PropertyChecks
,
+which extends both TableDrivenPropertyChecks
and GeneratorDrivenPropertyChecks
.
diff --git a/app/views/userGuide/usingAssertions.scala.html b/app/views/userGuide/usingAssertions.scala.html index 43fe401ac..69ffa24ec 100644 --- a/app/views/userGuide/usingAssertions.scala.html +++ b/app/views/userGuide/usingAssertions.scala.html @@ -242,14 +242,8 @@
assert
macroAppendedClues
.
-@*
--Next, learn about Using expectations. -
-*@ --Next, learn about tagging your tests. +Next, learn about using expectations.
diff --git a/conf/routes b/conf/routes index 8fbe27c4a..875b9c6c9 100644 --- a/conf/routes +++ b/conf/routes @@ -28,6 +28,7 @@ GET /getting_started_with_testng_in_scala controll GET /at_a_glance/:style controllers.Application.atAGlance(style) GET /at_a_glance controllers.Application.bareAtAGlance() +GET /release_notes/3.3.0 controllers.ReleaseNotes.v330 GET /release_notes/3.2.19 controllers.ReleaseNotes.v3219 GET /release_notes/3.2.18 controllers.ReleaseNotes.v3218 GET /release_notes/3.2.17 controllers.ReleaseNotes.v3217 @@ -100,6 +101,7 @@ GET /user_guide/using_matchers controll GET /user_guide/testing_with_mock_objects controllers.UserGuide.testingWithMockObjects GET /user_guide/tests_as_specifications controllers.UserGuide.testsAsSpecifications GET /user_guide/property_based_testing controllers.UserGuide.propertyBasedTesting() +GET /user_guide/scalacheck_property_based_testing controllers.UserGuide.scalacheckPropertyBasedTesting() GET /user_guide/other_goodies controllers.UserGuide.otherGoodies GET /user_guide/using_inside controllers.UserGuide.usingInside GET /user_guide/using_OptionValues controllers.UserGuide.usingOptionValues @@ -113,7 +115,9 @@ GET /user_guide/selecting_a_style controll GET /user_guide/defining_base_classes controllers.UserGuide.definingBaseClasses() GET /user_guide/using_junit_runner controllers.UserGuide.usingJunitRunner GET /user_guide/table_driven_property_checks controllers.UserGuide.tableDrivenPropertyChecks +GET /user_guide/scalacheck_table_driven_property_checks controllers.UserGuide.scalacheckTableDrivenPropertyChecks GET /user_guide/generator_driven_property_checks controllers.UserGuide.generatorDrivenPropertyChecks +GET /user_guide/scalacheck_driven_property_checks controllers.UserGuide.scalacheckDrivenPropertyChecks GET /user_guide/writing_scalacheck_style_properties controllers.UserGuide.writingScalacheckStyleProperties GET /user_guide/using_the_runner controllers.UserGuide.usingTheRunner GET /user_guide/invoking_execute controllers.UserGuide.invokingExecute