CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scalacheck--scalacheck-2-12

A comprehensive property-based testing library for Scala and Java applications that enables developers to specify program properties as testable assertions and automatically generates test cases to verify these properties.

Pending
Overview
Eval results
Files

property-collections.mddocs/

Property Collections

ScalaCheck's Properties framework enables organizing related properties into testable suites with command-line runners, batch execution support, and hierarchical organization. Property collections provide structure for large test suites and enable systematic testing of complex systems.

Capabilities

Core Properties Class

The fundamental class for organizing and executing collections of named properties.

open class Properties(val name: String) {
  val name: String
  def properties: Seq[(String, Prop)]
  def check(prms: Test.Parameters = Test.Parameters.default): Unit
  def main(args: Array[String]): Unit
  def include(ps: Properties): Unit
  def include(ps: Properties, prefix: String): Unit
  def overrideParameters(p: Test.Parameters): Test.Parameters
}

Usage Examples:

object MathProperties extends Properties("Mathematics") {
  property("addition commutative") = forAll { (a: Int, b: Int) =>
    a + b == b + a
  }
  
  property("multiplication identity") = forAll { (a: Int) =>
    a * 1 == a
  }
  
  property("division by self") = forAll { (a: Int) =>
    (a != 0) ==> (a / a == 1)
  }
}

// Check all properties
MathProperties.check()

// Run from command line
MathProperties.main(Array("--verbosity", "2"))

Property Specification

The mechanism for adding properties to collections using assignment syntax.

sealed class PropertySpecifier {
  def update(propName: String, p: => Prop): Unit
}

val property: PropertySpecifier

Usage Examples:

object StringProperties extends Properties("String Operations") {
  // Using property specifier with assignment syntax
  property("reverse twice") = forAll { (s: String) =>
    s.reverse.reverse == s
  }
  
  property("concatenation length") = forAll { (s1: String, s2: String) =>
    (s1 + s2).length == s1.length + s2.length
  }
  
  property("empty string identity") = forAll { (s: String) =>
    s + "" == s && "" + s == s
  }
}

Property Specification with Seeds

Advanced property specification that allows explicit seed control for reproducible testing.

sealed class PropertyWithSeedSpecifier {
  def update(propName: String, optSeed: Option[String], p: => Prop): Unit
}

val propertyWithSeed: PropertyWithSeedSpecifier

Usage Examples:

object ReproducibleTests extends Properties("Reproducible") {
  // Property with explicit seed for reproducibility
  propertyWithSeed("deterministic test", Some("SGVsbG8gV29ybGQ")) = forAll { (x: Int) =>
    x + 0 == x
  }
  
  // Property without seed (will use random seed but show it on failure)
  propertyWithSeed("debug test", None) = forAll { (data: ComplexData) =>
    processData(data).isValid
  }
}

Property Collection Inclusion

Mechanism for composing larger test suites from smaller property collections.

def include(ps: Properties): Unit
def include(ps: Properties, prefix: String): Unit

Usage Examples:

object CoreLogic extends Properties("Core") {
  property("basic invariant") = forAll { (x: Int) => x == x }
}

object AdvancedLogic extends Properties("Advanced") {
  property("complex invariant") = forAll { (data: ComplexType) => 
    validateComplex(data) 
  }
}

object AllTests extends Properties("Complete Test Suite") {
  // Include other property collections
  include(CoreLogic)
  include(AdvancedLogic, "advanced.")
  
  // Additional properties specific to this suite
  property("integration test") = forAll { (a: Int, b: String) =>
    integrate(a, b).nonEmpty
  }
}

// This will run all properties from included collections plus local properties
AllTests.check()

Parameter Customization

Override default test parameters for specific property collections.

def overrideParameters(p: Test.Parameters): Test.Parameters

Usage Examples:

object PerformanceTests extends Properties("Performance") {
  // Custom parameters for this collection
  override def overrideParameters(p: Test.Parameters): Test.Parameters = {
    p.withMinSuccessfulTests(10000)
     .withWorkers(4)
     .withMaxSize(1000)
  }
  
  property("large data processing") = forAll { (data: List[Int]) =>
    processLargeDataset(data).size <= data.size
  }
}

object QuickTests extends Properties("Quick Smoke Tests") {
  override def overrideParameters(p: Test.Parameters): Test.Parameters = {
    p.withMinSuccessfulTests(10)
     .withMaxSize(20)
  }
  
  property("smoke test") = forAll { (x: Int) => x + 1 > x }
}

Command-Line Execution

Built-in command-line runner with parameter parsing and result reporting.

def main(args: Array[String]): Unit

