1、简介
对编码完成的功能,进行
测试,是每个程序员最熟悉不过的事了,每完成一部分功能,都需要对实现的功能进行测试,然后才能进行交付。但如何保证自己完成的每个功能都是正确无误的呢?对,
单元测试!
2.1 pom中增加Junit的jar的依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
</dependency>
2.2 增加单元测试类
例如,要测试的类为src下的com.runqianapp.userManager.dao.UserDao类中的
publicStringgetUserName(StringuserId)方法
则首先新建一个
test的源码目录,原因是测试代码要与原功能代码分离,自动构建的时候,只需要把原功能的代码构建到最新的jar里。然后新建跟被测试类相同的包路径,如
test下的com.runqianapp.userManager.dao.UserDaoTest
这样,就为UserDao建好了一个单元测试类,
对于被测试的方法,需要传不同的参数,来检验方法的正确性,这个时候,不必写多个@Test来测试,而是把需要传入的参数放入配置文件,然后读配置文件。
参数配置文件的位置是与单元测试类同一目录下,如
test下com.runqianapp.userManager.dao.testData_UserDao
2.3 JUnit的常用注解介绍
2.3.1 @Test
需要运行的单元测试方法,可以有多个
2.3.2 @Before
在每一个@Test方法运行之前都会被运行,可以用来初始化方法
2.3.3 @Before
在每一个@Test方法运行之后都会被运行,可以用来方法的释放资源
2.3.4 @BeforeClass
针对整个单元测试类,只会被运行一次,在所有方法运行之前被运行,可以用来初始化环境
必须声明成staticvoid
2.3.5 @AfterClass
针对整个单元测试类,只会被运行一次,在所有方法运行之后被运行,可以用来释放资源
必须声明成staticvoid
2.4 断言
对于需要测试的方法,用断言来判断其它执行结果是否正确。
assertEquals([Stringmessage],expected,actual)
message是个可选的消息,将会在发生错误时报告这个消息。
expected是期望值,通常都是用户指定的内容。
actual是被测试的代码返回的实际值。
如:
booleansuccess=update();
assertEquals(“更新是否成功”,true,success)
这样,当运行update()的返回值不为true的时候,这个单元测试就会失败。
但有些时候,我们测试的方法并没有返回值,方法只是执行一个动作,那么这个时候,我们就不能用判断的返回值来判断方法是否执行成功。
如新建文件的方法,publicvoidcreateFile(StringfilePath),这个时候,方法并没有返回值,我们在判断方法是否执行成功的时候,可以写一个辅助方法,来检查一下指定的文件是否新建成功了,以此来验证createFile()的准备性。如:
createFile(“d:/a.txt”); booleanexist=fileExist(“d:/a.txt”); assertEquals(“新建文件”,true,exist); |
3、JMockit
当我们在编写单元测试的时候,常常会出现一些如调用的方法需要其它对象提供,而这个类现在又不具备,如很难创建、没有环境、没有开发完等情况,这个时候,我们就需要用JMockit模拟出一个类,来满足我们的需求,来完成我们核心功能的测试。
3.1 pom中增加JMockit的依赖
<dependency> <groupId>jmockit</groupId> <artifactId>jmockit</artifactId> <version>1.0</version> <scope>test</scope> </dependency> |
3.2 模拟对象
如需要测试的一个方法的一个值是配置在misInitConfig.xml中的,而misInitConfig.xml中的值需要用ReadConfInfo.getPropery()来获取,而ReadConfInfo是在应用启动时初始化的,这个时候并没有web环境,也不能初始化,而我们也不需要自己手动初始化ReadConfInfo,因为我们测试的重点不是ReadConfInfo,而是我们现有的功能。这个时候,我们就可以用JMockit模块出ReadConfInfo,来完成此功能的测试。
/** *模拟ReadConfInfo */ publicstaticvoidmockReadConfInfo(){ newMockUp<ReadConfInfo>(){ @Mock publicStringgetPropery(StringpropertyName){ if("conf_loglevel".equals(propertyName)){ return"debug"; }elseif("conf_logfolder".equals(propertyName)){ return"log"; }else{ return""; } } } } |
在单元测试的@BeforCalss中初始化,
/** *初始化环境 */ @BeforeClass publicstaticvoidinit()throwsException{ //模拟ReadConfInfo mockReadConfInfo(); } |
这样,我们就可以在程序中使用ReadConfInfo.getPropery()来完成我们的功能了。
1.sudo介绍
sudo是linux下常用的允许普通用户使用超级用户权限的工具,允许系统管理员让普通用户执行一些或者全部的root命令,如halt,reboot,su等等。这样不仅减少了root用户的登陆和管理时间,同样也提高了安全性。Sudo不是对
shell的一个代替,它是面向每个命令的。它的特性主要有这样几点:
sudo能够限制用户只在某台主机上运行某些命令。
sudo提供了丰富的日志,详细地记录了每个用户干了什么。它能够将日志传到中心主机或者日志服务器。
sudo使用时间戳文件--日志 来执行类似的“检票”系统。当用户调用sudo并且输入它的密码时,用户获得了一张存活期为5分钟的票(这个值可以在编译的时候改变)。
sudo的配置文件是sudoers文件,它允许系统管理员集中的管理用户的使用权限和使用的主机。它所存放的位置默认是在/etc/sudoers,属性必须为0411。
2.下面详细解释配置实例:
[root@ocm1 ~]# rpm -qa|grep sudo --查询是否已经安装SUDO,一般是安装了的。
sudo-1.6.9p17-5.el5
[root@ocm1 ~]# visudo ---root使用visudo会默认打开/etc/sudoers文件,
root ALL=(ALL) ALL ---系统默认只有这一行
oracle ALL=(ALL) ALL ----为ORACLE用户增加这一行,
配置完成后,重新打开会话,使用 sudo vi /etc/hosts,,往里面增加并保存测试SUDO是否可用。
3.配置中可能碰到的问题:
1、主机名配置错误导致sudo不能执行和日志纪录。
错误提示:Sorry, user
test is not allowed to execute '/bin/cat /etc/sudoers' as root on
localhost.localdomain.
解决:将其中的localhost改为真实主机名字或IP即可
test localhost=/sbin/cat /etc/sudoers
Defaults@localhost log_host /var/log/sudo.log
2、命令别名列表中命令错误导致sudo不能执行
错误提示:[jackyu@localhost jackyu]$ sudo cat /etc/sudoers
Sorry, user jackyu is not allowed to execute '/bin/cat /etc/sudoers' as root on
localhost.localdomain.
解决:由于在Cmnd alias里定义的时候命令书写有误(Cmnd_Alias CAT = /bin/cat -n /etc/sudoers).
执行:sudo cat -n /etc/sudoers
1,进入MySQL目录下的bin文件夹:cd MySQL中到bin文件夹的目录
如我输入的命令行:cd C:\Program Files\MySQL\MySQL Server 4.1\bin
(或者直接将windows的环境变量path中添加该目录)
2,导出数据库:mysqldump -u 用户名 -p 数据库名 > 导出的文件名
如我输入的命令行:mysqldump -u root -p news > news.sql (输入后会让你输入进入MySQL的密码)
(如果导出单张表的话在数据库名后面输入表名即可)
3、会看到文件news.sql自动生成到bin文件下
命令行导入数据库:
1,将要导入的.sql文件移至bin文件下,这样的路径比较方便
2,同上面导出的第1步
3,进入MySQL:mysql -u 用户名 -p
如我输入的命令行:mysql -u root -p (输入同样后会让你输入MySQL的密码)
4,在MySQL-Front中新建你要建的数据库,这时是空数据库,如新建一个名为news的目标数据库
5,输入:mysql>use 目标数据库名
如我输入的命令行:mysql>use news;
6,导入文件:mysql>source 导入的文件名;
如我输入的命令行:mysql>source news.sql;
MySQL备份和还原,都是利用mysqldump、mysql和source命令来完成的。
1.Win32下MySQL的备份与还原
1.1 备份
开始菜单 | 运行 | cmd |利用“cd \Program Files\MySQL\MySQL Server 5.0\bin”命令进入bin文件夹 | 利用“mysqldump -u 用户名 -p databasename >exportfilename”导出数据库到文件,如mysqldump -u root -p voice>voice.sql,然后输入密码即可开始导出。
1.2 还原
进入MySQL Command Line Client,输入密码,进入到“mysql>”,输入命令"show databases;",回车,看看有些什么数据库;建立你要还原的数据库,输入"create database voice;",回车;切换到刚建立的数据库,输入"use voice;",回车;导入数据,输入"source voice.sql;",回车,开始导入,再次出现"mysql>"并且没有提示错误即还原成功。
2.Linux下MySQL的备份与还原
2.1 备份
[root@localhost ~]# cd /var/lib/mysql (进入到MySQL库目录,根据自己的MySQL的安装情况调整目录)
[root@localhost mysql]# mysqldump -u root -p voice>voice.sql,输入密码即可。
2.2 还原
法一:
[root@localhost ~]# mysql -u root -p 回车,输入密码,进入MySQL的控制台"mysql>",同1.2还原。
法二:
[root@localhost ~]# cd /var/lib/mysql (进入到MySQL库目录,根据自己的MySQL的安装情况调整目录)
[root@localhost mysql]# mysql -u root -p voice<voice.sql,输入密码即可。
一、泛型概述
jdk1.5版本以后出现的新特性,用于解决安全问题,是一个安全机制。
好处:
1,将运行时期的问题ClassCastException转到了编译时期。
2,避免了强制转换的麻烦。
什么时候用:
当操作的引用数据类型不确定的时候。就使用泛型(<>)。将要操作的引用数据类型传入即可,其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型。
泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。
泛型的通配符:未知类型。
二、泛型的使用
publicclass GenericDefineDemo4 { publicstaticvoid main(String[] args) { ArrayList<String> arr=new ArrayList<String>(); arr.add("java01"); arr.add("java02"); arr.add("java03"); arr.add("java04"); Iterator<String> it=arr.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } } |
三、泛型类
1、格式
class ClassName<datatype>
{
}
2、优点
便于对任何未知的的数据类型进行操作,特别适合用作工具类,避免了强制类型转换的麻烦。
3.示例
import java.util.*; publicclass GenericClass { publicstaticvoid main(String[] args) { Tools<String> tools=new Tools<String>("hello"); System.out.println(tools.getObj()); } } class Tools<T> { private T obj; public Tools(T t) { this.obj=t; } public T getObj() { returnobj; } publicvoid setObj(T obj) { this.obj = obj; } } |
四、泛型方法
当只需要某一个方法可以接收不同类型的参数,而不是整个类的时候,就可以用到泛型方法,它将大大提高程序代码的扩展性。
示例代码如下:
import java.util.*; publicclass GenericMethod { publicstaticvoid main(String[] args) { String s="hello!Nice to meet you!"; Integer inter=3; Show show=new Show(); show.print(s); show.print(inter); } } class Show { public <Q> void print(Q q) { System.out.println(q); } } |
五、静态泛型方法
1、格式
public static <T> methodName(T param)
{
}
2、特点
由于静态方法不可以访问类中的泛型,如果静态方法操作的类型不确定,这可以把泛型定义在静态方法上。
3、示例
publicstatic <Q> void print(Q q)
{
System.out.println(q);
}
六、泛型接口
1、格式
interface Inter<T>
{
void show(T t);
}
2、特点
当接口要操作的数据类型不确定的时候,就可以使用泛型接口。
注意:
当实现接口的类已经确定了接口具体的类型时,创建对象时就不用再指明类型了,如:
class InterImpl implements Inter<String>
{
}
如果实现类仍不知道具体的类型,就要创建对象时指明,如:
class InterImpl<T> implements Inter<T>
{
}
InterImpl<String> im=new InterImpl<String>();
3、示例
import java.util.*; public class Test { public static void main(String[] args) { InterImpl<String> im=new InterImpl<String>(); im.print("hello"); } } interface Inter<T> { void print(T t); } class InterImpl<T> implements Inter<T> { public void print(T s) { System.out.println(s); } } |
七、泛型extends限定
extends E:接收E类型或者E的子类型对象。上限
一般存储对象的时候用。比如添加元素 addAll.
八、泛型super限定
super E:接收E类型或者E的父类型对象。下限。
一般取出对象的时候用。比如比较器。
android和
java程序的
测试有点小小的区别。在java中我们可以在每个类中都可以拥有一个静态的main方法,我们可以用来测试该类里面的代码的正确性。但是在android中这点确实行不通。在今天,用一个junit来进行测试。
新建一个测试例子的步骤如下:
第一,要在清单文件中导入相关的类:
<uses-library android:h=name="android.test.runner"/>这句话在<application>标签中进行添加,指定了进行测试程序需要理考的共享库。
第二,在清单中指定测试工具。用<instrumentation android:targetPackage="xxxxxxxx"(指定包名)
android:name="android.test.InstrumentationTestRunner"
/>来指定测试的工具类。
第三,假如你要进行对如下的类来测试:
class A { public int add(int c,int d) { System.out.println("a+b="+(a+b)); } } |
这是一个非常简单的类,如果要测试这个类就要再定义一个测试类,该测试类可以继承AndroidTestCase这个类。
class TestClass extends AndroidTestCase { public void testMethod() { //新建一个类的实例 A a=new A(); //调用要测试的方法 a.add(2,3); } } |
这样就可以打印出结果来达到测试的目的。
定义
单元测试是一段自动化的代码,用来调用被测试的方法或类,而后去验证基于该方法或类的一些假设,
白盒测试的一种。
特性
自动化
可复用
易于实现
快速运行
一个优秀的单元测试,应该是全自动的、可信赖的、可读性强、可维护的。
目的
帮助程序员较早的发现代码缺陷,提升产品质量,节省人力成本。
实践
这里我们使用VS2010自带的Unit
Test进行单元测试。
场景如下,我们现在有一个功能模块能够实现计算器的功能,代码如下:
我们需要对它进行单元测试,如图:
添加测试项目后,添加如下测试类型:
对计算器中的除法先进行测试:
右键Run Test(也可在左侧Run),运行后,结果为绿色,表示通过:
但如果我们对测试代码稍做调整,将除法的被除数变为0,看看会发生什么情况:
单元测试
结果显示Failed,发生错误,表示我们的代码存在问题,未考虑除零错误,对代码进行调整:
重新运行后通过,到这里,一个简单的测试就完成了。
性能测试常用术语
性能测试的应用领域
性能测试工具原理
2.LoardRunner基本操作及应用
LoardRunner原理分析
LoardRunner脚本开发流程
VuGen(基本设置、检查点、参数化、关联、调试、事物)
Controller(场景设计、场景监视<Windows Resources、IIS服务器、Unix/Linux Resource、Weblogic Server、
数据库服务器>、集合点、IP欺骗技术、负载均衡、场景执行)
Analysis(摘要报告、常见图分析、结果分析实践<分析图合并、分析图关联、页面细分、钻取技术、外部数据导入、HttpWatch分析>)
3.Linux监测、分析、调优
CPU监控
内存监控
磁盘监控
网络监控
4.Windows监测、分析、调优
LoardRunner直接监控、
Windows性能工具监控、Windows计数器
5. 数据库监测、分析、调优
6. 常用的服务器监测、分析、调优
Apache监控
Apache调优
Tomcat监控
7. 前端性能调优
前端性能分析概要
Firebug工具
HttpWatch
Chrome自带的开发工具
DynaTrace Ajax Edition工具
8.网络分析
9.代码分析与调优
10.项目实战
11.推荐书籍
<<软件性能测试过程详解与案例剖析>>
<<性能测试进阶指南>>
<<深入性能测试LoardRunner>>
/etc/profile : 在登录时,操作系统定制用户环境时使用的第一个文件 ,此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行。
/etc /environment : 在登录时
操作系统使用的第二个文件, 系统在读取你自己的profile前,设置环境文件的环境变量。
~/.profile : 在登录时用到的第三个文件 是.profile文件,每个用户都可使用该文件输入专用于自己使用的
shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件。
/etc/bashrc : 为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取.
~/.bashrc : 该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该该文件被读取。
PASH环境变量的设置方法:
方法一:用户主目录下的.profile或.bashrc文件(推荐)
登录到你的用户(非root),在终端输入:
$ sudo gedit ~/.profile(or .bashrc)
可以在此文件末尾加入PATH的设置如下:
export PATH=”$PATH:your path1:your path2 ...”
保存文件,注销再登录,变量生效。
该方式添加的变量只对当前用户有效。
方法二:系统目录下的profile文件(谨慎)
在系统的etc目录下,有一个profile文件,编辑该文件:
$ sudo gedit /etc/profile
在最后加入PATH的设置如下:
export PATH=”$PATH:your path1:your path2 ...”
该文件编辑保存后,重启系统,变量生效。
该方式添加的变量对所有的用户都有效。
方法三:系统目录下的 environment 文件(谨慎)
在系统的etc目录下,有一个environment文件,编辑该文件:
$ sudo gedit /etc/environment
找到以下的 PATH 变量:
PATH="<......>"
修改该 PATH 变量,在其中加入自己的path即可,例如:
PATH="<......>:your path1:your path2 …"
各个path之间用冒号分割。该文件也是重启生效,影响所有用户。
注意这里不是添加export PATH=… 。
方法四:直接在终端下输入
$ sudo export PATH="$PATH:your path1:your path2 …"
这种方式变量立即生效,但用户注销或系统重启后设置变成无效,适合临时变量的设置。
注 意:方法二和三的修改需要谨慎,尤其是通过root用户修改,如果修改错误,将可能导致一些严重的系统错误。因此笔者推荐使用第一种方法。另外嵌入式 Linux的开发最好不要在root下进行(除非你对Linux已经非常熟悉了!!),以免因为操作不当导致系统严重错误。
下面是一个对environment文件错误修改导致的问题以及解决方法示例:
问题:因为不小心在 etc/environment里设在环境变量导致无法登录
提示:不要在 etc/environment里设置 export PATH这样会导致重启后登录不了系统
解决方法:
在登录界面 alt +ctrl+f1进入命令模式,如果不是root用户需要键入(root用户就不许这么罗嗦,gedit编辑会不可显示)
/usr/bin/sudo /usr/bin/vi /etc/environment
光标移到export PATH** 行,连续按 d两次删除该行;
输入:wq保存退出;
然后键入/sbin/reboot重启系统(可能会提示need to boot,此时直接power off)
selenium获取input时候,发现type=”hidden” 的input无法修改value,经牛人指点,可以使用js修改
首先html源文件如下,设置为text 、hidden、submit
View Code
在浏览器加载之后如下:
这时候email 不能对外显示
使用selenium,代码如下
1 import org.openqa.selenium.Alert; 2 import org.openqa.selenium.JavascriptExecutor; 3 import org.openqa.selenium.By; 4 import org.openqa.selenium.WebDriver; 5 import org.openqa.selenium.WebElement; 6 import org.openqa.selenium.chrome.ChromeDriver; 7 //import org.openqa.selenium.ie.InternetExplorerDriver; 8 //import org.openqa.selenium.remote.DesiredCapabilities; 9 10 public class selenium { 11 12 /** 13 * @param args 14 * @throws InterruptedException 15 */ 16 public static void main(String[] args) throws InterruptedException { 17 // TODO Auto-generated method stub 18 19 String URL="E:\\2.html"; 20 //set web driver property 21 System.setProperty("webdriver.chrome.driver", "E:\\chromedriver.exe"); 22 //create a WebDriver instance 23 WebDriver driver = new ChromeDriver() ; 24 driver.manage().window().maximize(); 25 26 //load the URL 27 driver.get(URL); 28 //print current title 29 System.out.println(driver.getTitle()); 30 //run JS to modify hidden element 31 ((JavascriptExecutor)driver).executeScript("document.getElementById(\"em\").type ='text';"); 32 Thread.sleep(3000); 33 //run JS and add a alert 34 ((JavascriptExecutor)driver).executeScript("alert(\"hello,this is a alert!\");value=\"Alert\""); 35 36 //wait for 3 seconds 37 Thread.sleep(3000); 38 39 40 //create a alert instance 41 Alert alert1=driver.switchTo().alert(); 42 //print alert text 43 System.out.println(alert1.getText()); 44 //click accept button 45 alert1.accept(); 46 47 //create elements 48 WebElement we=driver.findElement(By.id("fn")); 49 WebElement su=driver.findElement(By.id("su")); 50 WebElement em=driver.findElement(By.id("em")); 51 // input something 52 we.sendKeys("username test"); 53 Thread.sleep(3000); 54 //print email tagname 55 System.out.print("Email isDislayed="+em.isDisplayed()+"\n"); 56 Thread.sleep(3000); 57 //click submit button 58 su.click(); 59 Thread.sleep(3000); 60 61 Alert alert=driver.switchTo().alert(); 62 System.out.print( alert.getText()); 63 alert.accept(); 64 65 Thread.sleep(3000); 66 67 //close web browser 68 driver.quit(); 69 70 } 71 72 } |
可以通过js修改input的type value,执行js只需要export
import org.openqa.selenium.JavascriptExecutor;
运行结果如下:
Starting ChromeDriver (v2.9.248315) on port 30175
this is a test
hello,this is a alert!
Email isDislayed=true
I am an alert box!!
上一篇里我们讨论了
测试的必需性,如果大家目前还在公司里做着测试的
工作,那就说明还是落在必需的范围里面,或者至少一段时间是吧。那接下来我们看下既然需要做测试,需要做哪些事情。
基于我自己的一些理解和观察,我试图把测试工作的层次分成三个阶段,越到后面涵盖的范围越广。这里讨论的一些做法可能更偏向于
互联网方面的测试,特别是第三个阶段。
对于一个城市,假设我们的工作目标是提升环境的质量,减少垃圾。那么我们可以做什么?
首先,我们可以请很多环卫工人,出去打扫各个街道,这个马上就有了效果,环境变得更干净了。但是还不够好的地方是明天还是有很多东西需要打扫,治标不治本,只要一停下来立马回到之前的状况。
接下来,我们往前面想一想,为什么有那么多垃圾呢?其中一个方面是很多人乱扔垃圾。所以更进步一点的方案是,对于乱扔垃圾的人有些约束或者惩罚,比如抓到了曝光或者罚钱,这样扔垃圾的人会变少。
再然后,我们发现即使做到了上面,还是有不少垃圾,而且上面强制的方案也带来不少的反感。我们需要更深层次的思考,为什么会有那么多垃圾?是因为垃圾桶太少?设计得不合理?如果是这样,就需要从其他公共设施方面做一些改进了。
对于我们的测试工作,也是有类似的思路,只不过细节上要考虑更多。
第一个阶段:发现和解决bug的阶段
这个阶段的思路基本上尽可能发现更多的bug,见一个灭一个,来两个灭一双。
发现bug,解决后验证bug,没有任何根源性的推动,或者推动的效果不好。
这个阶段,测试工作主要集中在发现bug,要做好这个,需要多个方面的努力,比如下面这些:
- 更高效的发现bug,考验测试设计的能力。
这方面有非常多的方法和技巧,以及经验,这里不细说。
- 发现bug之后如何清晰的描述,定级,以及跟进和验证。
这个看似简单,但是你会发现很多测试工作做了几年的人这样的基本功还是不够扎实。也可能没有受到过很好的训练或者一直没有人指导。
- 对业务和架构的理解能力。
没有这方面的能力,很难发现一些深层次的bug。而这样的能力对于快速
学习和一些技术基础也有不低的要求。
- 发现bug之后如果举一反三的尽早发现更多类似的bug。
大家看到的很多经典的测试书籍讲的基本都是这个阶段做的事情,比如Software Testing,How We Test Software at Microsoft,以及探索性测试相关的书籍,都是专注在如何更高效的发现缺陷。
上面这些东西都是一个业务测试人员的基本功。看似简单,但是做好并不是一件容易的事情。也许这些事情一点都不cool,不sexy,甚至去做职级评审的时候不占优势,但是对于系统质量的提升,是切切实实带来很大帮助的,其工作的价值应该得到认可。但是如果一直停留在这个阶段,就陷入到上面例子中说的扫马路的阶段,因为如果没有其他方面的改变,每次都有那么多的bug。
不过很多时候,我们的测试停留在这个阶段也是因为现状,考虑下这样的情况:
- 开发基本不自测,甚至没有自测的环境,特别是涉及多个系统的对接。
- 提测后很多基本的功能都不能正常使用
-
项目管理比较混乱,但是最终的发布日期又被老大们定死,所以测试时间常常被压缩
而且,而且没有对于开发人员的质量方面的考核,那么很长一段时间,我们的测试将处于这个初级阶段。
我相信目前还有不少的团队是处于这样疲于应对的情况下,不只是小公司,可能一些大公司的部分项目也是如此。随着整个研发体系的发展和深入,我们应该有更高的追求。
第二个阶段:质量的管理
在第一个阶段中,可能有一些人会停下来想:我们一直这样下去也不是个办法?有没有更好的做法呢?
最直接会想到的就是,怎么让别人少丢垃圾,让本身的bug就更少一些。如果我们做的工作只是发现bug解决bug,那么就是一个消耗战。不能形成一个良性的循环,就不能持续的优化,工作的长期累积价值就体现不出来。
这个阶段核心的思路是对缺陷做分析和考核,并做研发流程中主要问题的梳理和改善。
常常做的事情可以从下面几个方面来看:
1. 做质量数据的统计和分析
收集的数据很多,常见的有:
- 外网的bug情况,包括事故,及影响的程度
- 测试阶段的bug数量,分布(按系统,团队,开发个人),严重程度,bug的类别等维度
- bug的横向跨团队和系统的对比,纵向的和历史情况对比
- 版本发布的情况,代码变更行数的情况
从这些数据的收集中就能发现很多问题,比如问题集中在哪里,哪些模块,哪些人,哪些类别等等,以及有没有改善。
2. 问题的追溯和对于开发的考核
这个方面也许有一些争议,但是我还是觉得这个是一个很重要的方法。光靠观念和自觉是不够的,必需要有一定的反馈机制,就好比交规一定是配合着扣分和罚款等手段,否则记录闯红灯有什么意义呢?而且现实的来说,这些方法起到约束的作用,也是一种心理暗示,要做自己做的东西负责,也便于养成好的习惯。
通常的考核指标涉及这些方面:
- 编译失败次数的考核
- 外网事故和bug的数量
- 测试阶段的bug,特别是基础功能bug和严重bug
粗略的列了这么多,其实可以有很多,比如配置文件改错的情况,漏提测文件的次数等等。
这里也许有很多的讨论,但是让我们看看一个实际的例子。下图是某个系统的编译失败的情况,在11月份的时候提出要统计并公开(并无惩罚条款)编译失败的情况,包含到开发的团队和个人等明显,12月份开始出现了明显的下降并稳定了。这个图隐藏了一些细节,如果剔除其他因素只看开发代码原因的编译失败则更明显,特别是后面有惩罚机制之后,进一步下降。
编译失败大幅的下降一方面是节省了大家的时间,另一方面其实也是提高了版本质量,想想如果有很多的编译失败,而且是到提交测试的阶段,这样的代码质量能好吗?是可能做过自测吗? 有了这样的机制,至少会更仔细一些。
对于bug方面其实也是一样,如果开发在乎(或者被迫在乎)外网bug或者被测试发现的bug数量,他写代码的时候一定会更仔细,也会做些简单的自测,让提测的质量更高,提高了整个研发系统的效率,同时也是提升了质量,因为quality must be built in。
我个人的经验,作为测试人员几乎同时面对过两个开发团队,一个有上述的考核,一个没有。表现出来的版本质量和对质量的关注完全不一样,而且前者也并没有出现开发和测试的对立,以及测试不敢提bug等负面的情况。
3. 对于测试的考核
除了对于开发的考核,同样也有对于测试的考核,这样也更加的公平。
测试的考核通常考虑下面的指标:
- 漏测:绝对数量或者漏测率
- 版本的工作量和测试效率
- 发布延期的情况
如果测试有这样的压力,也需要不断努力去发现更多的bug。
说起考核,总有人觉得这不符合智力劳动的情况,或者互联网的作风,其实不太理解为什么会这么觉得,放眼望去,有什么工作不被考核呢,sales要背quota,为什么软件开发和测试不能对自己的工作的质量负责呢?当然,具体的指标如何去定才更合理那是另一个要去考虑的。
换个角度来看,适当的压力(不应该导致焦虑和扭曲的做法),其实是让一个人表现最好的状态。
4. 推动开发的自测
这个问题一向是个老大难问题。愿意自测的开发团队你不用太多的推动,不愿意做的推动也很难,或者你无法判断他有没有做自测。而且这方面,通常取决于开发负责人的观念和态度。
如果是介于之间的,我们可以做一些事情,比如:
- 统计测试阶段的bug中,属于开发可自测发现的比例。通过这个可以看有多少bug是不应该到测试阶段的,以及横行纵向的对比。当然这个标准要自己拿捏。
- 给出一个自测的checklist。开发在提交前要完成这个list并正式的给出报告。这个方式我们曾经在一个项目中用过,效果不错,基本功能都通过这个保证了,前提是开发负责人认可。
- 有一套自动化验收的用例,可以挂接到自动部署之后或者daily build。前提是我们的自动化要足够的问题,效果才会好。
这个阶段除了业务测试的努力,也体现出了QA的价值。这里的QA是指质量管理,有的地方叫SQA,专注在质量度量和研发流程的管理上。
到这个阶段,发现事情顺了很多,质量也有更大程度的提升,并有改善额趋势。
第三个阶段:推动全面的质量提升
到上面第二个阶段,我们发现质量有了一定的提升,但是还是有不少的问题,而且有些问题需要我们把思路和眼界拓宽来看。这里讨论的一些东西可能更适合互联网的产品。
这里列一些我们可以去做的事情,受限于个人的经验,可能还很片面。
1. 研发流程的梳理
其实在阶段2的时候也可能有些团队已经开始做这样的事情,因为在分析质量和效率问题的时候,我们发现很多问题不单纯是代码的问题,可能还涉及研发流程的很多方面,比如:
- 需求不清楚
- 跨团队的配合问题
- 代码版本管理
- 技术方面的评审和大家的理解
所以整个研发流程的规范和梳理,以及配合对应的需求和版本管理的系统也是非常的必要,实际中发现效果也是比较的明显。而且还有一点体会,在接手一个很混乱的状况时,这样角度的数量和调整比技术方案的引入更重要和切中要点,能从40分到60分,技术是往80分走的过程效果更明显。
2. 提交测试前后做的一些事情
- 代码的静态扫描
这个方法很多的团队都在做,但是实际的效果似乎差别很多,而且ROI也很难说,不过从方法本身来说还是值得去做的,对测试人员也提出来更高的要求。
- code review
这个开发应该要做,特别是开发间的交叉review,非常的有帮助。不过这个也和自测一样,取决于开发负责人的态度。另外,测试也应该去做,特别是对于diff 代码的review,我们检查做了大概两个月的时间,发现还是非常的有收获。发现了一些黑盒难以发现的问题,以及开发的代码夹带,并且对于这个版本影响范围的评估也更准确。但问题是短期会花费测试更多时间,以及需要测试人员有一定的技术能力。