半山云岚

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  10 随笔 :: 0 文章 :: 1 评论 :: 0 Trackbacks

#

从09年1月份开始在项目中推行测试驱动开发,在实践中有一些所得:

1. (TDD的基本原则)如果需要对代码的逻辑行为做任何修改,必须思考并先创建单元测试用例。

2. TDD的初衷是可以完全取代 Low Level Design。但是在实践中发现,对于大多数新的TDD程序员,要在完全没有详细设计之前写出有效实用的单元测试用例是非常困难的事情。其实这是个思维习惯问题(我们绝大多数人都习惯了用文字来描述场景和问题,而不习惯用代码来做同样的事情,仿佛代码没有文字直观),经过有意识地训练,我发现用测试代码来描述问题更具体,更节省时间。那么对于经验不足的程序员,一个折中的办法是,可以允许他在写测试代码前有一个简单的详细设计文档。但是注意:这个文档不是一个很正式的可参考的设计文档,不是我们的 work product ,它仅仅用于帮助经验不够的程序员进行思考分析。(在敏捷开发实践中,我们提倡 Simple Design,设计不应该描述太多的代码细节)

3. 在设计文档中(无论是 High Level Design 或 Detail Design),我们需要对预期需要的单元测试用例进行描述,比如罗列出单元测试需要测试哪些场景。这有两个好处:

a. 强制设计人员在写设计时就考试考虑如何进行验证;

b. 确保程序员在 coding 时不会忘记某些必要的测试。

4. 如果有 Code Review 的流程,单元测试用例和运行结果也必须是代码审查活动的一部分。

5. 单元测试用例必须要被频繁的执行或重构,团队需要保证提交到版本控制库里的所有代码的测试用例在任何时候都可以成功通过(建议部署持续集成CI的流程来自动化单元测试执行),不是仅仅在开发的时候写一次就自不维护了。而且一个程序员写的测试用例要能够很容易得被其它程序员执行。

6. 尽可能将测试数据和用到它的测试代码放在同一个类中或同一个目录下,尽可能将不同单元测试用例的测试数据隔离,避免互相影响(除了一些基础数据,业务测试数据可以在每个用例测试前创建,测试后清除);程序员需要保证,单元测试用例的执行不能过于依赖本地数据和环境的配置。

7. TDD的单元测试用例应该从需求,从业务场景的角度来思考和创建,而不是像传统单元测试那用针对每个类,每个方法来写测试。记住:真正的TDD是一个业务驱动的设计和开发方法!

More to be added...

posted @ 2010-01-06 17:45 Alfred. Yao 阅读(204) | 评论 (0)编辑 收藏

用了 Derby 数据库好一段时间了,雁过留痕。

作为内存数据库或嵌入式数据库,Derby 是一个很好的选择,完全由 Java 实现,在 JDK1.6 中 Derby 已经作为其一部分来发布了,称之为 JavaDB。

Derby 数据库通过 derby.properties 文件来配置,这个文件需要放在程序的 classpath 或者 jdk 的 javadb classpath 路径中。下面是一些配置的例子:

---------------------------- Derby derby.properties information ----------------------------
--### Derby System Home Directory
derby.system.home=D:/workspace/Boulevard/BoulevardDB


--### Derby Authentication Configuration and Turn On
derby.connection.requireAuthentication=true
derby.authentication.provider=BUILTIN
derby.database.propertiesOnly=true
--### User/Password Definition (System Level)
derby.user.alfred=alfred
--### User Authorization Definition (System Level)
derby.database.defaultAccessMode=fullAccess
derby.fullAccessUsers=cube


--### Some Others Configuration
derby.infolog.append=true
derby.storage.pageSize=4096
derby.storage.pageReservedSpace=60
derby.locks.deadlockTimeout=20
derby.locks.waitTimeout=30
---------------------------- Derby derby.properties information ----------------------------


Derby 数据库可以启动为两种不同的模式:嵌入式模式服务器模式

1. 嵌入式模式(embedded):当 Java 应用程序第一次尝试连接数据库时启动数据库实例,当程序进程结束时数据库实例也被关闭。当数据库实例启动后,仅仅启动它的进程可以访问,其它任何外部进程(运行在当前进程的JVM之外的程序)都不能访问。

    下面的命令用于启动并连接一个嵌入式数据库实例:
