当 class 依赖隐式时,是否有一种惯用的方法将 Class 转换为对象?

Is there an idiomatic way to convert a Class to an Object when the class relies on an implicit?

我是 Scala 新手。

我有一系列执行 UI 测试的测试 类,以及一系列包含可重用辅助方法的 类。

示例测试Class:

class MyCoolTestClass extends FreeSpec {

    //Note:  This driver needs to be configurable by the test in some way.
    implicit val driver:WebDriver = new ChromeDriver()

    val myCoolPage = new MyCoolPage

    //Tests below rely on myCoolPage methods
}

示例助手 Class:

class MyCoolPage(implicit driver:WebDriver) {
    def clickElement1(){
     //relies on driver
    }
    def assertElement2Enabled(){
     //relies on driver
    }
}

现在,助手 类 的 none 实际上具有可变状态。这让我想将它们从 classes 转换为 objects.

但是,我能弄清楚如何执行此操作的唯一方法是向每个方法添加一个 implicit WebDriver 参数。那行得通,但是很难看。有没有更简洁的方法来实现我在这里想要的?或者,是否有更惯用的方式来完全组织此测试 Class / Helper Method 关系?

考虑将 MyCoolPage 变成具有抽象隐式驱动字段的特征

trait MyCoolPage {
  implicit val driver: WebDriver

  def clickElement1() = {
    //relies on driver
  }

  def assertElement2Enabled(){
    //relies on driver
  }
}

然后 MyCoolTestClass 扩展 MyCoolPage 并使用自定义配置的驱动程序覆盖 driver 字段,如下所示

class MyCoolTestClass extends FreeSpec with MyCoolPage {
  override implicit val driver: WebDriver = new ChromeDriver()

  //Tests below rely on myCoolPage methods
}

现在 MyCoolTestClass 可以访问 MyCoolPage 中的每个方法,并且这些方法不需要隐式驱动程序参数。

可以将您的帮助 classes 更改为对象并仍然向成员方法提供 implicit 值。

object MyCoolPage {

  private val driver :WebDriver = implicitly[WebDriver]

  def clickElement1() = ???          //relies on driver
  def assertElement2Enabled() = ???  //relies on driver
}

但是 implicit 声明必须移出测试 class。我想到了两种可能性:WebDriver 对象...

object WebDriver {
  implicit val wd :WebDriver = new ChromeDriver()
  ...

...或在专用对象中。

object MyCoolPage {
  import MyTestImplicits._
  private val driver :WebDriver = implicitly[WebDriver]
  ...

总而言之,我不认为这样做是值得的。

Now, none of the helper classes actually have mutable state. This makes me want to convert them from classes to objects.

但他们确实有状态。是的,它是不可变的,但大多数情况下 类、Options、Lists 的状态也是如此......其中 None 应该转换为 objects。我认为没有比您开始时更好的解决方案了。

但是,这里有一个选项:将辅助对象嵌套在超类中:

abstract class AbstractTestClass extends FreeSpec {
  // may optionally be implicit or non-abstract
  val driver: WebDriver 

  object MyCoolPage {
    def clickElement1(){
      //relies on driver
    }
    def assertElement2Enabled(){
      //relies on driver
    }
  }

  object MyCoolPage2 ...
}

class MyCoolTestClass extends AbstractTestClass {
  override val driver: WebDriver = new ChromeDriver()

  // can use MyCoolPage methods
}

请注意,object 是延迟加载的,因此如果 MyCoolTestClass 不使用 MyCoolPage2,则不会为此付费。权衡是所有助手 类 必须在单个文件中定义。

我认为,没有惯用的方法 可以将 classes 转换为 objects,如您所问。这是我对原因的看法:

您在 MyCoolClass 中声明了一个属性 driver,您正在方法中使用。因此你的 class 实际上有状态。 WebDriverstate injected 变成 MyCoolPage。根据注入的实现 WebDriver,处理方法调用的方式可能会有所不同。

为了克服这个问题,您需要将 WebDriver 作为每个方法的隐式参数,正如您自己发现的那样。但是,这将允许在运行时从外部替换驱动程序。这将违反 Open-Closed-Principle,我认为 比使用您的 class 构造更不惯用

如果以后再遇到这个问题,不妨尝试不使用implicit来写代码。如果将 class 更改为 object 仍然是一种合适的方法,那么您可能就可以开始了。