Selenium 学习笔记 - 最佳实践

/ 技术文章默认分类 / 0 条评论 / 530浏览

这两天和同事写Selenium Case的时候,查了一下写Selenium的最佳实践,发现好像有了一点更新,有好几点是我以前没有见过的,今天有空就把他们好好整理一下。 其实网上有许许多多的Selenium最佳实践,其实都大同小异,我们就看看Selenium自己官方的Best Practise 这是一篇学习笔记,写给我自己的,可能只有要点,别人看可能会看的比较模糊,以后有空再补吧(以后有空就是永远不会在不了,嗯)。 不管三七二十一先把所有的条目都列出来,先看一下:

1. PAGE OBJECT MODELS
2. DOMAIN SPECIFIC LANGUAGE
3. GENERATING APPLICATION STATE
4. MOCK EXTERNAL SERVICES
5. IMPROVED REPORTING
6. AVOID SHARING STATE
7. TEST INDEPENDENCY
8. CONSIDER USING A FLUENT API
9. FRESH BROWSER PER TEST

其中 5,6,7,9,都是比较传统的说法,大家也应该比较清楚,自己写的case应该已经符合了这几条,可能这篇笔记就不会太多的描述(毕竟是写给我自己的),其实主要是对第1点比较感兴趣,其他的也顺便稍微写一点吧,以后找起来也方便。

PAGE OBJECT MODELS

页面对象模型: 说白了就是将页面元素抽象成一个对象,从而将页面元素与测试逻辑解耦出来。当页面元素发上改变可以不需要触碰测试逻辑的代码,只需要修改页面元素相关代码就行了。 其实我们自己写的测试套件里面已经有了这种思想了,但是就是实现的方式比较low。现在我们的做法是将所有的页面元素的id或者xpath都抽出来,作为常量,根据所在页面放在不同的类里面。 这样的做法能够将页面元素的已修改剥离出来,但不能把对页面元素的操作给抽象出来。

Selenium官方文档上的推荐做法是将每个页面(注意是页面而不是页面元素)封装成一个对象,这个页面对象里包含了这个页面所可能需要用到的操作。 比如,我要完成一个登录页面的测试案例,涉及到的页面可能会有,登录页面和登陆之后的主页面,两个页面,也就是会用到两个页面模型(Page Model)

/** * Page Object encapsulates the Sign-in page. */public class SignInPage {

        private Selenium selenium;

        public SignInPage(Selenium selenium) {
                this.selenium = selenium;
                if(!selenium.getTitle().equals("Sign in page")) {
                        throw new IllegalStateException("This is not sign in page, current page is: "
                                        +selenium.getLocation());
                }
        }

        /**
        * Login as valid user
        *
        * @param userName
        * @param password
        * @return HomePage object
        */
        public HomePage loginValidUser(String userName, String password) {
                selenium.type("usernamefield", userName);
                selenium.type("passwordfield", password);
                selenium.click("sign-in");
                selenium.waitForPageToLoad("waitPeriod");

                return new HomePage(selenium);
        }}


/** * Page Object encapsulates the Home Page */public class HomePage {

        private Selenium selenium;

        public HomePage(Selenium selenium) {
                if (!selenium.getTitle().equals("Home Page of logged in user")) {
                        throw new IllegalStateException("This is not Home Page of logged in user, current page" +
                                        "is: " +selenium.getLocation());
                }
        }

        public HomePage manageProfile() {
                // Page encapsulation to manage profile functionality
                return new HomePage(selenium);
        }

        /*More methods offering the services represented by Home Page
       of Logged User. These methods in turn might return more Page Objects
       for example click on Compose mail button could return ComposeMail class object*/

}

然后测试案例就能够写成这样

/*** * Tests login feature */public class TestLogin {

        public void testLogin() {
                SignInPage signInPage = new SignInPage(selenium);
                HomePage homePage = signInPage.loginValidUser("userName", "password");
                Assert.assertTrue(selenium.isElementPresent("compose button"),
                                "Login was unsuccessful");
        }}

以上案例代码均来自http://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern 创建页面对象模型的时候有几点要求:

1. 每个页面对象的行为方法都要有返回值(返回页面对象或者布尔值都是比较常见的做法),这样我们才能做其他测试逻辑的判断或操作
2. 页面对象不做任何断言,断言全部交给测试逻辑
3. 唯一需要在页面对象中验证的是,当前是否在指定页面中(判断title或者标志性元素)
4. 页面对象并不一定是整个页面,也可以是页面的某一部分,某一frame或者某一component
5. (页面对象工厂类?)

