自动检测服务器状态方案

1.需求分析:

为了减轻测试人员对被测系统页面的检查,让更多的测试时间花费在业务流程以及功能测试点上,所以需要一个自动检测网站页面错误(死链接,静态资源报错等)的功能,降低测试工作范围从而提高测试效率。

具体需求:

1.定时自动检测服务器状态
2.发生错误邮件通知
3.生成测试报告

2.技术选型:

1.使用Selenium自动化测试框架实现。
优点:能获取访问页面静态资源或JS报错等页面错误,兼容性较强。
缺点:对测试环境依赖性较大,(浏览器版本、Jar版本、浏览器驱动)、脚本不稳定并且执行速度过慢。

2.使用HttpClient工具包实现。
优点:执行速度快,对测试环境要求较低
缺点:兼容性差,各项目访问页面存在加密并且加密方式不同,难以做到大规模使用。

结合现状,选择Selenium作为实现工具。

3.方案设计

1.通过Selenium打开网页并进行简单爬虫获取子页面链接。
2.页面报错时Selenium能获取到相关错误并获取。
3.通过递归获取到指定层级所有子页面树并依次测试
4.当发生错误时邮件通知指定测试人员
5.生成测试报告

项目结构


4.核心代码

1.配置

2.环境配置

3.登录配置

4.测试用例

5.项目(执行引擎)

public class Project {
    private Config config;
    private List<TestCase> testCases;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Config getConfig() {
        return config;
    }

    public void setConfig(Config config) {
        this.config = config;
    }

    public List<TestCase> getTestCases() {
        return testCases;
    }

    public void setTestCases(List<TestCase> testCases) {
        this.testCases = testCases;
    }

    /**
     * 测试执行
     * @return
     * @throws Exception
     */
    public List<Result> run() throws Exception {
        List<Result> results = new ArrayList<Result>();
        if (testCases != null && testCases.size() > 0) {
            //循环项目中的所有测试用例
            for (TestCase testCase : testCases) {
                results.addAll(testRun(testCase));
            }
            int t = 0, s = 0, skip = 0, f = 0;
            for (Result result : results) {
                t++;
                switch (result.getStatus()) {
                case failure:
                    f++;
                    break;
                case skip:
                    skip++;
                    break;
                case success:
                    s++;
                    break;
                default:
                    break;
                }
            }
            if (t > s) {//如果该项目测试结果总数>成功的数量则发送邮件
                try {
                    new MailSender(config.getReceivers(), "米多测试报告", "自动检测服务器状态");
                } catch (Exception e) {
                }
            }
        }
        return results;
    }

    /**
     * 测试用例执行并返回该测试 用例所有的测试结果
     * 
     * @param testCase
     * @return
     */
    public List<Result> testRun(TestCase testCase) {
        WebDriver driver = null;
        List<Result> results = new ArrayList<>();
        String[] browsers = config.getBrowsers();
        String[] environments = testCase.getEnvironments();
        String login = testCase.getLogin();
        String title = testCase.getTitle();
        // 循环浏览器设置
        for (String browser : browsers) {
            driver = BrowserUtil.init(browser);
            // 循环测试用例指定的测试环境
            for (String environment : environments) {
                EnvironmentConfig environmentConfig = config.getEnvironment(environment);
                Result result = new Result();
                result.setProjectName(this.name);
                result.setTestCaseTitle(title);
                result.setBrowser(browser);
                result.setEnvironment(environment);
                if (environmentConfig == null) {// 如果测试用例中指定的环境配置找不到则该用例对该环境的测试结果为failure
                    result.setStatus(Status.failure);
                    result.setImg(BrowserUtil.screenshotByBase64(driver));
                    result.setErrors(Arrays.asList(new String[] { "环境配置错误,找不到:" + environment + "环境" }));
                    results.add(result);
                    continue;
                } else {
                    if (login != null && login.trim().length() > 0) {// 判断测试用例是否需要登录
                        if (!environmentConfig.login(driver, login)) {// 判断登录是否报错,如果报错了,则截图并将该用例对该环境的状态为跳过
                            result.setStatus(Status.skip);
                            result.setErrors(Arrays.asList(new String[] { "登陆失败" }));
                            result.setImg(BrowserUtil.screenshotByBase64(driver));
                            results.add(result);
                            continue;
                        }
                    }
                    String url = environmentConfig.getHost() + testCase.getAddress();
                    int level = testCase.getLevel();
                    int interval = testCase.getInterval();
                    Set<String> requested = new HashSet<>();
                    //执行测试操作
                    Map<Result, Object> map = run(requested, driver, url, level, interval, browser, environment, title);
                    results.addAll(getResults(map));
                }
            }
            driver.quit();
        }
        return results;
    }

