前言

单例模式是一种比较常用的设计模式,目的是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

测试种可能用到的场景 :

在很多时候,有些对象我们希望在整个程序只有一个实例,如线程池、数据库连接池、缓存、日志对象、注册表等。而最近,在我的实际工作中,在编写接口自动化代码时就遇到了下列两种场景:

  1. 自动化所有用到的接口,在发送https请求时,都需要包含一个参数sessionId,该参数可以通过登录webserver的接口获取,我希望这个sessiondId是唯一的,且只需要获取一次。
  2. 由于系统的webserver是支持高可用的,即如果一个active webserver挂了,另一个standby webserver就会立即投入工作,此时web host就需要切换。为了支持高可用,我在发送请求时加入了兼容代码:如果捕获了连接异常(ConnectException)就会去尝试switchWebHost。在多线程并发执行测试用例的时候,我希望这个switchWebHost操作只需要执行一次。而如果将整个代码块加入synchronized同步,会导致不能同时发送https请求,导致并发量降低。
  3. 移动端或者WEB自动化测试的时候,初始化driver(常见的webdriver,appium下的Androiddriver 等)的时候,我们也希望可以实例化,全局方法可以访问,但是只执行一次

借用单例模式或借鉴其思想就可以解决上述问题。

简单单例模式:

public class Singleton{
private static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance(){
if (null==uniqueInstance){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

上面给出的是Singleton 懒汉式(lazy-loading)实现,Singleton类拥有一个静态变量uniqueInstance来记录Singleton的唯一实例,注意它的构造函数是private的,这就注定了只有Sinleton类内才可以使用该构造器。在其他类中我们无法通过new Singleton()的方式类获取一个Singleton的实例,只能通过Singleton.getInstance()的方式获取。并且由于uniqueInstance是一个静态变量,属于Singleton这个类,所以保证了其唯一性。

经典模式有个好处,就是它的对象的实例化只有等到getInstance方法被调用时才会被jvm加载,如果getInstance始终没有被调用,jvm就不会生成该实例。如果该对象的实例化需要消耗较多的资源,这种“延迟实例化”的方式可以减小jvm的开销。

但是,上述的实现方式很容易会想到存在一个严重的缺陷,就是“非线程安全”。不能运用于多线程环境,当多个线程同时调用Singleton.getInstance()来获取实例时,uniqueInstance对象就可能被多次实例化。最简单的方式就是通过synchronized关键字来实现线程同步:

public static synchronized Singleton getInstance(){
if (null==uniqueInstance){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

Double-Checked Locking实现(DCL) :

public class Singleton{
private volatile static Singleton uniqueInstance;
private Singleton(){}
public Singleton getInstance(){
if (null == uniqueInstance){
synchronized (Singleton.class){
if( null == uniqueInstance){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

这种方式也是将实例化延迟到了getInstance方法被调用时,区别于经典单例模式,该方式引入了“双重检查(Double-Checked Locking)”,

在多线程并行执行到同步代码块时,会再次判断uniqueInsance是否为null,有效防止了多次实例化的发生。

并且这种方式并没有对整个getInstance方法加锁,只是在第一次生成Singleton的唯一实例时进行了一次同步,并没有降低程序的并发性。

直接同步整个getInstance()方法产生性能低下的原因是,在判断(instance==null)时,所有线程都必须等待。而(instance==null)并非是常有情况,每次判断都必须等待,会造成阻塞。

因此,有了这种双重检测的实现方法,待检查到实例没创建后(instance=null),再进行同步,然后再检查一次确保实例没创建。

静态内部类方式

静态内部类的方式也可以实现同时满足性能和并发要求的单例模式。

public class Singleton{
private static class Holder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return Holder.INSTANCE;
}
}

解决接口自动化里面的sessionid的获取:

使用“静态内部类”方法创建SessionFactory类:

public class SessionFactory {
private static String sessionId;
private static BaseConfig baseConfig = BaseConfigFactory.getInstance(); private static class SessionidHolder{
private final static SessionFactory INSTANCE = new SessionFactory();
} public static final SessionFactory getInstance(){
return SessionidHolder.INSTANCE;
}
private SessionFactory(){
LoginApi api = new LoginApi();
String username = baseConfig.getLoginUsername();
String password = baseConfig.getLoginPassword();
sessionId= api.login(username, password).getValue("session.id"); /** 获取session.id,在api.login类中 **/
} public String getSessionId() {
return sessionId;
}
}

使用Testng编写测试用例:

public class SessionTest {
@Test(threadPoolSize=10, invocationCount=10)
/** 预期: 10个线程并发执行时,session.id是唯一的,说明sessionFactory是唯一的,只被实例化了一次。**/
public void sessionTest(){
SessionFactory sessionFactory = SessionFactory.getInstance();
System.out.println("Thread id="+ Thread.currentThread().getId()+
", session.id=" + sessionFactory.getSessionId());
}
}

遇到webserver切换时,希望switchWebHost操作只需要执行一次

/**
切换webhost部分的代码进行同步,并且在切换时先通过调用isWebHostChanged()方法判断是否已经被其他线程切换。
防止host多次发生切换。同时,这种方式不会影响到sendHttpsRequest方法的并发。
**/
try {
sendHttpsRequest();
}catch(ConnectException e){
numRquestFail++;
synchronized (BaseApi.class) {
if (isWebHostChanged()){
return;
}
switchWebHost(actualUrl, numRequestFail, e);
}
}

单例模式封装webdriver

同理,该模式可以运用到androiddriver

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver; public class SingletonWebDriver {
    private static WebDriver instance = null;
    public static WebDriver getWebDriver(String browserDriverType,String browserDriverPath) {
        if(instance == null) {
          
            if("webdriver.chrome.driver".equalsIgnoreCase(browserDriverType)) {
                System.setProperty(browserDriverType, browserDriverPath);
                instance = new ChromeDriver();
                
            }else if ("webdriver.ie.driver".equalsIgnoreCase(browserDriverType)) {
                System.setProperty(browserDriverType, browserDriverPath);
                instance = new InternetExplorerDriver();
                
            }else if ("webdriver.firefox.bin".equalsIgnoreCase(browserDriverType)) {
                System.setProperty(browserDriverType, browserDriverPath);
                instance = new FirefoxDriver();
        }
    }   
        return instance;
    }
}

单例可参考: https://www.cnblogs.com/techyc/p/3529983.html

05-28 17:36