Selenium, Eclipse, Junit折腾记

    很早以前就想搞自动化Web功能测试,知道了Selenium,看了些文档,但当真正到开发项目的关头,测试总是草草而过,跑完一遍手工的拉倒,回归测试更是无从谈起。前几天终于痛下决心写起使用Selenium的Web自动测试代码。

    先扯扯Selenium(字面上是“硒”的意思)。当初出来的时候结结实实把我震撼了一回。原来搞Web自动化测试基本上走的是GUI的那条老路(当然可能也是我当年孤陋寡闻),而这种GUI自动测试工具往往是功能强大的私有软件(比如WinRunner),另外对Web这种多变的测试元素用起来也是很别扭。Selenium另辟蹊径,从JS入手调用浏览器,同时允许通过跨平台的代码调用。从API就可以看出来这个东西的直观易用:

   1:  selenium.windowMaximize();
   2:  selenium.click("link=信息系统");
   3:  selenium.waitForPageToLoad("30000");
   4:  selenium.click("//a[contains(@href, 'projects.do?method:view&project.id=1')]");
   5:  selenium.waitForPageToLoad("30000");

    另外,Selenium还提供了一个Firefox插件-Selenium IDE,用于录制用户的操作(虽然部分动作无法录制)。录制的动作可以直接导出成HTML/Java/Ruby/C#/PHP等格式的代码,配合提供的SeleneseTestCase,当作JUnit的TestCase使用。

    不过这折腾就折腾在这TestCase上。Selenium的开发提供的SeleneseTestCase是Junit3风格的,放在JUnit4底下跑,JUnit4的Annotation功能就用不起来了(这点经我浏览代码查证)。Selenium要启动浏览器,如果用不上@BeforeClass的话,每次启动都初始化一下Selenium,开个IE或者Firefox,这个测试的效率可吃不消(也有比较麻烦的Workaround,但总觉得不是很好)。而甩开SeleneseTestCase的话,又舍不得那个在测试没有通过的时候自动截屏的功能。于是开始Google,兼看Junit4的源代码。

    最后终于在这里找到了方案。但问题又来了:Eclipse自带的Junit 4.3还没有这个方案需要的类(JUnit4ClassRunner )!自己手动把原来的Library移除换上Junit 4.5,又发现这个类才用了一个版本就Deprecated了。于是换上了新类(BlockJUnit4ClassRunner)。

    接下去的工作就是用上Decorator模式,把原来SeleneseTestCase的代码给移到新的BaseTestCase上。期间还遇上了一些Override的问题。上代码:

SeleniumTestListner类,用于拦截异常的抛出

   1:  public class SeleniumTestListener extends RunListener {
   2:   private SeleneseTestCaseAdapter stca;
   3:      @Override
   4:   public void testFailure(Failure failure) throws Exception{
   5:          Selenium selenium = stca.getSeleniumTestBase().getSelenium();
   6:   if(!stca.isCaptureScreenShotOnFailure()){
   7:   return;
   8:          }
   9:   if (selenium != null) {
  10:              String filename = failure.getDescription().getDisplayName() + ".png";
  11:   try {
  12:                  selenium.captureScreenshot(filename);
  13:                  System.err.println("Saved screenshot " + filename);
  14:              } catch (Exception e) {
  15:                  System.err.println("Couldn't save screenshot " + filename + ": " + e.getMessage());
  16:                  e.printStackTrace();
  17:              }
  18:          }
  19:   
  20:      }
  21:   
  22:   public void setSeleneseTestCaseAdapter(SeleneseTestCaseAdapter stca){
  23:   this.stca = stca;
  24:      }
  25:  }
 
