前言
单例模式是一种比较常用的设计模式,目的是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
测试种可能用到的场景 :
在很多时候,有些对象我们希望在整个程序只有一个实例,如线程池、数据库连接池、缓存、日志对象、注册表等。而最近,在我的实际工作中,在编写接口自动化代码时就遇到了下列两种场景:
- 自动化所有用到的接口,在发送https请求时,都需要包含一个参数sessionId,该参数可以通过登录webserver的接口获取,我希望这个sessiondId是唯一的,且只需要获取一次。
- 由于系统的webserver是支持高可用的,即如果一个active webserver挂了,另一个standby webserver就会立即投入工作,此时web host就需要切换。为了支持高可用,我在发送请求时加入了兼容代码:如果捕获了连接异常(ConnectException)就会去尝试switchWebHost。在多线程并发执行测试用例的时候,我希望这个switchWebHost操作只需要执行一次。而如果将整个代码块加入synchronized同步,会导致不能同时发送https请求,导致并发量降低。
- 移动端或者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; } }