持续集成之路——数据访问层的单元测试

本文探讨了持续集成中的数据访问层测试挑战,介绍了使用HSQLDB内存数据库替代MySQL的方法,以及利用Hibernate的DDL支持来简化数据库结构维护的过程。

        翻看之前的文章才发现,最近一次记录持续集成竟然是3年前,并且只记录了两篇,实在是惭愧。不过,持续集成的这团火焰却始终在心中燃烧,希望这次的开始可以有些突破。

         测试是持续集成的基石,没有测试的集成基本上是毫无意义的。如何写好测试就是横亘在我面前的第一个问题。那就从数据访问层开始吧。说起来可笑,从3年前第一次准备做持续集成式,就开始考虑测试数据访问层的一些问题:

  1. 难道我要在测试服务器上装一个MySQL?
  2. 数据库结构发生了变化怎么办?
  3. 怎么样才能消除测试间的依赖?
  4. 测试数据怎么管理?何况测试数据间还有那么多的逻辑?
  5. 结果如何验证?
  6. ……

        这些问题在我脑海萦绕很久,《一代宗师》里说“宁在一思进,莫在一思停”,及时想破脑袋,不如直接实践。那就一个个问题来。

        在继续之前,先交代一下当前程序的架构,很经典的Spring + Spring Data + Hibernate + MySQL,所以下面的解决方案都是基于这个架构。另外,程序是通过Maven构建。

        一、用内存数据库替代MySQL

        我选择了HSQLDB,官网上有很多示例可以参考。HSQLDB提供几种不同的使用模式,这里只选用内存模式。Spring通过<jdbc:embedded-database>标签提供对了嵌入式数据库的支持,在applicationContext-test.xml中对数据源的配置十分简单:

<jdbc:embedded-database id="dataSource" type="HSQL"/>

       HSQL不需要安装,在pom.xml将jar包作为依赖引入即可:

        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.2.8</version>
            <scope>test</scope>
        </dependency>

       二、数据库结构的维护

        在项目的开发过程中,一直使用flyway维护数据库结构变化。虽然Spring也通过<jdbc:initialize-database>提供了执行SQL的机会,但是经过测试发现并且不能完成flyway完成的任务。这个就开始思考:是否一定要选用flyway,并且通过SQL来控制结构改变?flyway主要是参考了ruby的db migration机制,每次修改都是上一次版本的基础进行的,从而不会影响正在运行的逻辑。可是在开发阶段并没有必要使用flyway来控制,并且对SQL的维护也是要花费精力。于是就把目光转向了Hibernate对DDL的支持,便有了下面的配置:

	<!-- Jpa Entity Manager 配置 -->
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"></property>
		<property name="packagesToScan" value="com.noyaxe.myapp" />
		<property name="jpaProperties">
			<props>
				<!-- 命名规则 My_NAME->MyName -->
				<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
				<prop key="hibernate.show.sql">true</prop>
                                <prop key="hibernate.hbm2ddl.auto">update</prop>
			</props>
		</property>
	</bean>

        可是对于Hibernate的DDL支持,我还是心存疑虑:1. 如果数据库中已经存在数据,那么字段类型改变会如何处理?2. 如何才能更好维护DDL的变化?

        (待续)


       补记:

       首先十分感谢csfreebird的指教,让我停下来思考为什么要选用HSQLDB背后的问题。下面几篇文章供大家参考,尤其是最后一篇文章值得仔细思考:

       Testing Databases with JUnit and Hibernate Part 1: One to Rule them

       Database unit testing with DBUnit, Spring and TestNG

       Basic Mistakes in Database Testing

<script type="text/javascript">var _bdhmprotocol = (("https:" == document.location.protocol) ? " https://" : " http://"); document.write(unescape("%3cscript src='" + _bdhmprotocol + "hm.baidu.com/h.js%3f94beed115f7adca9895daf1cc025ad4f' type='text/javascript'%3e%3c/script%3e"));</script>
### 单元测试与集成测试的定义 单元测试是一种针对软件中的最小可测试部分(通常是函数或模块)进行验证的方法[^1]。其目标是确保单个组件的行为符合预期,通常由开发人员编写并执行。 集成测试则是指在完成单元测试之后,将各个模块组合在一起进行测试的过程[^2]。它关注的是不同模块之间的交互以及整体系统的功能实现情况。集成测试可能涉及真实的外部依赖项,例如数据库或其他服务[^3]。 --- ### 单元测试与集成测试的区别 #### 1. **测试范围** - 单元测试专注于单独的功能模块或方法,目的是确认该模块是否按照设计规范正常运行。 - 集成测试则侧重于多个已通过单元测试的模块如何协同工作,检测接口之间是否存在错误。 #### 2. **测试对象** - 单元测试的对象是一个独立的工作单元,比如类、函数或者子程序。 - 集成测试的对象是由若干个工作单元组成的集合体,这些单元共同构成更大的逻辑结构。 #### 3. **依赖关系** - 在单元测试中,为了减少对外部环境的影响,常使用模拟技术(Mocking),替代实际存在的复杂资源如文件系统、网络连接等。 - 而集成测试由于需要评估真实场景下的表现,因此会尽可能多地利用真正的外部依赖来检验整个流程的有效性。 #### 4. **执行时机** - 单元测试一般会在编码阶段完成后立即实施,作为持续集成的一部分频繁运行以快速发现潜在缺陷。 - 集成测试往往发生在后期,在所有必要的个体部件均已准备好并通过初步审查后再启动。 #### 5. **失败原因分析难度** - 如果某个单元测试未能通过,则定位问题相对容易,因为只涉及到单一实体及其内部逻辑。 - 对比之下,当集成测试报错时,排查起来更加困难一些,因为它不仅牵涉到各组成部分自身的正确与否,还包括它们相互作用的方式是否恰当。 --- ### 示例代码对比 以下是两个简单的例子分别展示单元测试和集成测试: #### 单元测试示例 (Python) ```python import unittest def add(a, b): return a + b class TestAddFunction(unittest.TestCase): def test_add(self): self.assertEqual(add(1, 2), 3) if __name__ == "__main__": unittest.main() ``` 此段代码仅检查 `add` 函数本身能否返回期望的结果,属于典型的单元测试范畴。 #### 集成测试示例 (Python with Database Interaction) 假设我们有一个应用程序需访问 MySQL 数据库存储数据: ```python import mysql.connector from unittest import TestCase class IntegrationTestDatabase(TestCase): @classmethod def setUpClass(cls): cls.conn = mysql.connector.connect( host="localhost", user="root", password="password", database="testdb" ) def test_insert_and_retrieve_data(self): cursor = self.conn.cursor() insert_query = "INSERT INTO users(name) VALUES('John')" select_query = "SELECT name FROM users WHERE id=LAST_INSERT_ID()" try: cursor.execute(insert_query) cursor.execute(select_query) result = cursor.fetchone()[0] assert result == 'John' finally: cursor.close() if __name__ == '__main__': from unittest import main as run_tests run_tests() ``` 上述片段演示了一个更复杂的案例——即把新记录写入表内再读取出来核验一致性,这显然超出了单纯某一部分行为考量而进入到了跨层协作层面,故归类为集成测试。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mydeman

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值