------------------------------- Derby connection information -------------------------------
-- ### Use ij command to connect embeded database; if not exist, create it.
CONNECT 'jdbc:derby:data;create=true;user=alfred;password=alfred' AS CUBE;
-- ### Use ij command to connect embeded database; if not exist, don't create it, throw out error.
CONNECT 'jdbc:derby:data;user=alfred;password=alfred' AS CUBE;
------------------------------- Derby connection information -------------------------------

2. 服务器模式(network server):数据库实例只能由命令行启动:"...\javadb\bin\startNetworkServer.bat",该实例始终有效,直到通过命令行停止:"...\javadb\bin\stopNetworkServer.bat"。任何运行在本地或远程的JVM进程都可以访问,不会随着访问它的程序的结束而关闭。

    下面的命令用于连接(不能启动)服务器数据库实例:
------------------------------- Derby connection information -------------------------------
-- ### Use ij command to connect network server database, reading default repository;
-- ### If not exist, create it.
CONNECT 'jdbc:derby://localhost:1527/data;create=true;user=alfred;password=alfred' AS BOULEVARD;
-- ### Use ij command to connect network server database; reading default repository;
-- ### If not exist, don't create it, throw out error.
CONNECT 'jdbc:derby://localhost:1527/data;user=alfred;password=alfred' AS BOULEVARD;
-- ### Use ij command to connect network server database, reading specific repository;
-- ### If not exist, create it.
CONNECT 'jdbc:derby://localhost:1527/D:/workspace/Boulevard/BoulevardDB/data;create=true;user=alfred;password=alfred' AS BOULEVARD;
-- ### Use ij command to connect network server database, reading specific repository;
-- ### If not exist, don't create it, throw out error.
CONNECT 'jdbc:derby://localhost:1527/D:/workspace/Boulevard/BoulevardDB/data;user=alfred;password=alfred' AS BOULEVARD;
------------------------------- Derby connection information -------------------------------

posted @ 2010-01-06 17:44 Alfred. Yao 阅读(1993) | 评论 (0)编辑 收藏

Hibernate提供了三个级别的缓存策略:Session缓存(基本的事务级缓存),Query Cache(查询缓存),Seond-Level Cache(二级缓存)

Session缓存(First-Level Cache):Session是Hibernate用于管理持久化对象的核心机制,它是针对持久性数据的事务级缓存。PersistenceContext中包括:

entityKeyscollectionKeys

insertionupdatesdeletions

collectionCreationscollectionRemovalscollectionUpdates

由此可见,Session不会把所有的持久化对象实体本身缓存,而只是缓存实体或Collection的Identiy值,和状态被更新过的实体或Collection(包括插入,更新,删除)

当Session中缓存的内容过多时会导致OutOfMemory的问题,可以通过两种方式删除缓存的内容:

  • clear(): 清除所有Session缓存;
  • evict(PersistentObject): 将一个特定持久化对象从Session缓存中删除。 

Query Cache:Hibernate可以对频繁进行的查询(相同查询,相同参数)进行缓存以提高效率。但是查询缓存不会缓存结果集中实际的数据实体,而是只缓存Identiy值和结果值类型,因此它应该总是和二级缓存一起使用。但由于实际环境中完全相同的频繁查询很少,所以默认该缓存是disabled的。可以通过两种方式使之生效:(个人觉得这个缓存意义不大)

配置属性:<prop key="hibernate.cache.use_query_cache">true</prop>

代码:Query.setCacheable(true)

Second-Level Cache:  二级缓存,用于对持久化对象实体或Collection的实际数据进行缓存,用于提供一个集群级别(cluster level),JVM级别,或文件系统级的缓存机制。Hibernate的二级缓存通常都是由第三方的开源项目提供,可以通过配置选择特定的缓存实现,例如:EHCache,OSCache,JBoss Cable等。

配置属性:<prop key="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</prop>

二级缓存同样可以通过两种方式来管理:

配置属性:<prop key="hibernate.cache.use_second_level_cache">false</prop>

代码:Query.setCacheMode(CacheMode.IGNORE) (或者GET,NORMAL,PUT,REFRESH)

  • IGNORE: 禁用二级缓存
  • NORMAL: 启用二级缓存,正常读写
  • GET: 只从二级缓存读,除非有数据update
  • PUT: 只向二级缓存写
  • REFRESH: 强制对写入二级缓存的内容进行刷新

最后,在程序调试中,我们可能需要查看各种Cache中实际缓存的内容,可以通过配置属性让Hibernate收集缓存统计信息。当我们遭遇可能由于缓存导致的问题时,这一方法特别有用。

配置属性:<prop key="hibernate.generate_statistics">true</prop>

