You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Scalatest中实现工厂:Trait多实现契约测试复用问题

Great question! In ScalaTest, you can combine reflection-based implementation discovery with parameterized testing to automatically run your contract tests against all implementations of TraitToTest without manually specifying each one. Here's a step-by-step solution:


1. Build a Factory to Auto-Discover Implementations

First, create a helper object that uses Scala's reflection API to find all concrete classes implementing TraitToTest and instantiate them. This acts as your auto-discovery factory:

import scala.reflect.runtime.universe._
import scala.reflect.runtime.{currentMirror => cm}

object TraitToTestFactory {
  def getAllImplementations: List[TraitToTest] = {
    // Get the type symbol for our trait
    val traitSymbol = typeOf[TraitToTest].typeSymbol
    
    // Filter for concrete classes that implement TraitToTest
    val concreteImplementations = cm.classes.filter { cls =>
      cls.baseClasses.contains(traitSymbol) && 
      !cls.isAbstract && // Skip the trait itself and abstract subclasses
      cls.fullName.startsWith("com.yourpackage.impl") // Optional: Restrict to your implementation package
    }

    // Try to instantiate each class (handles singletons and no-arg constructors)
    concreteImplementations.flatMap { cls =>
      try {
        // First try to load as a singleton object (object Impl1 extends ...)
        val module = cm.staticModule(cls.fullName)
        Some(cm.reflectModule(module).instance.asInstanceOf[TraitToTest])
      } catch {
        case _: Exception =>
          // Fall back to instantiating via no-arg constructor (class Impl1 extends ...)
          try {
            val classSymbol = cls.asClass
            val constructor = classSymbol.primaryConstructor.asMethod
            val instance = cm.reflectClass(classSymbol).reflectConstructor(constructor)()
            Some(instance.asInstanceOf[TraitToTest])
          } catch {
            case e: Exception =>
              println(s"Skipping ${cls.fullName}: Failed to instantiate - ${e.getMessage}")
              None
          }
      }
    }.toList
  }
}

2. Reuse Contract Tests with Parameterized Testing

Next, use ScalaTest's TableDrivenPropertyChecks to iterate over all discovered implementations and run your contract tests against each one. This ensures your test suite automatically includes new implementations as you add them:

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.prop.TableDrivenPropertyChecks

class TraitToTestContractSpec extends AnyFunSpec with Matchers with TableDrivenPropertyChecks {

  // Load all implementations from our factory
  val testImplementations = Table(
    "implementation",
    TraitToTestFactory.getAllImplementations: _*
  )

  describe("TraitToTest general contracts") {
    // Run contract tests for every implementation
    forAll(testImplementations) { impl =>
      val implName = impl.getClass.getSimpleName

      it(s"should satisfy contract 1 for $implName") {
        // Insert your contract 1 test logic here
        // Example: Verify no exceptions are thrown during doSome()
        noException should be thrownBy impl.doSome()
      }

      it(s"should satisfy contract 2 for $implName") {
        // Insert your contract 2 test logic here
        // Example: Add assertions specific to your trait's requirements
      }
    }
  }
}

Key Notes & Optimizations

  • Constructor Parameters: If your implementations require constructor arguments, adjust the factory to inject predefined test values (e.g., mock dependencies) or use a dependency injection framework to resolve instances.
  • Test Isolation: Each implementation gets its own set of test cases, so failures in one won't affect others. The test report will clearly show which implementation failed which contract.
  • Performance: Reflection can add minor overhead. For large codebases, consider caching the discovered implementations or using compile-time code generation (e.g., with macros) instead.
  • Filtering: Tweak the concreteImplementations filter to exclude test-only classes or internal implementations you don't want to validate.

内容的提问来源于stack exchange,提问作者Some Name

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:00:59