首页 > Space导入, 技术 > Selenium, Eclipse, Junit折腾记

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:  }
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word


Warning: fsockopen() has been disabled for security reasons in /home/onlymars/public_html/wp/wp-includes/class-snoopy.php on line 1142