    /**
     * 单用例执行
     * 
     * @param requested
     * @param driver
     * @param url
     * @param level
     * @param interval
     * @param browser
     * @param environment
     * @param testCaseTitle
     * @return
     */
    private Map<Result, Object> run(Set<String> requested, WebDriver driver, String url, int level, int interval,
            String browser, String environment, String testCaseTitle) {
        Map<Result, Object> map = new LinkedHashMap<>();
        driver.get(url);
        requested.add(url.toLowerCase());
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Result result = getResult(driver, this.name, browser, environment, url, testCaseTitle);
        if (level > 1) { // 如果传入的level>1则获取此页面所有子页面
            List<String> sub = getSubPage(driver, requested, interval);
            if (sub.size() == 0) {// 如果子页面数量为0。则不操作
                map.put(result, null);
            } else if (sub.size() == 1 && !requested.contains(sub.get(0).toLowerCase())) {// 如果子页面数量为1并且没被访问过则递归测试并将结果保存为Map
                requested.add(sub.get(0).toLowerCase());
                map.put(result,
                        run(requested, driver, sub.get(0), level - 1, interval, browser, environment, testCaseTitle));
            } else {// 如果子页面数量>1则递归测试所有子页面并且将测试结果保存为List
                ArrayList<Object> list = new ArrayList<>();
                for (String string : sub) {
                    if (!requested.contains(string.toLowerCase())) {
                        requested.add(string.toLowerCase());
                        list.add(run(requested, driver, string, level - 1, interval, browser, environment,
                                testCaseTitle));
                    }
                }
                map.put(result, list);
            }
        } else {
            map.put(result, null);
        }
        return map;
    }

    /**
     * 获取当前页面所有子页面
     * 
     * @param driver
     * @param requested
     * @param interval
     * @return
     */
    private List<String> getSubPage(WebDriver driver, Set<String> requested, int interval) {
        List<String> subPage = new ArrayList<>();
        // 获取所有<a>标签的所有href属性值。
        List<WebElement> elements = driver.findElements(By.tagName("a"));
        for (WebElement webElement : elements) {
            String url = webElement.getAttribute("href");
            // 排除不存在href并且href为JS的链接
            if (url != null && url.contains("/")) {
                // 如果当前链接是相对链接,则获取当前链接的Host进行拼接
                if (url.startsWith("/")) {
                    String nowUrl = driver.getCurrentUrl();
                    url = nowUrl.substring(0, nowUrl.indexOf("/", nowUrl.indexOf("//") + 2)) + url;
                }
                // 为防止误操作(访问了删除类型接口),所以将链接参数去除
                if (url.contains("?")) {
                    url = url.substring(0, url.indexOf("?"));
                }
                // 经过处理后的链接如果不存在已经访问过的链接中,则将此链接加入到子页面列表中
                if (!requested.contains(url)) {
                    subPage.add(url);
                }
            }
        }
        return subPage;
    }

    /**
     * 页面测试结果
     * 
     * @param driver
     * @param projectName
     * @param browser
     * @param environment
     * @param requestUrl
     * @param testCaseTitle
     * @return
     */
    private Result getResult(WebDriver driver, String projectName, String browser, String environment,
            String requestUrl, String testCaseTitle) {
        List<String> errors = new ArrayList<>();
        // 获取当前页面所有报错信息
        for (String log : driver.manage().logs().getAvailableLogTypes()) {
            for (LogEntry entry : driver.manage().logs().get(log)) {
                if (entry.getLevel() == Level.SEVERE) {
                    errors.add(entry.getMessage());
                }
            }
        }
        Result result = new Result();
        result.setProjectName(projectName);
        result.setBrowser(browser);
        result.setEnvironment(environment);
        result.setRequestUrl(requestUrl);
        result.setTestCaseTitle(testCaseTitle);
        result.setPageUrl(driver.getCurrentUrl());
        // 如果存在报错,则测试结果状态为failure并且截图
        if (errors.size() > 0) {
            result.setStatus(Status.failure);
            result.setErrors(errors);
            result.setImg(BrowserUtil.screenshotByBase64(driver));
        } else {
            result.setStatus(Status.success);
            errors.add(requestUrl + " - success");
            result.setErrors(errors);
        }
        return result;
    }

    /**
     * 递归获取所有测试结果
     * 
     * @param object
     * @return
     */
    private List<Result> getResults(Object object) {
        List<Result> results = new ArrayList<>();
        if (object instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) object;
            for (Object result : map.keySet()) {
                results.add((Result) result);
                results.addAll(getResults(map.get(result)));
            }
        } else if (object instanceof List) {
            for (Object result : (List<?>) object) {
                results.addAll(getResults(result));
            }
        } else if (object instanceof Result) {
            results.add((Result) object);
        }
        return results;
    }
}

6.主方法
  1. XML数据源

buid.xml

<?xml version="1.0" encoding="UTF-8"?>
<projects>
    <project path='//projects//lipinshichangConfig.xml' />
    <project path='//projects//BgConfig.xml' />
</projects>

project.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name='bg'>
    <config>
        <browser value='chrome' />
        <e-mail>
            <receiver name='' value='546724963@qq.com' />
        </e-mail>
        <environments>
            <environment name='138' host='**********保密***********'>
                <logins>
                    <login name='account' url='**********保密***********' check=''>
                        <operation locator='id.uName' type='input' value='userName' />
                        <operation locator='id.pwd' type='input' value='password' />
                        <operation locator='xpath.//input[@type="button"]' type='click' />
                    </login>
                </logins>
            </environment>
        </environments>
    </config>
    <testPlan>
        <testcase title='Bg管理后台' address='**********保密***********' level='1'
            login='account' environments='138' interval='500' />
    </testPlan>
</project>
  1. 测试报告展示

测试报告支持检索,分页,以及查看详情
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容