unit-testing - Spock 与 DbUnit 测试用例缓慢

标签 unit-testing spock dbunit

我们已经实现了 Spock + Db 单元 框架作为自动化单元测试的一部分。

我们现在有 2000 个测试用例(功能),用于 150 个 DbUnit 规范

在这里,我们在数据库中添加所需的条目,然后测试每个方法的行为。

我们观察到执行这些测试用例大约需要 2 小时 30 分钟。

我有时间戳 setup fixture 并在特征方法中添加了时间戳。以下是我的观察:

allergy.dao.AllergyFormDAOSpec > Get Allergy Form STANDARD_OUT
    setup method execution started at : Fri Jan 12 19:00:42 IST 2018

allergy.dao.AllergyFormDAOSpec > API to get Allergy Form STANDARD_OUT
Feature method execution started at : Fri Jan 12 19:00:44 IST 2018
Feature method execution ended at: Fri Jan 12 19:00:45 IST 2018
Total time taken to run the one test case: 242

cleanup method execution started at : Fri Jan 12 19:00:45 IST 2018
Total time taken to run a feature method : 2531

在这里,我观察到在设置后加载特征方法平均需要 2-4 秒。但是,原始测试用例的执行时间不到一秒。

我想知道我是否可以获得有关此处可能延迟的内容的指示?因为,2000 个测试用例的 3 秒意味着 Spock 花费了将近 1 小时 30 分钟的时间,而不是真正的功能执行。

总而言之,我们希望减少每天运行 Spock 测试用例所花费的总时间。

规范

package allergy.dao

import java.util.Date

import org.dbunit.IDatabaseTester;
import org.dbunit.ext.mssql.InsertIdentityOperation;

import allergy.AllergyForm;
import be.janbols.spock.extension.dbunit.DbUnit;
import spock.lang.Shared
import util.MasterSpec

class AllergyFormDAOSpec extends MasterSpec {
    def dao = new AllergyFormDAO();
    @Shared Date timeStart1
    @Shared Date timeEnd1

@DbUnit(configure={ IDatabaseTester it ->
    it.setUpOperation = InsertIdentityOperation.REFRESH
    it.tearDownOperation = InsertIdentityOperation.DELETE
})
def content =  {
    allergy_form(formId:99999,formName:'DummySpockForm',displayIndex:1,deleteFlag:0,is_biological:1)
    allergy_form_facilities(id:99999,formId:99999,facilityid:2)
    form_concentration(id:99999,formId:99999,name:'1:100',deleteflag:0,displayindex:1)
}

def setup(){
    timeStart1 = new Date()
    println "setup method execution started at : " +  timeStart1
}

def "API to test delete Form facility"(){
    def startTime = new Date()
    println "Feature method execution started at : " +  startTime
    given:"form Id is given"
        def formId = 99999
    when:"delete form facilities"
        def result =dao.deleteFormFacilities(null, formId)
    then:"validate result"
        (result>0)==true
        def endTime = new Date()
        println "Feature method execution ended at: " +  endTime
        println 'Total time taken to run the one test case: '+ (endTime.getTime() - startTime.getTime())
}

def cleanup() {
    timeEnd1 = new Date()
    println "cleanup method execution started at : " +  timeEnd1

    def difference = timeEnd1.time - timeStart1.time
    println "Total time taken to run a fixture method : " + difference
}
}

MasterSpec

package util

import com.ecw.dao.SqlTranslator
import catalog.Root
import spock.lang.Shared
import spock.lang.Specification

import javax.sql.DataSource

/**

 */
