本人性格外向,乐于交友。

发布新日志

  • JUnitPerf 进行性能测试

    2010-04-08 15:28:27

     

    JUnitPerf 进行性能测试

    在应用程序的开发周期中,性能测试常被放到最后考虑,这并不是因为它不重要,而是因为存在这么多未知变量,很难有效地测试。在本月的 追求代码质量 系列中,Andrew Glover 使性能测试成为开发周期的一部分,并介绍了两种简单的实现方法。

    在应用程序的开发中,验证应用程序的性能几乎总处于次要的地位。请注意,我强调的是验证 应用程序的性能。应用程序的性能总是 首要考虑的因素,但开发周期中却很少包含对性能的验证。

    由于种种原因,性能测试常被延迟到开发周期的后期。以我的经验,企业之所以在开发过程中不包含性能测试是因为,他们不知道对于正在进行开发的应用程序要期待什么。提出了一些(性能)指数,但这些指数是基于预期负载提出的。

    发生下列两种情况之一时,性能测试就成为头等大事:

    生产中出现显而易见的性能问题。 
    在同意付费之前 ,客户或潜在客户询问有关性能指数的问题。 
    本月,我将介绍两种简单的性能测试技术,在上述两种情况中的任何一种发生前进行测试。

    用 JUnitPerf 进行测试

    在软件开发的早期阶段,使用 JUnit 很容易确定基本的低端性能指数。JUnitPerf 框架能够将测试快速地转化为简单的负载测试,甚至压力测试。 

    可使用 JUnitPerf 创建两种测试类型:TimedTest 和 LoadTest。这两种类型都基于 Decorator 设计模式并利用 JUnit 的 suite 机制。TimedTest 为测试样例创建一个(时间)上限 —— 如果超过这个时间,那么测试失败。LoadTest 和计时器一起运行,它通过运行所需的次数(时间间隔由配置的计时器控制),在一个特定的测试用例上创建一个人工负载。


    恰当的时限测试

    JUnitPerf TimedTest 让您可以编写有相关时间限制的测试 —— 如果超过了该限度,就认为测试是失败的(即便测试逻辑本身实际上是成功的)。在测试对于业务致关重要的方法时,时限测试相比其他测试来说,在确定和监控性能指数方面很有帮助。甚至可以测试得更加细致一些,可以测试一系列方法来确保它们满足特定的时间限制。 

    例如,假设存在一个 Widget 应用程序,其中,特定的对于业务致关重要的方法(如 createWidget())是严格的性能限制的测试目标。假设需要对执行该 create() 方法的功能方面进行性能测试。这通常会由不同的团队使用不同的工具在开发周期的后期加以确定,这通常不能指出精确的方法。但假设决定选择早期经常测试 方法取而代之。

    创建 TimedTest 首先要创建一个标准的 JUnit 测试。换言之,将对 TestCase 或其派生类进行扩展,并编写一个以 test 开头的方法,如清单 1 所示:


    清单 1. 简单的 widget 测试

    public class WidgetDAOImplTest extends TestCase { 
    private WidgetDAO dao; 

    public void testCreate() throws Exception{
    IWidget wdgt = new Widget(); 
    wdgt.setWidgetId(1000); 
    wdgt.setPartNumber("12-34-BBD"); 
    try{
    this.dao.createWidget(wdgt); 
    }catch(CreateException e){
    TestCase.fail("CreateException thrown creating a Widget"); 

    }

    protected void setUp() throws Exception { 
    ApplicationContext context = 
    new ClassPathXmlApplicationContext("spring-config.xml"); 
    this.dao = (WidgetDAO) context.getBean("widgetDAO"); 

    }



    由于 JUnitPerf 是一个基于装饰器的框架,为了真正地驾驭它,必须提供一个 suite() 方法并将现有的测试装饰以 TimedTest。TimedTest 以 Test 和执行该测试的最大时间量作为参数。

    也可以选择传入一个 boolean 标志作为第三个参数(false),这将导致测试快速失败 —— 意味着如果超过最大时间,JUnitPerf 将立即 迫使测试失败。否则,测试样例将完整运行,然后失败。区别很微妙:在一个失败的样例中,不带可选标志运行测试可以帮您了解运行总时间。传入 false 值却意味着得不到运行总时间。

    例如,在清单 2 中,我在运行 testCreate() 时设定了一个两秒钟的上限。如果执行总时间超过了这个时间,测试样例将失败。由于我并未传入可选的 boolean 参数,该测试将完整运行,而不管运行会持续多久。


    清单 2. 为生成 TimedTest 而实现的 suite 方法
    public static Test suite() {
    long maxElapsedTime = 2000; //2 seconds 
    Test timedTest = new TimedTest(
    new WidgetDAOImplTest("testCreate"), maxElapsedTime); 
    return timedTest; 
    }



    此测试通常在 JUnit 框架中运行 —— 现有的 Ant 任务、Eclipse 运行器等等,会像运行任何其他 JUnit 测试一样运行这个测试。惟一的不同是,该测试将发生在计时器的上下文中。 

    过度的负载测试

    与在测试场景中验证一个方法(或系列方法)的时间限制正好相反,JUnitPerf 也方便了负载测试。正如在 TimedTest 中一样,JUnitPerf 的 LoadTest 也像装饰器一样运行,它通过将 JUnit Test 和额外的线程信息绑定起来,从而模拟负载。

    使用 LoadTest,可以指定要模拟的用户(线程)数量,甚至为这些线程的启动提供计时机制。JUnitPerf 提供两类 Timer:ConstantTimer 和 RandomTimer。通过为 LoadTest 提供这两类计时器,可以更真实地模拟用户负载。如果没有 Timer,所有线程都会同时启动。

    清单 3 是用 ConstantTimer 实现的含 10 个模拟用户的负载测试:


    清单 3. 为生成负载测试而实现的 suite 方法
    public static Test suite() {
    int users = 10; 
    Timer timer = new ConstantTimer(100); 
    return new LoadTest(
    new WidgetDAOImplTest("testCreate"), 
    users, timer); 
    }



    请注意,testCreate() 方法运行 10 次,每个线程间隔 100 毫秒启动。未设定时间限制 —— 这些方法完整运行,如果其中任何的方法执行失败,JUnit 会相应地报告失败。

    用样式进行装饰

    装饰器并不局限于单个的装饰物。例如,在 Java™ I/O 中,可以为 FileInputStream 装饰上一个带 BufferedReader 的 InputStreamReader(只要记住:BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("infilename"), "UTF8")))。

    装饰可以有多个层次,JUnitPerf 的 TimedTest 和 LoadTest 也是一样。当这两个类彼此装饰时,将导致一些强制的测试场景,例如像这样的场景:在一项业务中放置了负载并应用了时间限制。或者,我们可以仅仅将之前的两个测试场景以如下方式结合起来:

    在 testCreate() 方法中放置一项负载。 
    规定每个线程必须在该时间限制内结束。 
    我通过为一个标准 Test 装饰上 LoadTest(由 TimedTest 装饰)应用了上述规范,清单 4 显示了其结果。


    清单 4. 经装饰的负载和时限测试
    public static Test suite() {
    int users = 10; 
    Timer timer = new ConstantTimer(100); 
    long maxElapsedTime = 2000; 
    return new TimedTest(new LoadTest(
    new WidgetDAOImplTest("testCreate"), users, timer), 
    maxElapsedTime); 
    }



    正如您所看到的那样,testCreate() 方法运行 10 次(每隔 100 毫秒启动一个线程),且每个线程必须在 2 秒内完成,否则整个测试场景将失败。

    使用注意

    尽管 JUnitPerf 是一个性能测试框架,但也要先大致估计一下测试要设定的性能指数。这是由于所有由 JUnitPerf 装饰的测试都通过 JUnit 框架运行,所以就存在额外的消耗,特别是在利用 fixture 时。由于 JUnit 本身用一个 setUp 和一个 tearDown() 方法装饰所有测试样例,所以要在测试场景的整个上下文中考虑执行时间。

    相应地,我经常创建使用我想要的 fixture 逻辑的测试,但也会运行一个空白测试来确定性能指数基线。这是一个大致的估计,但它必须作为基线添加到任何想要的测试限制中。

    例如,如果运行一个由 fixture 逻辑(使用 DbUnit)装饰的空白测试用时 2.5 秒,那么您想要的所有测试限制都应将这一额外时间考虑在内 —— 这可以从清单 5 中的基准测试中看到: 


    清单 5. JUnitPerf 基准测试
    public class DBUnitSetUpBenchmarkTest extends DatabaseTestCase {
    private WidgetDAO dao = null; 

    public void testNothing(){
    //should be about 2.5 seconds
    }

    protected IDatabaseConnection getConnection() throws Exception { 
    Class driverClass = Class.forName("org.hsqldb.jdbcDriver"); 
    Connection jdbcConnection = 
    DriverManager.getConnection(
    "jdbc:hsqldb:hsql://127.0.0.1", "sa", ""); 
    return new DatabaseConnection(jdbcConnection); 
    }

    protected IDataSet getDataSet() throws Exception { 
    return new FlatXmlDataSet(new File("test/conf/seed.xml")); 
    }

    protected void setUp() throws Exception {
    super.setUp(); 
    final ApplicationContext context = 
    new ClassPathXmlApplicationContext("spring-config.xml"); 
    this.dao = (WidgetDAO) context.getBean("widgetDAO"); 
    }
    }



    请注意,清单 5 的测试样例 testNothing() 什么都没做。其惟一的目的是确定运行 setUp() 方法(当然,该方法也通过 DbUnit 设置了一个数据库)的总时间。

    也请记住,测试时间将依赖于机器的配置而变化,同时也依赖于在执行 JUnitPerf 测试时运行的东西而变化。我经常发现,将 JUnitPerf 测试放到它们自己的分类中有助于将它们同标准测试隔离开。这意味着,在运行一个测试时不必每次都运行 JUnitPerf 测试,例如在一个 CI 环境中签入代码。我也会创建特定的 Ant 任务,从而只在精心策划的将性能测试考虑在内的场景或环境中运行这些测试。

    试试吧!

    用 JUnitPerf 进行性能测试无疑是一门严格的科学,但在开发生命周期的早期,这是确定和监控应用程序代码的低端性能的极佳方式。另外,由于它是一个基于装饰器的 JUnit 扩展框架,所以可以很容易地用 JUnitPerf 装饰现有的 JUnit 测试。

    想想您已经花了这么多时间来担心应用程序在负载下会怎样执行。用 JUnitPerf 进行性能测试可以为您减少担忧并节省时间,同时也确保了应用程序代码的质量。

  • JDBC连接各种数据库经验技巧集萃

    2010-03-25 11:30:11

    Java数据库连接(JDBC)由一组用 Java 编程语言编写的类和接口组成。JDBC 为工具/数据库开发人员提供了一个标准的 API,使他们能够用纯Java API 来编写数据库应用程序。然而各个开发商的接口并不完全相同,所以开发环境的变化会带来一定的配置变化。本文主要集合了不同数据库的连接方式。

      一、连接各种数据库方式速查表

      下面罗列了各种数据库使用JDBC连接的方式,可以作为一个手册使用。

      1、Oracle8/8i/9i数据库(thin模式)

    Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
    String url="jdbc:oracle:thin:@localhost:1521:orcl"; //orcl为数据库的SID
    String user="test";
    String password="test";
    Connection conn= DriverManager.getConnection(url,user,password);

      2、DB2数据库

    Class.forName("com.ibm.db2.jdbc.app.DB2Driver ").newInstance();
    String url="jdbc:db2://localhost:5000/sample"; //sample为你的数据库名
    String user="admin";
    String password="";
    Connection conn= DriverManager.getConnection(url,user,password);

      3、Sql Server7.0/2000数据库

    Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();
    String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb";
    //mydb为数据库
    String user="sa";
    String password="";
    Connection conn= DriverManager.getConnection(url,user,password);

      4、Sybase数据库

    Class.forName("com.sybase.jdbc.SybDriver").newInstance();
    String url =" jdbc:sybase:Tds:localhost:5007/myDB";//myDB为你的数据库名
    Properties sysProps = System.getProperties();
    SysProps.put("user","userid");
    SysProps.put("password","user_password");
    Connection conn= DriverManager.getConnection(url, SysProps);

      5、Informix数据库

    Class.forName("com.informix.jdbc.IfxDriver").newInstance();
    String url = "jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver;
    user=testuser;password=testpassword"; //myDB为数据库名
    Connection conn= DriverManager.getConnection(url);

      6、MySQL数据库

    Class.forName("org.gjt.mm.mysql.Driver").newInstance();
    String url ="jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1"
    //myDB为数据库名
    Connection conn= DriverManager.getConnection(url);

      7、PostgreSQL数据库

    Class.forName("org.postgresql.Driver").newInstance();
    String url ="jdbc:postgresql://localhost/myDB" //myDB为数据库名
    String user="myuser";
    String password="mypassword";
    Connection conn= DriverManager.getConnection(url,user,password);

      8、access数据库直连用ODBC的

    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") ;
    String url="jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ="+application.getRealPath("/Data/ReportDemo.mdb");
    Connection conn = DriverManager.getConnection(url,"","");
    Statement stmtNew=conn.createStatement();

    二、JDBC连接MySql方式

      下面是使用JDBC连接MySql的一个小的教程

      1、查找驱动程序

      MySQL目前提供的java驱动程序为Connection/J,可以从MySQL官方网站下载,并找到mysql-connector-java-3.0.15-ga-bin.jar文件,此驱动程序为纯java驱动程序,不需做其他配置。

      2、动态指定classpath

      如果需要执行时动态指定classpath,就在执行时采用-cp方式。否则将上面的.jar文件加入到classpath环境变量中。

      3、加载驱动程序

    try{
      Class.forName(com.mysql.jdbc.Driver);
      System.out.println(Success loading Mysql Driver!);
    }catch(Exception e)
    {
      System.out.println(Error loading Mysql Driver!);
      e.printStackTrace();
    }

      4、设置连接的url

      jdbc:mysql://localhost/databasename[?pa=va][&pa=va]

      三、以下列出了在使用JDBC来连接Oracle数据库时可以使用的一些技巧

      1、在客户端软件开发中使用Thin驱动程序

      在开发Java软件方面,Oracle的数据库提供了四种类型的驱动程序,二种用于应用软件、applets、servlets等客户端软件,另外二种用于数据库中的Java存储过程等服务器端软件。在客户机端软件的开发中,我们可以选择OCI驱动程序或Thin驱动程序。OCI驱动程序利用Java本地化接口(JNI),通过Oracle客户端软件与数据库进行通讯。Thin驱动程序是纯Java驱动程序,它直接与数据库进行通讯。为了获得最高的性能,Oracle建议在客户端软件的开发中使用OCI驱动程序,这似乎是正确的。但我建议使用Thin驱动程序,因为通过多次测试发现,在通常情况下,Thin驱动程序的性能都超过了OCI驱动程序。

      2、关闭自动提交功能,提高系统性能

      在第一次建立与数据库的连接时,在缺省情况下,连接是在自动提交模式下的。为了获得更好的性能,可以通过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,如下所示:

      conn.setAutoCommit(false);

      值得注意的是,一旦关闭了自动提交功能,我们就需要通过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。

      3、在动态SQL或有时间限制的命令中使用Statement对象

      在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。这可能会使你认为,使用PreparedStatement对象比使用Statement对象的速度更快。然而,我进行的测试表明,在客户端软件中,情况并非如此。因此,在有时间限制的SQL操作中,除非成批地处理SQL命令,我们应当考虑使用Statement对象。

      此外,使用Statement对象也使得编写动态SQL命令更加简单,因为我们可以将字符串连接在一起,建立一个有效的SQL命令。因此,我认为,Statement对象可以使动态SQL命令的创建和执行变得更加简单。

    4、利用helper函数对动态SQL命令进行格式化

      在创建使用Statement对象执行的动态SQL命令时,我们需要处理一些格式化方面的问题。例如,如果我们想创建一个将名字O‘Reilly插入表中的SQL命令,则必须使用二个相连的“‘‘”号替换O‘Reilly中的“‘”号。完成这些工作的最好的方法是创建一个完成替换操作的helper方法,然后在连接字符串心服用公式表达一个SQL命令时,使用创建的helper方法。与此类似的是,我们可以让helper方法接受一个Date型的值,然后让它输出基于Oracle的to_date()函数的字符串表达式。

      5、利用PreparedStatement对象提高数据库的总体效率

      在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令缓冲区。然后,每当执行同一个PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,我会建议在除动态SQL命令之外的所有情况下使用PreparedStatement对象。

      6、在成批处理重复的插入或更新操作中使用PreparedStatement对象

      如果成批地处理插入和更新操作,就能够显著地减少它们所需要的时间。Oracle提供的Statement和 CallableStatement并不真正地支持批处理,只有PreparedStatement对象才真正地支持批处理。我们可以使用addBatch()和executeBatch()方法选择标准的JDBC批处理,或者通过利用PreparedStatement对象的setExecuteBatch()方法和标准的executeUpdate()方法选择速度更快的Oracle专有的方法。要使用Oracle专有的批处理机制,可以以如下所示的方式调用setExecuteBatch():

    PreparedStatement pstmt3D null;
    try {
      ((OraclePreparedStatement)pstmt).setExecuteBatch(30);
      …
      pstmt.executeUpdate();
    }

      调用setExecuteBatch()时指定的值是一个上限,当达到该值时,就会自动地引发SQL命令执行,标准的executeUpdate()方法就会被作为批处理送到数据库中。我们可以通过调用PreparedStatement类的sendBatch()方法随时传输批处理任务。

      7、使用Oracle locator方法插入、更新大对象(LOB)

      Oracle的PreparedStatement类不完全支持BLOB和CLOB等大对象的处理,尤其是Thin驱动程序不支持利用PreparedStatement对象的setObject()和setBinaryStream()方法设置BLOB的值,也不支持利用setCharacterStream()方法设置CLOB的值。只有locator本身中的方法才能够从数据库中获取LOB类型的值。可以使用PreparedStatement对象插入或更新LOB,但需要使用locator才能获取LOB的值。由于存在这二个问题,因此,我建议使用locator的方法来插入、更新或获取LOB的值。

      8、使用SQL92语法调用存储过程

      在调用存储过程时,我们可以使用SQL92或Oracle PL/SQL,由于使用Oracle PL/SQL并没有什么实际的好处,而且会给以后维护你的应用程序的开发人员带来麻烦,因此,我建议在调用存储过程时使用SQL92。

      9、使用Object SQL将对象模式转移到数据库中

      既然可以将Oracle的数据库作为一种面向对象的数据库来使用,就可以考虑将应用程序中的面向对象模式转到数据库中。目前的方法是创建Java bean作为伪装的数据库对象,将它们的属性映射到关系表中,然后在这些bean中添加方法。尽管这样作在Java中没有什么问题,但由于操作都是在数据库之外进行的,因此其他访问数据库的应用软件无法利用对象模式。如果利用Oracle的面向对象的技术,可以通过创建一个新的数据库对象类型在数据库中模仿其数据和操作,然后使用JPublisher等工具生成自己的Java bean类。如果使用这种方式,不但Java应用程序可以使用应用软件的对象模式,其他需要共享你的应用中的数据和操作的应用软件也可以使用应用软件中的对象模式。

      10、利用SQL完成数据库内的操作

      我要向大家介绍的最重要的经验是充分利用SQL的面向集合的方法来解决数据库处理需求,而不是使用Java等过程化的编程语言。

      如果编程人员要在一个表中查找许多行,结果中的每个行都会查找其他表中的数据,最后,编程人员创建了独立的UPDATE命令来成批地更新第一个表中的数据。与此类似的任务可以通过在set子句中使用多列子查询而在一个UPDATE命令中完成。当能够在单一的SQL命令中完成任务,何必要让数据在网上流来流去的?我建议用户认真学习如何最大限度地发挥SQL的功能。

  • 最近心情

    2008-10-15 12:04:23

    最近心情--最近不是很开心

    小工作将近一年,现在发展了2家公司。在目前这个单位来看管理体制有点混乱!可能是新公司的过吧!在这里工作小3月了。这家单位不是做软件行业的。但是聘请的开发人员已经有很大的规模。光周末一起踢球的哥们就已经发展到了13位。

    单位开发需求总是根据老板的意图随意更改。搞的开发总是开发将近结束。又要从新改变。前一些日子居然要从新设计数据库结构~~~我们测试的也是测了白测。没有任何效果。需求变了哈~整个页面都变了。版本也是一个接着一个的来。测还是不测。我一直比较矛盾。测半天白测。整个是在做无用功。没有任何效果。

    开发所需要的各个阶段这个单位基本上是 口头需求、开发想象、测试无厘头啊!

    测试版本的控制权不在我们手里~无法在我们手里。现在好像要弄弄这个问题。不知道如何解决?目前就是写用例。测试兼容性 功能 UI 几个方面。纯手工操作!

    我也用过一些自动化测试工具QTP LR 我感觉用的太简单了就是简单录下脚本做个检查点、写个FOR循环。搞一下日期插件的赋值问题。LR就更别提了~~还没有做过压力测试!估计做了也不会让我们几个参与!大致可以知道最多就是录完脚本插弄一并发跑一下!看看结果。至于分析。我感觉没那么高的人!能链上数据库我看都存在很大问题。

    我想先学习把~这里人还不错。同志们没那么多乱七八糟的!比较单纯~

    最近正在研究单元方面,重要是比较好奇。多学点没坏处。可是遇到了不小阻力~求助论坛基本石沉大海~~~~求人不如求自啊?!~

     

  • 最近心情

    2008-05-03 17:29:28

    我。07界毕业生。当年9月来到了大城市开始找工作。倒目前这个单位是我的地2份工作。来这家单位做测试已经有一个月的时间对这个单位有了一个初步的了解。测试在这个单位的领导的眼中可能不是很重要。再单位同事的心目中测试部门也是可有可无的部门,这些原因是我不能左右的,我能左右的只有我的人生方向。我回一如既往的做测试,寻找一个新的单位可能是我目前的想法。这个单位的工作流程把握的不是很好。可能不适合我。
  • 该给找工作画上句号了

    2008-03-26 11:12:09

    不能漂流。需要稳定的工作学习了!~~
  • 丫的找工作中

    2008-01-10 12:45:56

    找工作。找工作

Open Toolbar