Usage Examples:

object CommandLineTests extends Properties("CLI Tests") {
  property("always passes") = Prop.passed
  property("sometimes fails") = forAll { (x: Int) => x != 42 }
}

// Command line usage:
// scala CommandLineTests --minSuccessfulTests 1000 --workers 4
// scala CommandLineTests --verbosity 2 --maxDiscardRatio 10

// In application code:
CommandLineTests.main(Array(
  "--minSuccessfulTests", "500",
  "--workers", "2", 
  "--verbosity", "1"
))

Property Organization Patterns

Hierarchical Test Organization

// Domain-specific property collections
object UserManagement extends Properties("User Management") {
  property("user creation") = forAll { (name: String, email: String) =>
    val user = createUser(name, email)
    user.name == name && user.email == email
  }
  
  property("user validation") = forAll { (user: User) =>
    validateUser(user).isValid ==> user.isComplete
  }
}

object OrderProcessing extends Properties("Order Processing") {
  property("order total calculation") = forAll { (items: List[OrderItem]) =>
    val order = Order(items)
    order.total >= 0 && order.total == items.map(_.price).sum
  }
}

object DatabaseOperations extends Properties("Database") {
  property("CRUD operations") = forAll { (entity: Entity) =>
    val saved = database.save(entity)
    val retrieved = database.findById(saved.id)
    retrieved.contains(saved)
  }
}

// Top-level test suite
object ApplicationTestSuite extends Properties("E-Commerce Application") {
  include(UserManagement, "users.")
  include(OrderProcessing, "orders.")
  include(DatabaseOperations, "db.")
  
  property("end-to-end workflow") = forAll { (user: User, items: List[Item]) =>
    val order = createOrder(user, items)
    processPayment(order).isSuccess ==> order.status == OrderStatus.Paid
  }
}

Environment-Specific Testing

trait TestEnvironment {
  def databaseUrl: String
  def apiEndpoint: String
}

object LocalTestEnv extends TestEnvironment {
  def databaseUrl = "jdbc:h2:mem:test"
  def apiEndpoint = "http://localhost:8080"
}

object StagingTestEnv extends TestEnvironment {
  def databaseUrl = "jdbc:postgresql://staging-db:5432/app"
  def apiEndpoint = "https://staging-api.example.com"
}

abstract class EnvironmentProperties(name: String, env: TestEnvironment) extends Properties(name) {
  override def overrideParameters(p: Test.Parameters): Test.Parameters = {
    p.withTestCallback(ConsoleReporter(1))
     .withMinSuccessfulTests(if (env == LocalTestEnv) 100 else 1000)
  }
}

object LocalTests extends EnvironmentProperties("Local Tests", LocalTestEnv) {
  property("database connection") = Prop {
    connectToDatabase(LocalTestEnv.databaseUrl).isSuccess
  }
}

object StagingTests extends EnvironmentProperties("Staging Tests", StagingTestEnv) {
  property("api availability") = Prop {
    httpGet(StagingTestEnv.apiEndpoint + "/health").status == 200
  }
}

Property Categorization

object PropertyCategories {
  // Fast unit tests  
  object UnitTests extends Properties("Unit Tests") {
    override def overrideParameters(p: Test.Parameters): Test.Parameters = {
      p.withMinSuccessfulTests(100).withMaxSize(50)
    }
    
    property("string operations") = forAll { (s: String) =>
      s.toLowerCase.toUpperCase.toLowerCase == s.toLowerCase
    }
  }
  
  // Slower integration tests
  object IntegrationTests extends Properties("Integration Tests") {
    override def overrideParameters(p: Test.Parameters): Test.Parameters = {
      p.withMinSuccessfulTests(50).withWorkers(2)
    }
    
    property("service integration") = forAll { (request: ServiceRequest) =>
      val response = callExternalService(request)
      response.isValid && response.correlationId == request.id
    }
  }
  
  // Performance-focused tests  
  object PerformanceTests extends Properties("Performance Tests") {
    override def overrideParameters(p: Test.Parameters): Test.Parameters = {
      p.withMinSuccessfulTests(1000)
       .withMaxSize(10000)
       .withWorkers(Runtime.getRuntime.availableProcessors())
    }
    
    property("large data processing") = forAll { (data: List[DataRecord]) =>
      val start = System.currentTimeMillis()
      val result = processLargeDataset(data)
      val duration = System.currentTimeMillis() - start
      
      result.size == data.size && duration < 5000 // Under 5 seconds
    }
  }
}