class MasterSpec extends Specification {

@Shared
Properties properties = new Properties()
@Shared
public DataSource dataSource
@Shared
protected xmlDataSource = [:]

static int timeCntr = 0;

//setup is to read xml file's content in xmlDataSource Hashmap
def setup(){

    //Get Running Class name without its package
    def className = this.class.name.substring(this.class.name.lastIndexOf('.') + 1)
    def resourceAnno = specificationContext.currentFeature.featureMethod.getAnnotation(FileResource)

    if(resourceAnno != null){
        def files = resourceAnno.xmlFiles()
        def packageName = (this.class.package.name).replaceAll('\\.','/')

        for(int i=0;i< files.length;i++){
            def f = new File("src/test/resources/"+packageName+"/"+className+"/"+files[i])
            def engine = new groovy.text.GStringTemplateEngine()
            def template = engine.createTemplate(f).make(null)
            def xmlString = template.toString()

            //load the hashmap with file name as Key and its content in form of string as Value
            xmlDataSource.put(files[i].split("\\.")[0],xmlString)
        }
    }
}

def setupSpec() {
    Date timeStart = new Date()

    File propertiesFile = new File('src/test/webapps/myApp/conf/connection.properties').withInputStream {
        properties.load it
    }

    String strDBName = getPropertyValue("myApp.DBName")
    if(strDBName.indexOf('?') > -1){
        strDBName = strDBName.substring(0, strDBName.indexOf('?'))
    }
    String strServerName = getPropertyValue("myApp.DBHost");
    if(strServerName.indexOf(':') > -1){
        strServerName = strServerName.substring(0, strServerName.indexOf(':'))
    }
    String strUrl = getPropertyValue("myApp.DBUrl")
    String strPort = strUrl.substring(strUrl.lastIndexOf(':') + 1)

    //FOR MSSQL
    System.setProperty("myApp.SkipJndi", "yes")
    //dataSource = new JtdsDataSource()
    Object newObject = null;
    if(SqlTranslator.isDbSqlServer()){
        newObject = Class.forName("net.sourceforge.jtds.jdbcx.JtdsDataSource").newInstance()
    } else if(SqlTranslator.isDbMySql()){
        newObject = Class.forName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource").newInstance()
    }

    dataSource = (DataSource)newObject
    dataSource.setDatabaseName(strDBName)
    dataSource.setUser(getPropertyValue("myApp.DBUser"))
    dataSource.setPassword(getPropertyValue("myApp.DBPassword"))
    dataSource.setServerName(strServerName)
    dataSource.setPortNumber(Integer.parseInt(strPort))

}
}

最佳答案

这个问题确实太宽泛了,没有提供足够的信息来给出合格的答案。但是,我可以说 Spock 本身应该非常快(不如“原始”JUnit 快,毕竟它仍然很不错,但对于测试来说确实足够快)。

从你的问题来看,你似乎怀疑 Spock 是一个瓶颈,所以, 例如,您可以在启用 DbUnit 的测试旁边放置一个空的 Spock 测试并测量其执行情况,我可以向您保证,您获得的时间可以忽略不计。

所以我认为原因是在设置/清理期间,DbUnit 调用了一些与数据库相关的代码(可能是模式生成/表填充和/或删除)并且它花费了很多时间。因此,我的第二次尝试只是打印在测试期间运行的 SQL 查询,您可能会发现其中许多查询是在设置方法期间运行的。

另一个可能的原因是为了进行测试,在测试之前插入了太多数据。

还有一个可能的原因是您正在运行测试的数据库太慢(网络慢,数据库本身太忙)。

现在所有这些的解决方案是什么? :) 您可能想看看 Spring 测试数据访问层的方法 + 如何进行初始设置。由于远远超出了问题的范围,这里就不多说spring了,仅作为一个思路:

  • 生成一次数据
  • 在所有测试运行之前插入它
  • 随着测试的开始开始交易
  • 在测试完成时回滚事务(即使测试成功),这样数据就不会被保留。如果您使用 Isolation 并且数据库服务器支持它,您甚至可以并行运行测试,这没问题。

如果缓慢的原因是数据库服务器,那么(除了明显的建议,如“更改你的 RDBMS”)你可以尝试在同一台机器上的 docker 中运行一个数据库/甚至在你的测试开始本地使用之前启动一个数据库TestContainers .

关于unit-testing - Spock 与 DbUnit 测试用例缓慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48228080/

相关文章:

testing - Geb:为每个测试打开新标签

grails - 在为 Grails 应用程序编写 Geb/Spock 测试时,您将如何封装用户的登录操作,以便可以在测试中重用它?

javascript - AngularJS 单元测试,为什么有用?

java - HttpUnit 和 jQuery : EcmaError

javascript - MatDialog 服务单元测试 Angular 6 错误

postgresql - DBUnit bool 值

java - FlatXmlDataSet 空表 (DBUnit)

javascript - 如何在 Jest 单元测试中查看渲染的 React 组件是什么样子?

groovy - 如何从命令行运行 spock 测试?

java - 在 DBUnit 中导出/导入数据类型?