在代码中获得统计信息:sessionFactory.getStatistics()

posted @ 2010-01-06 17:21 Alfred. Yao 阅读(1927) | 评论 (0)编辑 收藏

在做自动定价流程的性能测试中,发现一个很棘手的性能问题:一个非常简单的查询跑起来非常非常慢,几乎每一秒钟只从数据库返回一条记录!数据库是zLinux Server 上的 DB2 8.5,CPU Utilization 只有 35% 而已。

花了大半天时间,最后我以为是数据库的问题(因为是由别人管理的远程数据库),SQL如此简单应该没有问题;但是从DBA的反馈是他检查了数据库的所有状态,一切正常:( 我几乎崩溃…

但今天,最终还是承认SQL确实有问题,而且犯大忌 (对查询优化的认识有待提高啊…)

-- less 1 record inserted per second, 30000 records insertion need 10 hours
-- to complete (timeout...)
INSERT INTO WWPRT.CABLEPRODUCT_PRICETYPE_JOIN_CN (CABLEPRODUCTID,
PRICETYPE, SLAVE) 
(
     SELECT cableproduct.ID, pricetypes.PRICETYPE, 'N'
     FROM WWPRT.CABLE_PRODUCT_JOIN_CN AS cableproduct,
     (
          SELECT DISTINCT usprice.PRICETYPE FROM WWPRT.PRICE_CN usprice
          JOIN WWPRT.CONF_PRICETYPE pt ON pt.ID = usprice.PRICETYPE
          WHERE usprice.CABLEID = 'USCABF3FVT01'
          AND pt.DOMAIN IN ( SELECT DISTINCT DOMAIN FROM WWPRT.CONF_GAP )
     ) AS pricetypes
     WHERE CABLEID = 'GEOANNF3FVT1'
     AND EXISTS (
         ......
     )
)


其罪魁祸首就是“from”分句中的 subselect 语句,数据库会对 WWPRT.CABLE_PRODUCT_JOIN_CN 表中的每一条记录都执行一次上面的 subselect 语句,太可怕了!修改了 SQL 如下,让上面的 subselect 部分只执行一次作为临时表:


-- 30000 records insertion only costs 9 seconds to complete!
WITH US_PRICETYPES (PRICETYPE) AS (
    SELECT DISTINCT usprice.PRICETYPE FROM WWPRT.PRICE_CN usprice
    JOIN WWPRT.CONF_PRICETYPE pt ON pt.ID = usprice.PRICETYPE
    LEFT OUTER JOIN WWPRT.CONF_GAP gap ON pt.DOMAIN = gap.DOMAIN
    WHERE usprice.CABLEID = 'USCABF3FVT01'
    AND gap.DOMAIN IS NOT NULL
)
SELECT COUNT(*) FROM NEW TABLE
(
    INSERT INTO WWPRT.CABLEPRODUCT_PRICETYPE_JOIN_CN
    (CABLEPRODUCTID, PRICETYPE, SLAVE)
    (
    SELECT cableproduct.ID, pricetypes.PRICETYPE, 'N'
    FROM WWPRT.CABLE_PRODUCT_JOIN_CN AS cableproduct,
    US_PRICETYPES AS pricetypes
    WHERE CABLEID = 'GEOANNF3FVT1'
    AND EXISTS (
        ......
    )
))

性能的改进太可怕了,需要10个小时跑完的查询10秒就结束了!尽管我用的是DB2,但我想对其它数据库应该也有这样的问题,没有验证过…

Followed:
--------------------------------------------------------------------------------------
谢谢Daniel的建议:

如果程序不关心究竟有多少条记录被插入了,可以用另一个 Fetch,而不是 Count(*),这样性能会更好:

SELECT CABLEPRODUCTID FROM NEW TABLE
(INSERT INTO WWPRT.CABLEPRODUCT_PRICETYPE_JOIN_CN (CABLEPRODUCTID,
PRICETYPE, SLAVE)
(
     SELECT cableproduct.ID, pricetypes.PRICETYPE, 'N'
     FROM WWPRT.CABLE_PRODUCT_JOIN_CN AS cableproduct,
     US_PRICETYPES AS pricetypes
     WHERE CABLEID = 'GEOANNF3FVT1'
     AND EXISTS (
         ......
     )
)) FETCH FIRST 1 ROWS ONLY

posted @ 2010-01-06 16:43 Alfred. Yao 阅读(404) | 评论 (0)编辑 收藏

仅列出标题
共2页: 上一页 1 2