在我看来这样做的好处就是能够提高页面内方法的重用率,当然这也不是唯一能够提高测试代码重用率的方法; 我们现有的测试脚本,将所有测试案例按照页面组织,也就是同一个页面的case都放在一个类当中,也能达到这样的效果。当然前提是案例还不多的情况下。 说实话这样写法的其他好处我暂时还看不出来,就是更面向对象一些。

DOMAIN SPECIFIC LANGUAGE

官方文档上讲的比较复杂,有兴趣的同学可以自己去看一下,这里我就说一下我自己现在的理解,如果有不对的,以后会改。 我的感觉,这条最佳实践就是叫你写Selenium case的时候说人话,用面向业务、面向用户的思路去设计case,去描述case,去命名、组织case 这就要谈到Selenium这个的工具的特点了,我用下来的感觉,Selenium这个工具他最擅长的还不是纯粹的功能性测试。他原生的数据驱动也不太理想,也不能适合用来做并发,我感觉Selenium最擅长的地方应该是用来做用户验收测试(UAT)和冒烟测试(SMOKE TEST),保证业务流程的正常流转,页面的正常跳转,软件的用法是否正确,而非界面元素对不对好看不好看。当然也比较适合用来做功能回归(但不是侧边界值那种)。 座椅设计case的时候,和单纯的功能测试案例还不太一样,应该更注重小块的面向使用者的用例的验证。所以说Selenium用来测试网站或者网页是否满足需求文档的要求,是最适合不过的。

GENERATING APPLICATION STATE

好像是说不要用来做其他测试的前置准备工作。大概是这个意思,自己看一下

MOCK EXTERNAL SERVICES

主要是为了减少依赖。 我认为并不是所所有外部的服务,都要mock。比如你自己的数据库,自己的环境都要一整套,因为他们本身就是测试的一部分。 需要mock的服务是指那些第三方接口。比如测试案例中需要包含支付接口的测试,我们不需要去真的连支付宝或微信支付做测试,只需要将这些服务接口mock掉就行了。 一方面保证待测软件的相对独立,另一方面避免了因第三方接口不稳定导致的测试失败。

IMPROVED REPORTING

官方自己也承认自己的report烂了,其实就是根本没有做reporting的接口,只能生成junit的xml。要自己去找第三方的包。现在感觉用gradle自带的报告也够了,暂时不考虑吧。

AVOID SHARING STATE

避免状态的共享,这一点在Selenium的文档上也没有多说,但就我个人的理解是为了避免其他case对某一共享对象的状态改、或者改变失败,导致其他case获取不到预想中的状态,从而导致其他case的失败。其实说白了,还是要保持case之间的独立性,无论从数据还是行为上都要。 其实这里有几条,感觉上都是差不多的,比如这一条和下一条的保持测试案例之间的独立性基本上都是同一个意思。 通过最后一条的在测试开始之前刷新页面,基本上可以达成目的。

TEST INDEPENDENCY

保持测试案例之间的独立性很好理解啦,千万不要将上一个测试案例的输出作为下一个测试案例的输入;或者在一个case里面创建一个内容,然后在其他case里面使用或者对他断言。显而易见,测试之间的关联会使得测试的失败影响其他测试的正常运行;而从报告里也看不出出错的根本原因;并且如果测试人员想要从庞大的测试案例中挑选一部分作为SMKOE TEST时也不方便;特别是当测试案例数目的上去之后,如果你想将测试并行执行时,非独立的测试会限制你的工作。总之,就是坏处多多。

CONSIDER USING A FLUENT API

好像是个Selenium官方封装的另一个库,有更加方便的API,能够写出更加优雅的脚本。 github上有readme和源码

FRESH BROWSER PER TEST

没啥说的,测试开始前刷新,应该是标准做法吧,其实写case的时候更偷懒的做法是在每个case最后退出浏览器,然后在下一个case开始之前在打开刘浏览器在重新登录。 但这样做,写擦色、是方便了,等case量上去之后,就会发现会在登录登出上花费大量时间,如果既要追求运行虚度,又要追求开发速度的话,最好还是花点时间把setup()和teardown()好好整整吧。

另:原来Selenium能够支持Verify,像这样

try {
assertTrue(driver.findElement(By.cssSelector("BODY")).getText().matches("^[\\s\\S]*verify text is present[\\s\\S]*$"));
} catch (Error e) {
verificationErrors.append(e.toString());
}

assert和verify的主要区别就是,发现错误以后是标失败后立即退出,还是继续往下跑。 虽然我感觉verify好像没设么用,错了就应该退出,上一步错了,下一步怎么继续呀?继续往下跑不是浪费时间吗? 但万一有用呢,先记着