随着软件行业的发展,各种软件更加复杂化,对测试要求也越来越高,并且希望测试能及时的跟进研发的需求变更。
测试团队怎样才能高效的应对频繁的需求变更呢?这是当今测试团队应该思考的主要问题。
今天分享一些个人的看法:
1. 如果需求频繁的变更,人工测试肯定不能很好的跟进测试、确保质量,所以最大程度的自动化将是应付需求变更的第一步。
2. 由于系统的复杂性,导致自动化的内容和类型的复杂性也相应提高,所以对于如何组织自动化,及时、有效、高效的运行将成为应付频繁需求变更的第二步
- 在传统的做法里,如果是linux,大家可能会使用crontab来进行管理,而windows可能则会使用计划任务进行管理。但是这两种管理方式显然不能达到及时的响应,就是你时间间隔设得很短,也理论上也是无法项目包一到达就立马启动测试
- 不太利于扩展,并且易错性比较高
3. 基于以上两点,我将介绍消息中间件如果在复杂的自动化环境中提高效率
- 消息中间件,很多人比较熟悉的如ActiveMQ, MetaQ等等,但是个人觉得,在测试行业,activeMQ就足够用了,
他的消息持久性特点能够保证每一个测试不遗漏。另外,任务队列管理极其方便,可扩展的东西很多
4. ActiveMQ在测试中应用流程
- 搭建ActiveMQ 服务器 (很简单)
- 开发实现ActiveMQ的 agent consumer 和 producer
a) producer 负责跟开发对接,如果有任何任务需求,及时通过producer 将任务加到消息队列。同时,详细的自定义属性很灵活,对于任务描述很有利
b) agent consumer端可以通过推或拉的方式来接收任务,如果是推的方式,只需实现ActiveMQ的订阅功能,当有任何到来时,订阅的测试服务器将自动消费信息进行测试(这时候消息的属性非常有用了),如果是拉的模式, 只需实现灵活的agent consumer,时时监听各自队列,任何任务到来时都会主动启动测试,非常便于扩展测试服务器,如,发现任务比较多的时候,直接在任何一个服务器上去启动另一个agent便可加入测试中来
c) 消息持久性的特点,你不用担心消息会丢失
d) 很容易就能实现producer端的手动干预任务,灵活,高效
5. 通过agent端运行测试,并将测试结果入库,及时通过管理系统展示测试结果,然后邮件方式获取测试结果页面,及时的共享出去
以上步骤,灵活,高效的完成了复杂系统的频繁变更测试。欢迎大家拍砖
所谓的对象序列化(在很多地方也被成为串行化),是指将对象转换成二进制数据流的一种实现手段。通过将对象序列化,可以方便地实现对象的传输及保存。
在Java中提供有ObjectInputStream与ObjectOutputStream这两个类用于序列化对象的操作。这两个类是用于存储和独缺对象的输入输出流类,只要把对象中的所有成员变量都存储起来,就等于保存了这个对象,之后从保存的对象之中再将对象读取进来就可以继续使用此对象。ObjectInputStream类与ObjectOutputStream类,用于帮助开发者完成保存和读取对象成员变量取值的过程,但要求读写或存储的对象必须实现了Serializable接口,但Serializable接口中没有定义任何方法,仅仅被用作一种标志,已被编译器作特殊处理。
下面是对象的序列化范例:
import java.io.* ; public class Person implements Serializable { private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } public String toString() { return " 姓名:"+this.name+",年龄:"+this.age ; } }; |
这个类实现了Serializable接口,所以此类的对象可序列化。下面的范例使用ObjectOutputStream与ObjectInputStream将Person类的对象保存在文件之中:
import java.io.*; public class SerializableDemo { public static void main( String args[] ) throws Exception { File f = new File("SerializedPerson") ; serialize(f); deserialize(f); } // 以下方法为序列化对象方法 public static void serialize(File f) throws Exception { OutputStream outputFile = new FileOutputStream(f); ObjectOutputStream cout = new ObjectOutputStream(outputFile); cout.writeObject(new Person("张三",25)); cout.close(); } // 以下方法为反序列化对象方法 public static void deserialize(File f) throws Exception { InputStream inputFile = new FileInputStream(f); ObjectInputStream cin = new ObjectInputStream(inputFile); Person p = (Person) cin.readObject(); System.out.println(p); } } |
在程序中声明了一个deserialize()方法,此方法用于从文件中读取已经保存的对象。
另外,如果不希望类中的某个属性被序列化,可以在声明属性之前加上transient关键字。用这个关键字修饰的属性没有被保存下来,输出时,将输出默认值。
数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连接池都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前所有的应用服务器取数据库连接的方式都是这种方式实现的。但是另外一个共同的问题是,它们同时不允许使用者显式的调用Connection.close()方法,而需要用其规定的一个方法来关闭连接。这种做法有两个缺点:
第一:改变了用户使用习惯,增加了用户的使用难度。
首先我们来看看一个正常的数据库操作过程:
int executeSQL(String sql) throws SQLException { Connection conn = getConnection(); //通过某种方式获取数据库连接 PreparedStatement ps = null; int res = 0; try{ ps = conn.prepareStatement(sql); res = ps.executeUpdate(); }finally{ try{ ps.close(); }catch(Exception e){} try{ conn.close();// }catch(Exception e){} } return res; } |
使用者在用完数据库连接后通常是直接调用连接的方法close来释放数据库资源,如果用我们前面提到的连接池的实现方法,那语句conn.close()将被某些特定的语句所替代。
第二:使连接池无法对之中的所有连接进行独占控制。由于连接池不允许用户直接调用连接的close方法,一旦使用者在使用的过程中由于习惯问题直接关闭了数据库连接,那么连接池将无法正常维护所有连接的状态,考虑连接池和应用由不同开发人员实现时这种问题更容易出现。
综合上面提到的两个问题,我们来讨论一下如何解决这两个要命的问题。
首先我们先设身处地的考虑一下用户是想怎么样来使用这个数据库连接池的。用户可以通过特定的方法来获取数据库的连接,同时这个连接的类型应该是标准的java.sql.Connection。用户在获取到这个数据库连接后可以对这个连接进行任意的操作,包括关闭连接等。
通过对用户使用的描述,怎样可以接管Connection.close方法就成了我们这篇文章的主题。
为了接管数据库连接的close方法,我们应该有一种类似于钩子的机制。例如在Windows编程中我们可以利用Hook API来实现对某个Windows API的接管。在JAVA中同样也有这样一个机制。JAVA提供了一个Proxy类和一个InvocationHandler,这两个类都在java.lang.reflect包中。我们先来看看SUN公司提供的文档是怎么描述这两个类的。
public interface InvocationHandler InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler. |
SUN的API文档中关于Proxy的描述很多,这里就不罗列出来。通过文档对接口InvocationHandler的描述我们可以看到当调用一个Proxy实例的方法时会触发Invocationhanlder的invoke方法。从JAVA的文档中我们也同时了解到这种动态代理机制只能接管接口的方法,而对一般的类无效,考虑到java.sql.Connection本身也是一个接口由此就找到了解决如何接管close方法的出路。
首先,我们先定义一个数据库连接池参数的类,定义了数据库的JDBC驱动程序类名,连接的URL以及用户名口令等等一些信息,该类是用于初始化连接池的参数,具体定义如下:
public class ConnectionParam implements Serializable { private String driver; //数据库驱动程序 private String url; //数据连接的URL private String user; //数据库用户名 private String password; //数据库密码 private int minConnection = 0; //初始化连接数 private int maxConnection = 50; //最大连接数 private long timeoutValue = 600000;//连接的最大空闲时间 private long waitTime = 30000; //取连接的时候如果没有可用连接最大的等待时间 |
其次是连接池的工厂类ConnectionFactory,通过该类来将一个连接池对象与一个名称对应起来,使用者通过该名称就可以获取指定的连接池对象,具体代码如下:
/** * 连接池类厂,该类常用来保存多个数据源名称合数据库连接池对应的哈希 * @author liusoft */ public class ConnectionFactory { //该哈希表用来保存数据源名和连接池对象的关系表 static Hashtable connectionPools = null; static{ connectionPools = new Hashtable(2,0.75F); } /** * 从连接池工厂中获取指定名称对应的连接池对象 * @param dataSource 连接池对象对应的名称 * @return DataSource 返回名称对应的连接池对象 * @throws NameNotFoundException 无法找到指定的连接池 */ public static DataSource lookup(String dataSource) throws NameNotFoundException { Object ds = null; ds = connectionPools.get(dataSource); if(ds == null || !(ds instanceof DataSource)) throw new NameNotFoundException(dataSource); return (DataSource)ds; } /** * 将指定的名字和数据库连接配置绑定在一起并初始化数据库连接池 * @param name 对应连接池的名称 * @param param 连接池的配置参数,具体请见类ConnectionParam * @return DataSource 如果绑定成功后返回连接池对象 * @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常 * @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类 * @throws IllegalAccessException 连接池配置中的驱动程序类有误 * @throws InstantiationException 无法实例化驱动程序类 * @throws SQLException 无法正常连接指定的数据库 */ public static DataSource bind(String name, ConnectionParam param) throws NameAlreadyBoundException,ClassNotFoundException, IllegalAccessException,InstantiationException,SQLException { DataSourceImpl source = null; try{ lookup(name); throw new NameAlreadyBoundException(name); }catch(NameNotFoundException e){ source = new DataSourceImpl(param); source.initConnection(); connectionPools.put(name, source); } return source; } /** * 重新绑定数据库连接池 * @param name 对应连接池的名称 * @param param 连接池的配置参数,具体请见类ConnectionParam * @return DataSource 如果绑定成功后返回连接池对象 * @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常 * @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类 * @throws IllegalAccessException 连接池配置中的驱动程序类有误 * @throws InstantiationException 无法实例化驱动程序类 * @throws SQLException 无法正常连接指定的数据库 */ public static DataSource rebind(String name, ConnectionParam param) throws NameAlreadyBoundException,ClassNotFoundException, IllegalAccessException,InstantiationException,SQLException { try{ unbind(name); }catch(Exception e){} return bind(name, param); } /** * 删除一个数据库连接池对象 * @param name * @throws NameNotFoundException */ public static void unbind(String name) throws NameNotFoundException { DataSource dataSource = lookup(name); if(dataSource instanceof DataSourceImpl){ DataSourceImpl dsi = (DataSourceImpl)dataSource; try{ dsi.stop(); dsi.close(); }catch(Exception e){ }finally{ dsi = null; } } connectionPools.remove(name); } } |
ConnectionFactory主要提供了用户将将连接池绑定到一个具体的名称上以及取消绑定的操作。使用者只需要关心这两个类即可使用数据库连接池的功能。下面我们给出一段如何使用连接池的代码:
String name = "pool"; String driver = " sun.jdbc.odbc.JdbcOdbcDriver "; String url = "jdbc:odbc:datasource"; ConnectionParam param = new ConnectionParam(driver,url,null,null); param.setMinConnection(1); param.setMaxConnection(5); param.setTimeoutValue(20000); ConnectionFactory.bind(name, param); System.out.println("bind datasource ok."); |
//以上代码是用来登记一个连接池对象,该操作可以在程序初始化只做一次即可
//以下开始就是使用者真正需要写的代码
DataSource ds = ConnectionFactory.lookup(name); try{ for(int i=0;i<10;i++){ Connection conn = ds.getConnection(); try{ testSQL(conn, sql); }finally{ try{ conn.close(); }catch(Exception e){} } } }catch(Exception e){ e.printStackTrace(); }finally{ ConnectionFactory.unbind(name); System.out.println("unbind datasource ok."); System.exit(0); } |
从使用者的示例代码就可以看出,我们已经解决了常规连接池产生的两个问题。但是我们最最关心的是如何解决接管close方法的办法。接管工作主要在ConnectionFactory中的两句代码:
source = new DataSourceImpl(param);
source.initConnection();
DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方法getConnection
/** * @see javax.sql.DataSource#getConnection(String,String) */ public Connection getConnection(String user, String password) throws SQLException { //首先从连接池中找出空闲的对象 Connection conn = getFreeConnection(0); if(conn == null){ //判断是否超过最大连接数,如果超过最大连接数 //则等待一定时间查看是否有空闲连接,否则抛出异常告诉用户无可用连接 if(getConnectionCount() >= connParam.getMaxConnection()) conn = getFreeConnection(connParam.getWaitTime()); else{//没有超过连接数,重新获取一个数据库的连接 connParam.setUser(user); connParam.setPassword(password); Connection conn2 = DriverManager.getConnection(connParam.getUrl(), user, password); //代理将要返回的连接对象 _Connection _conn = new _Connection(conn2,true); synchronized(conns){ conns.add(_conn); } conn = _conn.getConnection(); } } return conn; } /** * 从连接池中取一个空闲的连接 * @param nTimeout 如果该参数值为0则没有连接时只是返回一个null * 否则的话等待nTimeout毫秒看是否还有空闲连接,如果没有抛出异常 * @return Connection * @throws SQLException */ protected synchronized Connection getFreeConnection(long nTimeout) throws SQLException { Connection conn = null; Iterator iter = conns.iterator(); while(iter.hasNext()){ _Connection _conn = (_Connection)iter.next(); if(!_conn.isInUse()){ conn = _conn.getConnection(); _conn.setInUse(true); break; } } if(conn == null && nTimeout > 0){ //等待nTimeout毫秒以便看是否有空闲连接 try{ Thread.sleep(nTimeout); }catch(Exception e){} conn = getFreeConnection(0); if(conn == null) throw new SQLException("没有可用的数据库连接"); } return conn; } |
DataSourceImpl类中实现getConnection方法的跟正常的数据库连接池的逻辑是一致的,首先判断是否有空闲的连接,如果没有的话判断连接数是否已经超过最大连接数等等的一些逻辑。但是有一点不同的是通过DriverManager得到的数据库连接并不是及时返回的,而是通过一个叫_Connection的类中介一下,然后调用_Connection.getConnection返回的。如果我们没有通过一个中介也就是JAVA中的Proxy来接管要返回的接口对象,那么我们就没有办法截住Connection.close方法。
终于到了核心所在,我们先来看看_Connection是如何实现的,然后再介绍是客户端调用Connection.close方法时走的是怎样一个流程,为什么并没有真正的关闭连接。
/** * 数据连接的自封装,屏蔽了close方法 * @author Liudong */ class _Connection implements InvocationHandler { private final static String CLOSE_METHOD_NAME = "close"; private Connection conn = null; //数据库的忙状态 private boolean inUse = false; //用户最后一次访问该连接方法的时间 private long lastAccessTime = System.currentTimeMillis(); _Connection(Connection conn, boolean inUse){ this.conn = conn; this.inUse = inUse; } /** * Returns the conn. * @return Connection */ public Connection getConnection() { //返回数据库连接conn的接管类,以便截住close方法 Connection conn2 = (Connection)Proxy.newProxyInstance( conn.getClass().getClassLoader(), conn.getClass().getInterfaces(),this); return conn2; } /** * 该方法真正的关闭了数据库的连接 * @throws SQLException */ void close() throws SQLException{ //由于类属性conn是没有被接管的连接,因此一旦调用close方法后就直接关闭连接 conn.close(); } /** * Returns the inUse. * @return boolean */ public boolean isInUse() { return inUse; } /** * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object) */ public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { Object obj = null; //判断是否调用了close的方法,如果调用close方法则把连接置为无用状态 if(CLOSE_METHOD_NAME.equals(m.getName())) setInUse(false); else obj = m.invoke(conn, args); //设置最后一次访问时间,以便及时清除超时的连接 lastAccessTime = System.currentTimeMillis(); return obj; } /** * Returns the lastAccessTime. * @return long */ public long getLastAccessTime() { return lastAccessTime; } /** * Sets the inUse. * @param inUse The inUse to set */ public void setInUse(boolean inUse) { this.inUse = inUse; } } |
一旦使用者调用所得到连接的close方法,由于用户的连接对象是经过接管后的对象,因此JAVA虚拟机会首先调用_Connection.invoke方法,在该方法中首先判断是否为close方法,如果不是则将代码转给真正的没有被接管的连接对象conn。否则的话只是简单的将该连接的状态设置为可用。到此您可能就明白了整个接管的过程,但是同时也有一个疑问:这样的话是不是这些已建立的连接就始终没有办法真正关闭?答案是可以的。我们来看看ConnectionFactory.unbind方法,该方法首先找到名字对应的连接池对象,然后关闭该连接池中的所有连接并删除掉连接池。在DataSourceImpl类中定义了一个close方法用来关闭所有的连接,详细代码如下:
/** * 关闭该连接池中的所有数据库连接 * @return int 返回被关闭连接的个数 * @throws SQLException */ public int close() throws SQLException { int cc = 0; SQLException excp = null; Iterator iter = conns.iterator(); while(iter.hasNext()){ try{ ((_Connection)iter.next()).close(); cc ++; }catch(Exception e){ if(e instanceof SQLException) excp = (SQLException)e; } } if(excp != null) throw excp; return cc; } |
该方法一一调用连接池中每个对象的close方法,这个close方法对应的是_Connection中对close的实现,在_Connection定义中关闭数据库连接的时候是直接调用没有经过接管的对象的关闭方法,因此该close方法真正的释放了数据库资源。
以上文字只是描述了接口方法的接管,具体一个实用的连接池模块还需要对空闲连接的监控并及时释放连接。
一、编写测试案例向MySQL数据库中插入百万条数据。测试数据表建表脚本如下:use db_xk;
drop table if exists tb_test2; create table tb_test2 ( id int primary key auto_increment, subject varchar(50) not null, description varchar(200) not null, teacher_id int(10) zerofill not null, student_id int(10) zerofill default null, state boolean not null default false );state boolean not null default false ); |
测试案例源码如下:
package test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import util.DBUtil; public class TestDataBase2 { public static void main(String[] args) { Connection conn = DBUtil.getConnection(); String sql = "insert into tb_test2(subject, description, teacher_id, student_id) values (?,?,?,?)"; try { PreparedStatement prep = conn.prepareStatement(sql); // 将连接的自动提交关闭,数据在传送到数据库的过程中相当耗时 conn.setAutoCommit(false); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { long start2 = System.currentTimeMillis(); // 一次性执行插入10万条数据 for (int j = 0; j < 100000; j++) { prep.setString(1, "test2"); prep.setString(2, "test3"); prep.setInt(3, 1234562); prep.setInt(4, 12354545); // 将预处理添加到批中 prep.addBatch(); } // 预处理批量执行 prep.executeBatch(); prep.clearBatch(); conn.commit(); long end2 = System.currentTimeMillis(); // 批量执行一次批量打印执行依次的时间 System.out.print("inner"+i+": "); System.out.println(end2 - start2); } long end = System.currentTimeMillis(); System.out.print("total: "); System.out.println(end - start); } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn); } } } |
linux操作系统,制定定时任务步骤:
用ssh工具连接到系统后,执行如下步骤:
1)crontab -l 查看定时;
2)crontab -e 此时表示已可以编辑定时任务,但还没进入编辑模式;
3)再次输入i,进入编辑模式;
4)把自己写的定时代码,拷贝进来;如下:
0 4 * * * /opt/ltim/tomcat/bin/shutdown.sh ---表示每天4点关闭tomcat; 20 4 * * * /opt/ltim/tomcat/bin/startup.sh ---表示每天4点十分打开tomcat; |
5)按住esc键,退出编辑模式;
6)保存退出输入“:wq!”;
今天在做定时任务的时候,出现情况:手动重启tomcat可以执行,但是放在crontab定时任务里就没办法执行,解决办法是在.sh文件里面配置一下java_home
export JRE_HOME=/opt/ltim/j2sdk
另:附上重启tomcat的shell脚本:
#!/bin/sh #./etc/profile export JRE_HOME=/opt/ltim/j2sdk sh /opt/ltim/tomcat/bin/shutdown.sh sleep 30s sh /opt/ltim/tomcat/bin/startup.sh |
Maven 提供了跳过单元测试的能力,只需要使用 Surefire 插件的 skip 参数。 在命令行,只要简单的给任何目标添加 maven.test.skip 属性就能跳过测试:
$ mvn install -Dmaven.test.skip=true ... [INFO] [compiler:testCompile] [INFO] Not compiling test sources [INFO] [surefire:test] [INFO] Tests are skipped. ... |
当 Surefire 插件到达 test 目标的时候,如果 maven.test.skip 设置为 true ,它就会跳过单元测试。 另一种配置 Maven 跳过单元测试的方法是给你项目的 pom.xml 添加这个配置。 你需要为你的 build 添加 plugin 元素。
<project> [...] <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> [...] </project> |
当需要单元测试时,注意,一定要设置为false。
今天给大家分享一个高亮对象的方法,这个方法不止适用selenium,只要你是用java编写代码,其都可以
具体的js代码
package js;//该class在js包中 import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; // Highlight WebElement public class Highlight { public void highlightElement(WebDriver driver, WebElement element) { JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("element = arguments[0];" + "original_style = element.getAttribute('style');" + "element.setAttribute('style', original_style + \";" + "background: yellow; border: 2px solid red;\");" + "setTimeout(function(){element.setAttribute('style', original_style);}, 1000);", element); } } |
在具体的class中调用该js
import js.Highlight;//引入需要的js文件 WebElement sche= driver.findElement(By.name("preschedule"));//对位对象 Highlight hi=new Highlight(); hi.highlightElement(driver,sche);//高亮所定位的对象 |
建议:希望大家把不同的东西放在不同包里面,这样有利于后期的维护
所谓的对象序列化(在很多地方也被成为串行化),是指将对象转换成二进制数据流的一种实现手段。通过将对象序列化,可以方便地实现对象的传输及保存。
在Java中提供有ObjectInputStream与ObjectOutputStream这两个类用于序列化对象的操作。这两个类是用于存储和独缺对象的输入输出流类,只要把对象中的所有成员变量都存储起来,就等于保存了这个对象,之后从保存的对象之中再将对象读取进来就可以继续使用此对象。ObjectInputStream类与ObjectOutputStream类,用于帮助开发者完成保存和读取对象成员变量取值的过程,但要求读写或存储的对象必须实现了Serializable接口,但Serializable接口中没有定义任何方法,仅仅被用作一种标志,已被编译器作特殊处理。
下面是对象的序列化范例:
import java.io.* ; public class Person implements Serializable { private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } public String toString() { return " 姓名:"+this.name+",年龄:"+this.age ; } }; |
这个类实现了Serializable接口,所以此类的对象可序列化。下面的范例使用ObjectOutputStream与ObjectInputStream将Person类的对象保存在文件之中:
import java.io.*; public class SerializableDemo { public static void main( String args[] ) throws Exception { File f = new File("SerializedPerson") ; serialize(f); deserialize(f); } // 以下方法为序列化对象方法 public static void serialize(File f) throws Exception { OutputStream outputFile = new FileOutputStream(f); ObjectOutputStream cout = new ObjectOutputStream(outputFile); cout.writeObject(new Person("张三",25)); cout.close(); } // 以下方法为反序列化对象方法 public static void deserialize(File f) throws Exception { InputStream inputFile = new FileInputStream(f); ObjectInputStream cin = new ObjectInputStream(inputFile); Person p = (Person) cin.readObject(); System.out.println(p); } } |
在程序中声明了一个deserialize()方法,此方法用于从文件中读取已经保存的对象。
另外,如果不希望类中的某个属性被序列化,可以在声明属性之前加上transient关键字。用这个关键字修饰的属性没有被保存下来,输出时,将输出默认值。
输入中文时数据库中显示为乱码,提供以下两种解决方案:
1.修改字段类型
varchar是采用单字节存数
nvarchar采用的是unico编码,存储两个字节
一个英文字母存varchar格式,占一个字节;存nvarchar占2个字节。
varchar也可以存储中文,如果系统不支持中文,就会出现乱码(??)。
nvarchar的另一个好处是在判断字符串的时候可以不考虑中英文差别。
在存储代价低廉的情况下,推荐使用nvarchar,在确保不会出现中文的情况下可以采用varchar。
在语句中使用N'',N表示unicode的意思,表示按unicode进行编码.
如:
insert into UserInfo(UserName) values (N'XXXX') |
2.修改数据库的排序规则解决问题
假设数据库名为testDB
alter database testDB collate Chinese_PRC_CI_AS; |
执行成功的前提是要关闭其它连接
(51Testing软件测试网获人民邮电出版社和作者授权连载本书部分章节。任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。)
5.5.7 XPath(xPathToFind)
如果以上查找方法都无法定位到指定对象,那么可以按XPath进行查找。例如,百度首页有文本框,如图5-35所示。
图5-35 百度搜索文本框
其HTML代码如下:
<input id="kw" class="s_ipt" type="text" maxlength="100" name="wd" autocomplete="off"> |
如要操作该文本框,则可以通过其XPath表达式“//input[@id='kw']”作为查找条件获取该对象,找到该文本框,然后再输入文本,例如程序清单5-27和程序清单5-28所示的代码。
程序清单5-27 C#代码
IWebDriver driver = new FirefoxDriver(); INavigation navigation = driver.Navigate(); navigation.GoToUrl(http://www.baidu.com); IWebElement baiduTextBox = driver.FindElement(By.XPath("//input[@id='kw']")); baiduTextBox.SendKeys("找到文本框"); |
程序清单5-28 Java代码
WebDriver driver = new FirefoxDriver(); Navigation navigation = driver.navigate(); navigation.to(http://www.baidu.com); WebElement baiduTextBox = driver.findElement(By.xpath("//input[@id='kw']")); baiduTextBox.sendKeys("找到文本框"); |
5.6 操作页面元素WebElement
在Selenium 1中,直接通过Selenium的各种方法来操作页面元素,但在Selenium 2中则不同,需要通过上5.5讲到的By对象先定位到对应的页面元素,然后调用这个页面元素的相关方法来进行操作。
可以通过WebDriver的FindElement()方法获得WebElement的对象实例。
在获取页面元素后,就可以对该页面元素进行各种操作了。
5.6.1 Click()
Click()方法用于执行单击元素的操作。例如,百度首页上有“登录”超级链接,如图5-36所示。
图5-36 登录按钮
要单击“登录”超级链接,代码如程序清单5-29或程序清单5-30所示/
程序清单5-29 C#代码
IWebDriver driver = new FirefoxDriver(); INavigation navigation = driver.Navigate(); navigation.GoToUrl(http://www.baidu.com); IWebElement baiduLogin = driver.FindElement(By.LinkText("登录")); baiduLogin.Click(); |
程序清单5-30 Java代码
WebDriver driver = new FirefoxDriver(); Navigation navigation = driver.navigate(); navigation.to(http://www.baidu.com); WebElement baiduLogin = driver.findElement(By.LinkText("登录")); baiduLogin.click(); |
在执行“WebElement baiduLogin = driver.findElement(By.LinkText("登录"));”时,程序先通过“driver.findElement(By.LinkText("登录"));”找到该按钮,然后将其赋值给变量baiduLogin。baiduLogin就是获取到的页面元素,它代表“登录”按钮,获取到它之后就可以对它进行各种操作了,例如单击操作baiduLogin.click()。
注意,在Selenium 2中没有Check/UnCheck这类方法来勾选或取消复选框和单选框,所以只能通过Click方法来进行勾选或取消勾选。例如,百度贴吧的页面上有“记住我的登录状态”复选框如图5-37所示。
只能使用Click()方法对其进行勾选,如程序清单5-31和程序清单5-32所示。
程序清单5-31 C#代码
IWebDriver driver = new FirefoxDriver(); INavigation navigation = driver.Navigate(); navigation.GoToUrl(http://tieba.baidu.com/index.html); IWebElement rememberMe = driver.FindElement(By.Id("pass_loginLite_input_isMem0")); rememberMe.Click(); |
程序清单5-32 Java代码
WebDriver driver = new FirefoxDriver(); Navigation navigation = driver.navigate(); navigation.to(http://tieba.baidu.com/index.html); WebElement rememberMe = driver.findElement(By.id("pass_loginLite_input_isMem0")); rememberMe.click(); |
对于Selenium 2来说,因为页面元素没有Select方法,所以也只能用Click模拟实现对下拉列表框的选择。以百度贴吧搜索为例,如图5-38所示,假设要在排序方式下拉列表框中选择“按相关性进行排序”。
其HTML代码如图5-39所示。
可以编写代码即可选择“按相关性进行排序”,如程序清单5-33和程序清单5-34所示。
图5-38 搜索排序下拉列表框
图5-39 下拉列表框的HTML代码
5.6.3 Clear()
Clear()方法用于清空input元素的值。例如,百度首页有搜索文本框,如图5-42所示。
图5-42 百度首页搜索文本框
可以先给文本框填入内容,然后使用Clear()将其清除,清除前先使用Thread.Sleep(3000)等待3秒,以便于观察),代码如程序清单5-37和程序清单5-38所示。
程序清单5-37 C#代码
IWebDriver driver = new FirefoxDriver(); INavigation navigation = driver.Navigate(); navigation.GoToUrl(http://www.baidu.com); IWebElement baiduTextBox = driver.FindElement(By.XPath("//input[@id='kw']")); baiduTextBox.SendKeys("找到文本框"); System.Threading.Thread.Sleep(3000); baiduTextBox.Clear(); |
程序清单5-38 Java代码
WebDriver driver = new FirefoxDriver(); Navigation navigation = driver.navigate(); navigation.to("http://www.baidu.com"); WebElement baiduTextBox = driver.findElement(By.id("kw")); baiduTextBox.sendKeys("找到文本框"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } baiduTextBox.clear(); |
5.6.4 Submit()
Submit()方法用于对指定元素所在的form元素进行提交操作。
例如,百度贴吧的登录界面如图5-43所示。
图5-43 百度贴吧登录界面
用Firebug查看它的HTML,如图5-44所示。
图5-44 百度贴吧登录HTML代码
可以看到,它由一个form组成,只要对这个from中的任何元素使用Submit()方法,都会提交这个form。
本文选自《Selenium自动化测试指南》第五章节,本站经人民邮电出版社和作者的授权,近期将进行部分章节的连载,敬请期待!
版权声明:51Testing软件测试网获人民邮电出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。
相关文章:
查找条件对象By—Selenium自动化测试指南(3)