SeleniumTestRunner类:加入SeleniumTestListener监听器,得到Test实例并注入监听器
   1:  public class SeleniumTestRunner extends BlockJUnit4ClassRunner  {
   2:   private SeleniumTestListener stl;
   3:   public SeleniumTestRunner(Class<?> c) throws Exception{
   4:          super(c);
   5:          stl = new SeleniumTestListener();
   6:      }
   7:   
   8:      @Override
   9:   public void run(RunNotifier rn){
  10:          rn.addListener(stl);
  11:          super.run(rn);
  12:      }
  13:   
  14:   /**
  15:       * Copy from BlockJUnit4ClassRunner.methodBlock(FrameworkMethod method)
  16:       * to get tested instance
  17:       * @author Marshall
  18:       */
  19:      @Override
  20:   protected Statement methodBlock(FrameworkMethod method) {
  21:          Object test;
  22:   try {
  23:              test= new ReflectiveCallable() {
  24:                  @Override
  25:   protected Object runReflectiveCall() throws Throwable {
  26:   return createTest();
  27:                  }
  28:              }.run();
  29:          } catch (Throwable e) {
  30:   return new Fail(e);
  31:          }
  32:   
  33:   //Marshall added
  34:          stl.setSeleneseTestCaseAdapter((SeleneseTestCaseAdapter)test);
  35:   
  36:          Statement statement= methodInvoker(method, test);
  37:          statement= possiblyExpectingExceptions(method, test, statement);
  38:          statement= withPotentialTimeout(method, test, statement);
  39:          statement= withBefores(method, test, statement);
  40:          statement= withAfters(method, test, statement);
  41:   return statement;
  42:      }
  43:  }

SeleniumTestCaseAdapter, 打上了@RunWith。所有的TestCase都继承这个Adapter。但这个Adapter并不继承JUnit的TestCase类

   1:  /**
   2:   * Decorator pattern which makes this class have the same capability as the
   3:   * SeleneseTestCase class had provided. Copy a lot of source code from the 
   4:   * decorated class.
   5:   * @author Marshall
   6:   */
   7:  @RunWith(SeleniumTestRunner.class)
   8:  public class SeleneseTestCaseAdapter {
   9:   private static SeleniumTestBase stb = new SeleniumTestBase();
  10:   private boolean isCaptureScreenShotOnFailure = false;
  11:   
  12:   /** Use this object to run all of your selenium tests */
  13:   protected static Selenium selenium;
  14:   
  15:      @BeforeClass
  16:   public static void setUpSelenium() throws Exception{
  17:          stb.setUp("http://127.0.0.1:8080/", "*iexplore");
  18:          selenium = stb.getSelenium();
  19:      }
  20:   
  21:      @AfterClass
  22:   public static void tearDownSelenium() throws Exception{
  23:          stb.tearDown();
  24:      }
  25:   
  26:     ......
  27:  }

样例测试类:

   1:  public class TestRiskRepo extends SeleneseTestCaseAdapter {
   2:   public TestRiskRepo(){
   3:          setCaptureScreenShotOnFailure(true);
   4:      }
   5:      @Before
   6:   public void set() throws Exception {
   7:          selenium.open("/apis/login.do");
   8:          selenium.type("j_username", "marshall");
   9:          selenium.type("j_password", "xxxx");
  10:          selenium.click("//input[@value='登录']");
  11:          selenium.waitForPageToLoad("30000");
  12:      }
  13:   
  14:      @Test
  15:   public void repo() throws Exception {
  16:          selenium.windowMaximize();
  17:          selenium.click("link=信息系统");
  18:          selenium.waitForPageToLoad("30000");
  19:          selenium.click("//a[contains(@href, 'projects.do?method:view&project.id=1')]");
  20:          selenium.waitForPageToLoad("30000");
  21:          verifyTrue(selenium.isTextPresent("xxx"));
  22:          selenium.click("link=组织风险库");
  23:          selenium.waitForPageToLoad("30000");
  24:          verifyEquals("组织风险库 | APIS", selenium.getTitle());
  25:          verifyTrue(selenium.isTextPresent("可能性"));
  26:          verifyFalse(selenium.isVisible("//button[contains(text(), '搜索')]"));
  27:          selenium.click("css=.x-tool");
  28:          verifyTrue(selenium.isVisible("//button[contains(text(), '搜索')]"));
  29:      }
  30:   
  31:  }

Leave a Reply

Your email address will not be published.