Scala - 为使用数据库连接扩展特征/类的对象/单例编写单元测试 [英] Scala - write unit tests for objects/singletons that extends a trait/class with DB connection

查看:12
本文介绍了Scala - 为使用数据库连接扩展特征/类的对象/单例编写单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

单元测试相关问题

在测试扩展具有 DB 连接(或任何其他外部"调用)的另一个特征/类的 scala 对象时遇到问题

Encountered a problem with testing scala objects that extend another trait/class that has a DB connection (or any other "external" call)

在我的项目中的任何地方使用带有数据库连接的单例使得单元测试不是一个选项,因为我无法覆盖/模拟数据库连接

Using a singleton with a DB connection anywhere in my project makes unit-test not be a option because I cannot override / mock the DB connection

这导致更改我的设计仅在明显需要成为对象的情况下用于测试目的

This results in changing my design only for test purpose in situations where its clearly needed to be a object

有什么建议吗?

不可测试代码的代码片段:

Code snippet for a non testable code :

object How2TestThis extends SomeDBconnection {

  val somethingUsingDB = {
    getStuff.map(//some logic)
  }

  val moreThigs {
    //more things
  }

}

trait SomeDBconnection {
  import DBstuff._
  val db = connection(someDB)  
  val getStuff = db.getThings
}

推荐答案

  1. 其中一个选项是使用 cake 模式来根据需要要求一些 DB 连接和 mixin 特定的实现.例如:

  1. One of the options is to use cake pattern to require some DB connection and mixin specific implementation as desired. For example:

import java.sql.Connection

// Defines general DB connection interface for your application
trait DbConnection {
  def getConnection: Connection
}

// Concrete implementation for production/dev environment for example
trait ProductionDbConnectionImpl extends DbConnection {
  def getConnection: Connection = ???
}

// Common code that uses that DB connection and needs to be tested.
trait DbConsumer {
  this: DbConnection =>

  def runDb(sql: String): Unit = {
    getConnection.prepareStatement(sql).execute()
  }
}

...

// Somewhere in production code when you set everything up in init or main you
// pick concrete db provider
val prodDbConsumer = new DbConsumer with ProductionDbConnectionImpl
prodDbConsumer.runDb("select * from sometable")

...

// Somewhere in test code you mock or stub DB connection ...
val testDbConsumer = new DbConsumer with DbConnection { def getConnection = ??? }
testDbConsumer.runDb("select * from sometable")

如果你必须使用单例/Scala object 你可以有一个 lazy val 或一些 init(): Unit 方法来设置连接起来.

If you have to use a singleton/Scala object you can have a lazy val or some init(): Unit method that sets connection up.

另一种方法是使用某种注射器.例如查看电梯代码:

Another approach would be to use some sort of injector. For example look at Lift code:

package net.liftweb.http

/**
 * A base trait for a Factory.  A Factory is both an Injector and
 * a collection of FactorMaker instances.  The FactoryMaker instances auto-register
 * with the Injector.  This provides both concrete Maker/Vender functionality as
 * well as Injector functionality.
 */
trait Factory extends SimpleInjector

然后在你的代码中的某个地方你像这样使用这个供应商:

Then somewhere in your code you use this vendor like this:

val identifier = new FactoryMaker[MongoIdentifier](DefaultMongoIdentifier) {}

然后在您实际上必须访问 DB 的地方:

And then in places where you actually have to get access to DB:

identifier.vend

您可以在测试中提供替代提供程序,方法是在您的代码周围加上:

You can supply alternative provider in tests by surrounding your code with:

identifier.doWith(mongoId) { <your test code> }

可以方便地与specs2 Around上下文一起使用,例如:

which can be conveniently used with specs2 Around context for example:

implicit val dbContext new Around {
  def around[T: AsResult](t: => T): Result = {
    val mongoId = new MongoIdentifier {
      def jndiName: String = dbName
    }
    identifier.doWith(mongoId) {
      AsResult(t)
    }
  }
}

这很酷,因为它是在 Scala 中实现的,没有任何特殊的字节码或 JVM hack.

It's pretty cool because it's implemented in Scala without any special bytecode or JVM hacks.

如果您认为前 2 个选项过于复杂并且您有一个小应用程序,您可以使用 Properties 文件/cmd 参数来让您知道您是在测试模式还是生产模式下运行.这个想法再次来自 Lift :).您可以自己轻松实现它,但在这里您可以如何使用 Lift Props 来实现它:

If you think first 2 options are too complicated and you have a small app you can use Properties file/cmd args to let you know if you are running in test or production mode. Again the idea comes from Lift :). You can easily implement it yourself, but here how you can do it with Lift Props:

// your generic DB code:
val jdbcUrl: String = Props.get("jdbc.url", "jdbc:postgresql:database")

你可以有 2 个 props 文件:

You can have 2 props files:

  • production.default.props

  • production.default.props

jdbc.url=jdbc:postgresql:database

test.default.props

test.default.props

jdbc.url=jdbc:h2

Lift 会自动检测运行模式 Props.mode 并选择正确的 props 文件来读取.您可以使用 JVM cmd args 设置运行模式.

Lift will automatically detect run mode Props.mode and pick the right props file to read. You can set run mode with JVM cmd args.

因此,在这种情况下,您可以连接到内存数据库,也可以只读取运行模式并在代码中相应地设置您的连接(模拟、存根、未初始化等).

So in this case you can either connect to in-memory DB or just read run mode and set your connection in code accordingly (mock, stub, uninitialized, etc).

使用常规 IOC 模式 - 通过构造函数参数将依赖项传递给类.不要使用 object.除非您使用特殊的依赖注入框架,否则这很快就会变得不方便.

Use regular IOC pattern - pass dependencies via constructor arguments to the class. Don't use an object. This gets inconvenient quickly unless you use special dependency injection frameworks.

一些建议:

object 用于无法具有替代实现的事物,并且该实现是否适用于所有环境.将 object 用于常量和纯 FP 无副作用代码.在最后时刻使用单例进行连接 - 就像一个带有 main 的类,而不是在代码深处许多组件依赖它的地方,除非它没有副作用或者它使用类似可堆叠/可注入的东西供应商提供商(请参阅 Lift).

Use object for something that can't have an alternative implementation and if this only implementation will work in all environments. Use object for constants and pure FP non side effecting code. Use singletons for wiring things up at the last moment - like a class with main, not somewhere deep in the code where many components depend on it unless it has no side effects or it uses something like stackable/injectable vendor providers (see Lift).

结论:

您不能模拟对象或覆盖其实现.您需要将代码设计为可测试的,上面列出了一些选项.使用易于组合的部分使您的代码具有灵活性是一种很好的做法,这不仅是为了测试目的,而且也是为了可重用性和可维护性.

You can't mock an object or override its implementation. You need to design your code to be testable and some of the options for it are listed above. It's a good practice to make your code flexible with easily composable parts not only for the purposes of testing but also for reusability and maintainability.

这篇关于Scala - 为使用数据库连接扩展特征/类的对象/单例编写单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