// Master suite that includes all categories
object AllTests extends Properties("Complete Test Suite") {
  include(PropertyCategories.UnitTests, "unit.")
  include(PropertyCategories.IntegrationTests, "integration.")  
  include(PropertyCategories.PerformanceTests, "performance.")
}

Test Result Analysis

object TestAnalyzer extends Properties("Analyzer") {
  property("analyzed property") = forAll { (data: TestData) =>
    classify(data.size == 0, "empty") {
      classify(data.size < 10, "small", "large") {
        collect(data.category) {
          processTestData(data).isValid
        }
      }
    }
  }
}

// Custom test runner with result analysis
object CustomTestRunner {
  def runWithAnalysis(props: Properties): Unit = {
    val results = Test.checkProperties(
      Test.Parameters.default.withTestCallback(ConsoleReporter(2)),
      props  
    )
    
    val (passed, failed) = results.partition(_._2.passed)
    
    println(s"\n=== Test Results ===")
    println(s"Passed: ${passed.size}")
    println(s"Failed: ${failed.size}")
    
    if (failed.nonEmpty) {
      println(s"\nFailed Properties:")
      failed.foreach { case (name, result) =>
        println(s"  ✗ $name")
        result.status match {
          case Test.Failed(args, labels) =>
            println(s"    Args: ${args.map(_.arg).mkString(", ")}")
            if (labels.nonEmpty) println(s"    Labels: ${labels.mkString(", ")}")
          case Test.PropException(_, ex, _) =>
            println(s"    Exception: ${ex.getMessage}")
          case _ =>
        }
      }
    }
    
    // Analyze collected data
    results.foreach { case (name, result) =>
      if (result.freqMap.total > 0) {
        println(s"\n$name - Collected Data:")
        result.freqMap.getRatios.take(5).foreach { case (item, ratio) =>
          println(f"  $item: ${ratio * 100}%.1f%%")
        }
      }
    }
  }
}

// Usage
CustomTestRunner.runWithAnalysis(ApplicationTestSuite)

Conditional Property Execution

object ConditionalTests extends Properties("Conditional Tests") {
  // Only run expensive tests if environment variable is set
  if (sys.env.get("RUN_EXPENSIVE_TESTS").contains("true")) {
    property("expensive computation") = forAll { (data: LargeDataSet) =>
      expensiveComputation(data).isOptimal
    }
  }
  
  // Platform-specific tests
  if (System.getProperty("os.name").toLowerCase.contains("linux")) {
    property("linux-specific feature") = forAll { (path: String) =>
      linuxSpecificOperation(path).isSuccess
    }
  }
  
  // Database-dependent tests
  try {
    connectToTestDatabase()
    property("database operations") = forAll { (entity: Entity) =>
      saveToDatabase(entity).isSuccess
    }
  } catch {
    case _: Exception => 
      println("Skipping database tests - database not available")
  }
}

Nested Property Collections

abstract class ModuleTests(moduleName: String) extends Properties(s"$moduleName Module") {
  def unitTests: Properties
  def integrationTests: Properties
  
  include(unitTests, "unit.")
  include(integrationTests, "integration.")
}

object AuthenticationModule extends ModuleTests("Authentication") {
  object unitTests extends Properties("Auth Unit Tests") {
    property("password hashing") = forAll { (password: String) =>
      val hashed = hashPassword(password)
      hashed != password && verifyPassword(password, hashed)
    }
  }
  
  object integrationTests extends Properties("Auth Integration Tests") {
    property("login flow") = forAll { (credentials: LoginCredentials) =>
      val token = authenticate(credentials)
      token.isValid ==> validateToken(token).isSuccess
    }
  }
}

object PaymentModule extends ModuleTests("Payment") {
  object unitTests extends Properties("Payment Unit Tests") {
    property("amount calculations") = forAll { (amount: BigDecimal, tax: Double) =>
      val total = calculateTotal(amount, tax)
      total >= amount && total > 0
    }
  }
  
  object integrationTests extends Properties("Payment Integration Tests") { 
    property("payment processing") = forAll { (payment: PaymentRequest) =>
      val result = processPayment(payment)
      result.status == PaymentStatus.Success ==> result.transactionId.nonEmpty
    }
  }
}

object ApplicationModules extends Properties("All Modules") {
  include(AuthenticationModule)
  include(PaymentModule)
  
  override def overrideParameters(p: Test.Parameters): Test.Parameters = {
    p.withMinSuccessfulTests(200).withWorkers(3)
  }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-scalacheck--scalacheck-2-12

docs

arbitrary.md

cogen.md

generators.md

index.md

properties.md

property-collections.md

shrinking.md

stateful-testing.md

test-execution.md

tile.json