如何检验自身的技术水平?参加一场比赛就知道了

发表于:2019-12-05 10:17

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:合肥人真帅    来源:51Testing软件测试网原创

   前言
  我在CSDN上看到一篇名为《程序员为什么非要参加一场编程竞赛》的文章,这是一篇译文,原著是国外的作者。这让我想起前段时间我参加的一场软件测试比赛,最终的感想可以用8个字概括,人无远虑必有近忧。在这里我想和大家分享我的参赛过程和赛后总结。
  一、赛事简介
  比赛给人的第一印象就是激情,热血,因为比赛一定能分出高低。编程竞赛在国内出现的时间比较早,现在已经上规模,成体系了,加之媒体的报道,大家也比较熟悉。但是软件测试比赛或许大家还没听说过。实际上成体系的软件测试比赛,例如CST全国大学生软件测试大赛,在2016年就已经首次举办,今年已经是第四届了。赛事项目分有:单元测试性能测试安全测试web自动化测试APP自动化测试,嵌入式测试等,可谓是相当全面。赛程从初赛、省赛、决赛到国际赛,中间也会穿插一些其他比赛,例如欧洲邀请赛,以及一些工业性质的比赛。
  我所参加是南京7月份软博会期间,举办的一场工业APP软件测试比赛。
  二、赛程赛制
  比赛分为个人赛和团队赛,团队赛每队3人。由于个人赛和团队赛是同时进行的,所以无法同时参加2个项目。比赛分为3个阶段,第一阶段是报名和练习,选手报名后,可以在官方网站阅读比赛规则和注意事项,还会开放一些练习题,帮助赛前热身;第二阶段是网络预选赛,比赛时间3小时。个人赛前60名,团队赛前20名,可以进入决赛;第三阶段是决赛,比赛时间3小时,在南京国际博览中心进行线下比赛。总奖金12万RMB。
  三、评分标准
  预选赛评分标准:
  1. (60%)Selenium脚本的测试需求覆盖率以及成功回放率;
  2. (40%)Jmeter性能测试脚本的场景设置和参数设定准确性以及成功回放率;
  3. 总分=评分1+评分2,比赛有多道题则累加计算;
  4. 总分相同的选手按测试用例集运行时间二次排名,运行时间短优先。
  总决赛评分标准:
  1. (40%)Selenium脚本的测试需求覆盖率以及成功回放率;
  2. (20%)Jmeter性能测试脚本的场景设置和参数设定准确性以及成功回放率;
  3. (30%)协作式众包测试,Bug报告编写(0.6)+Bug报告审核(0.4);
  4. (10%)工业APP标准符合性评价
  5. 总分=评分1+评分2+评分3+评分4,比赛有多道题则累加计算;、
  6. 总分相同的选手按测试用例集运行时间二次排名,运行时间短优先。
  Selenium脚本和jmeter脚本为自动化评分;其余由专家人工评分。
  四、赛前准备和初赛
  报名后,我在阅读注意事项时,看到其中一条“所有的IDE都需要从官网下载,并按照官方要求进行配置,否则无法得分”。于是我在赛事官网上下载Eclipse,Jmeter,jdk等。浏览器、第三方jar包、驱动器、笔记本电脑需要选手自备。由于平时工作较忙,练习基本没做过,只是体验了一下比赛流程。初赛时间是下午2点到5点,持续3个小时时间,看似很长,但是分析完需求文档后,我觉得时间还是挺赶的。比赛过程中,是允许多次提交代码的。每次提交完都会立刻显示当前提交代码所获得的评分。最终的得分并不是以多次提交中的最高分为准,而是以最后一次提交代码所获得的分数为准。这就需要我们自己做好版本控制,若后期提交的代码分数不高,可以在比赛结束前进行回滚,以获取最高分。由于参赛人数较多,所以jmeter在调试脚本时,不允许使用大并发数,否则IP会被服务器的安全协议禁用,导致调试失败且无法提交代码。可以用1个线程进行调试,然后提交的时候改成多线程。
  五、决赛
  决赛是线下比赛,比赛地点在南京国际博览中心。比赛时间早上9点到12点,可以在8点后进入场地,调试比赛机器。
  当天处于软博会展会期间,参展人员很多,7月份天气又很热。到达现场已经是汗流浃背,心情比较浮躁。我是个喜欢比赛的人,中学参加过数学竞赛,大学参加过电子竞技,比赛经验也算比较丰富了。所以看到现场这么多人,我并没有紧张,但却也不平静。而是兴奋,因为这是我第一次参加和工作有关的比赛。找到我的位子后,我调整了一下心态,便开始调试机器。
  9点准时开始比赛,发放需求文档。selenium自动化测试需要完成13个页面的操作和校验;jmeter需要完成3个接口的性能测试;手工测试则针对整个被测软件,大小页面加起来约上百个;最后还要对被测软件进行工业标准符合性评价。
  看完文档我的第一感觉就是时间不够。于是我快速开始编写selenium脚本,大约2分钟时间,登录脚本写完,调试通过,但是代码提交后,显示运行得分为0,我认为是系统显示的问题,可能要等比赛结束才会显示分数。于是我继续编写脚本,我发现页面的iframe太多,来回切换比较费时,且被测系统也是一个陌生的环境,我便想着先进行手工测试,顺便熟悉下操作,然后再进行脚本编写。有了这个念头以后,我又重新看了一遍功能测试的需求文档,再次阅读后,我发现了一条重要的线索“当某位选手发布bug后,其余选手提交bug与该选手类似,则由专家判定,若确实相同,优先发布bug的选手得分,其余选手不得分”,需求中的原文我不记得了,但是理解后就是这个意思。我的天哪,这意味着手工测试的30分是抢分赛,如果我拿了10分,其余59人加起来只能拿到20分。此时已经9点50,我觉得这30分可能已经所剩无几了,我立刻提交当前selenium代码,开始转战手工测试,让我意外的是,仅10分钟的时候,我发布了15个bug,竟然没有一个是重复的,说明大部分选手并没有注意到这条规则的真正含义。我继续进行手工测试,渐渐的,开始出现重复bug,直到11点,几乎提交的bug都是重复bug。我知道大部分的人都在进行手工测试,且未被发现的bug也越来越少,也就是说手工测试这30分中,剩余的分数越来越少,相对于时间的紧迫性,我觉得剩余的bug性价比已经不高了。我便开始了jmeter脚本的编写,大约20分钟,没有任何阻碍,完成了性能脚本编写,提交后,虽然得分不高,但我不打算优化,毕竟jmeter的占比只有20%。
  我将剩余的时间放在了selenium脚本的编写。提交后,得分依然是0,此时引起了我的注意,一开始我认为可能比赛后才会显示分数,但jmeter是立刻就给出了分数,我断定是selenium的评分机制出了bug。我开始检查脚本,我发现在设置启动主站点时,裁判给我们的是url中有一个单词是大写。
  而在浏览器中,输入url,会被重定向成小写,
  然后我修改了selenium脚本中的url,提交后,得分出来了。最后简单的写了一下工业符合性评价。手工测试我抢到了不少分,其他选手如果没有发现url这个问题,可能selenium这一项就是0分了,而我是有得分的。我觉得占据着两大优势,可以拿到一个不错的名次。距离结束还有20分钟的时候,有一位选手提出了疑问,为什么selenium提交是0分。裁判询问后,发现现场很多选手都是0分,于是组委会专家开始寻找问题,在距离比赛结束还有10分钟的时候,问题找到了。大家修改url后开始疯狂的提交代码,以至于服务器承受不住压力,出现了断开连接、长时间未响应等异常情况,裁判最后也是允许未提交成功的选手,可以由组委会使用U盘将代码从选手的机器拷贝出来,复制到服务器上。最终我获得的是二等奖。
  六、赛后总结
  这次比赛自动化编码和手工测试占据了大部分分值。关于比赛中selenium的编码,我从3个地方做一个总结。(以下是我比赛中编写的一部分代码)
   1.public static void test(WebDriver driver) {
  2.    try {
  3.    driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
  4.    driver.get("http://app.eedi.org.cn/SEEMMS.Server/public/index.php/index/login/login.html");
  5.    driver.manage().window().maximize();
  6.    driver.findElement(By.name("ACCOUNT")).sendKeys("test32");
  7.    driver.findElement(By.name("PASSWORD")).sendKeys("752167");
  8.    driver.findElement(By.name("ylogin")).click();
  9.    Thread.sleep(1500);
  10.    driver.findElement(By.id("a-Conditionmonitor")).click();
  11.    driver.findElement(By.xpath("//*[@name='Conditionmonitor']/li[2]/a")).click();
  12.    driver.findElement(By.xpath("//*[@id='add-panel']/div/a")).click();
  13.    driver.findElement(By.name("SHAFT_SPEED")).sendKeys("10");
  14.    driver.findElement(By.name("SHAFT_TQRQUE")).sendKeys("20");
  15.    driver.findElement(By.name("SHAFT_POWER")).sendKeys("30");
  16.    driver.findElement(By.name("THRUST_TQRQUE")).sendKeys("40");
  17.    driver.findElement(By.name("THRUST_SPEED")).sendKeys("50");
  18.    driver.findElement(By.name("THRUST_POWER")).sendKeys("60");
  19.    driver.findElement(By.name("THRUST_THRUST")).sendKeys("70");
  20.    Thread.sleep(1000);
  21.    driver.findElement(By.xpath("//input[@value='确定']")).click();
  22.    driver.findElement(By.id("alert")).click();
  23.    Thread.sleep(1000);
  24.    driver.findElement(By.xpath("/html/body/div[2]/div/div[2]/ul/li[2]/a")).click();
  (1)定位问题。传统的定位方式都是选择id和name,在没有id和name属性时,选择使用其他属性,例如link,css等,最后选用xpath。但随着技术发展,这些传统的规则也有些不适用了。因为id,name可能会重名,link可能被封装等等元素。
  这段代码中有一部分我使用了xpath的方式去定位,但实际上这个元素是有id属性的。为什么我不用id去定位?因为现在很多前端页面,都是通过模板生成的或是程序员只写一种通用方法,每次重复生成页面元素。这就会产生下面这个现象:①id前缀相同,后缀是每次点击展开按钮时动态生成。这样的id每次都不一样。
  ②多个元素使用相同id
  此时使用id就无法就精准定位,所以传统的定位规则已经不适合现在的前端了。不论什么规则,我们只要理解其思想就行了。我认为满足3个条件就是好的定位方法。一是当前环境可以精准定位,运行成功率100%;二是具有健壮性,未来前端发生改变,可能会增加页面元素等,原方法依然可以定位到;三是易阅读,方便其他人员维护代码。
  例如上面的2个例子中,对于第一个树形结构,根据边界值测试的设计思想,我们需要定位首个或最后一个树节点,不能使用id去定位,那么可以写成这样:
   1.List<WebElement> tree = driver.findElement(
  2.                    By.xpath("//a[@title='安徽省']/following-sibling::ul"))
  3.                    .findElements(By.tagName("li"));
  先通过title找到安徽省,然后通过轴关键字找到其后的兄弟节点ul,最后将这个节点下所有的li元素全部加入到list中,那么以后不同权限的账号登录系统,无论其管理城市数量的多少,我们都可以通过
   1.tree.size();  //获取当前账户权限管理的城市数量
  2.tree.get(0);  //定位首个城市
  3.tree.get(tree.size()-1); //定位最后一个城市
  这2条语句定位到树结构的边界,对边界节点进行其他操作,完成边界测试。当然除了这3点,还有其他衡量的方法,例如增加冗余代码
  这是百度首页,点击“百度一下”按钮的操作代码,可以看到selenium IDE提供了5种定位方式,日后若这个按钮发生了属性变化,则运行代码时,他会先从第一种定位方式进行页面元素寻找,若5种方式都找不到,才会报NoSuchElementException
  (2)等待时间,比赛中有提到程序运行同分的,以运行时间二次排名。那么我的代码中加入了以下语句
 1.Thread.sleep(1500);
  这对于比赛显然是不利的,因为增加了运行时间。那为什么还要加等待时间呢?因为java执行速度过快,前端在执行页面跳转的过程中,java代码并不会停止,可能会产生页面还没有完全加载,java代码就已经执行下一句的,会导致无法找到页面元素报错。有时候我们看到NoSuchElementException报错时,并不是定位方法出了问题,而是页面加载速度跟不上java执行的速度。例如:
   1.driver.findElement(By.name("ylogin")).click();    //点击登录按钮
  2.Thread.sleep(1500);
  3.driver.findElement(By.id("a-Conditionmonitor")).click();   //点击监视器按钮
  系统登录后,会跳转到首页,首页右上角有一个监视器按钮。如果不加入第二行代码,就会报错。因为点击登录,页面跳转大约需要0.3秒的时间,但是此时java已经开始执行“点击监视器按钮”这个操作了,此时页面是空白的,所以无法找到元素,报错。加入等待1.5秒,页面完全加载成功,此时在执行“点击监视器按钮”操作,就可以正常运行了。有人会问既然要加等待时间,为什么选择这种固定的方式?动态等待,可以提高程序的执行速度呀?其实我是出于2点原因这么写的。第一是因为动态等待需要设置参照物,对于第一次测试这么庞大的系统,比赛时间又这么短,我不想把比赛时间浪费在脚本的性能调优上,能跑起来就行。第二是因为平时我做自动化测试时,都是以场景为设计对象,真实用户在操作时,每一步之间都会有停顿,我是尽可能的让机器去模仿用户的操作习惯,才这么写。用过LoadRunner的人都明白什么是“思考时间”,其实我这样写,就相当于是“思考时间”。当然,如果是追求全功能的回归测试,那么提高效率,还是应该使用动态等待。
  (3)测试类,功能类,配置类应该分开去写。而比赛中,官方提供的模板是
  Example.java内容如下:
   1.import java.util.concurrent.TimeUnit;
  2.
  3.public class Example {
  4.
  5.    // Mooctest Selenium Example
  6.
  7.    // <!> Check if selenium-standalone.jar is added to build path.
  8.
  9.    public static void test() {
  10.
  11.    }
  12.
  13.    public static void main(String[] args) {
  14.        // Run main function to test your script.
  15.
  16.    }
  17.}
  主类用于运行,测试类用于功能编写。也许是方便比赛评分吧。但实际中我认为测试类,功能类,配置类需要分开。这样易读且方便维护。
  ①这是配置类。定义了打开和关闭浏览器。打开浏览器需要的驱动、url、超时时间等。因为每次测试,都需要这些固定的步骤,所以单独写成一个配置类,如果测试地址改了,只需要在这里修改一次url就行了。如果功能、配置、运行写在一个文件中,那么一旦发生修改,你就要去所有的文件中修改。
   1.public class OpenAndCloseBrowse {
  2.String baseUrl = "http://app.eedi.org.cn/SEEMMS.Server/public/index.php/index/login/login.html";
  3.
  4.    public WebDriver openMethod(WebDriver driver) {
  5.        System.setProperty("webdriver.chrome.driver",
  6.                "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe");
  7.        driver = new ChromeDriver();
  8.        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
  9.        driver.get(baseUrl);
  10.        driver.manage().window().maximize();
  11.        return driver;
  12.    }
  13.
  14.    public void closeMethod(WebDriver driver) {
  15.        driver.quit();
  16.    }
  17.}
  ②这是运行类(测试类)。定义了要进行哪些测试,与测试用例对接。第11行代码中的username和password,可以写成从测试用例中动态获取。
   1.public class Login_TestCase {
  2.    public WebDriver driver;
  3.
  4.    @Test(description = "正常登录")
  5.    public void normalLogin() {
  6.        OpenAndCloseBrowse b = new OpenAndCloseBrowse();
  7.        driver = b.openMethod(driver);
  8.        try {
  9.            Login l = new Login();
  10.            Thread.sleep(2000);
  11.            l.login(driver, "username", "password");
  12.        } catch (Exception e) {
  13.            e.printStackTrace();
  14.        } finally {
  15.            b.closeMethod(driver);
  16.        }
  17.    }
  18.
  19.    @Test(description = "账号密码正确,无权限")
  20.    @Test(description = "输入错误密码登录")
  21.    @Test(description = "输入不存在的用户名登录")
  22.    ......
  23.}
  ③这是功能类。写了一个简短的登录功能。
   1.public class Login {
  2.
  3.public void login(WebDriver driver, String username, String password) {
  4.    SQLMethod sql = new SQLMethod();
  5.    String mobile = sql.getString("select mobile from user where mobile = '" + username + "'");
  6.    int role = sql.getInt("select role from user where and mobile = '" + username + "'");
  7.    String passwd = sql.getString("select password from user where mobile = '" + username + "'");
  8.    String sign = "";
  9.    // 没有输入约定好传no
  10.    try {
  11.        if (!username.equals("no")) {
  12.            sign = "没有找到用户名输入框";
  13.            driver.findElement(By.id("phone")).sendKeys(username);
  14.        }
  15.        if (!password.equals("no")) {
  16.            sign = "没有找到密码输入框";
  17.            driver.findElement(By.id("password")).sendKeys(password);
  18.        }
  19.        sign = "没有找到登录按钮";
  20.    driver.findElement(By.xpath("//*[contains(text(),'登录')]/parent::button")).click();
  21.        Thread.sleep(500);
  22.        if (username.equals(mobile)) {
  23.            if (password.equals(passwd)) {
  24.                if (role == 1) {
  25.                    // 正常登录
  26.                    Thread.sleep(2000);
  27.                    assertTrue(driver.getCurrentUrl().contains("HomePage"), "登录成功,
  28.                      跳转页面错误");
  29.                } else {
  30.                    // 帐号密码正确,权限不足
  31.                    sign = "用户无权限,没有给出提示";
  32.                    assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  33.                                .replaceAll(" ", ""), "用户无权限", "用户无权限,提示信息不正确");
  34.                }
  35.            } else if (password.equals("no")) {
  36.                // 正确帐号,不输入密码
  37.                sign = "不输入密码,没有给出提示";
  38.                assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  39.                   .replaceAll(" ", ""), "请输入正确的手机号/密码!", "不输入密码,提示信息不正确");
  40.            } else {
  41.                // 正确帐号,错误密码
  42.                sign = "输入错误的密码,没有给出提示";
  43.                assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  44.                    .replaceAll(" ", ""), "密码错误", "输入错误的密码,提示信息不正确");
  45.            }
  46.        } else {
  47.            if (username.equals("no")) {
  48.                if (password.equals("no")) {
  49.                    // 不输入帐号和密码
  50.                    sign = "不输入帐号和密码,没有给出提示";
  51.                    assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  52.                       .replaceAll(" ", ""), "请输入正确的手机号/密码!");
  53.                } else {
  54.                    // 不输入帐号,正确密码
  55.                    sign = "不输入帐号,没有给出提示";
  56.                    assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  57.                            .replaceAll(" ", ""), "请输入正确的手机号/密码!");
  58.                }
  59.            } else if (this.isInteger(username)) {
  60.                // 不存在的帐号,正确密码
  61.                sign = "输入不存在的帐号,没有给出提示";
  62.                assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  63.                        .replaceAll(" ", ""), "账户不存在", "输入不存在的帐号,提示信息不正确");
  64.            } else {
  65.                // 帐号格式错误
  66.                sign = "输入错误格式的帐号,没有给出提示";
  67.                assertEquals(driver.findElement(By.xpath("//*[@title='msg']")).getText()
  68.                        .replaceAll(" ", ""), "请输入正确的手机号/密码!");
  69.            }
  70.        }
  71.    } catch (NoSuchElementException e) {
  72.        assertEquals(false, sign);
  73.    } catch (Exception e) {
  74.        e.printStackTrace();
  75.    }
  76.}
  77.
  78.    public boolean isInteger(String str) {
  79.        Pattern pattern = Pattern.compile("^[-\\+] [\\d]*$");
  80.        return pattern.matcher(str).matches();
  81.    }
  82.}
  对于设计的自动化测试用例,每一条用例的操作步骤,都可以写成功能类;每一个条用例所需要的测试数据,可以写入到运行类(测试类);每一条用例的前置条件,可以写入到配置类;每一条用例的预期结果,写入到功能类,用断言的方式实现,并将实际结果返回给运行类(测试类),可以拓展一下,将实际结果回填到测试用例中。再集成到jenkins,实现定时自动化测试。我们只要定期维护测试用例和脚本即可。
  赛后我还在难过,如果没有选手质疑,可能大部分人第一项都是0分,或许我可以因此捡漏,拿到特等奖。但细想现实生活中,捡漏犹如彩票中奖,几率小且可遇不可求,想要拿到更高的奖项还是要提高自己。现阶段,软件测试比赛还是以大学生为主,纵观历届比赛,各分项赛对技能的标准和要求也是非常高的,能来参加比赛的学生都不是善茬,将来毕业后,就业,一定会对测试行业产生冲击。例如这次特等奖的选手,就是南京大学毕业的学生,已入职微软公司。若不想别淘汰,就要持续学习,努力奋进。
  生活在和平年代的我们,衣食无忧,在欢声笑语中长大。但这样的环境来之不易。尤其是观看完国庆阅兵后,这种紧迫感更加强烈。当下“持续学习”,“努力奋进”是2个比较流行的词语,也完美的诠释了如何做,才能居安思危。作为一个IT人,持续学习和努力奋进,说的小一点,可以提升自己的竞争力,改善自己的生活;说的大一点是在为祖国的伟大复兴做贡献。举个例子,2001年中美黑客大战,大战结果也是众说纷纭,我不做过多评论。但从测试的角度去分析这件事情,当时我国IT从业人员少,且能力弱。各门户网站无论是性能、还是安全都相对较弱。而今天我国IT行业的发展,无论是AI、5G又或者是物联网,都是世界领先,且质量是非常有保障的,若IT人员可以持续学习和努力奋进,将学习所得运用到实际生产。再次爆发黑客大战时,无论是网站还是产品都可以保持高质量(包括功能,性能,安全等等),可能就像康辉说的那样“不愿打,但也不怕打,必要时不得不打”。无论是为了国家,还是为了自己,都要想的长远一些,正所谓人无远虑,必有近忧。“持续学习,努力奋进”不是一个口号,而是要付出实际行动的,或许这是我们这一代人最好的出路。

      版权声明:本文出自51Testing会员投稿,51Testing软件测试网及相关内容提供者拥有内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像,否则将追究法律责任
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号