存档

‘程序员’ 分类的存档

软件测试工程师的职业素质

2010年8月30日 闫鹏 2 条评论

记得以前面试过一个女孩,她认为软件测试就是点击网页,囧,作为一名软件测试工程师,我当时真是无地自容啊。相信很多人都把这个职业想象的非常简单,作为软件测试工程师的我,是有必要普及一下软件测试的童鞋都需要在哪些方面提高自己的。

1.分析能力。软件测试的核心其实应该就是设计测试用例了(具体啥样的用例设计,请参见《什么样的测试用例是好的》),而设计测试用例,就是依赖与分析能力了。这里我们不说那些常用的设计方法,从一个稍高的层面上来讲,可以说就是怎么将一个复杂的系统进行抽象,分析拆成几个不同的维度,结合维度可能出现的情况进行有选择的组合,以最小成本获取最大的收益。无法将一个复杂系统拆解成简单的维度,是没法做好用例设计的

2.编程语言。语言其实就像说话一样,只不过我们常说的英语日语之类是与人沟通,计算机语言就是与计算机进行沟通的。对于测试工程师来说,精通一门语言,熟悉其它几门语言是有必要的。对于不同语言编写的被测程序,是有不同特点的,如果对实现的语言不了解,无法进行白盒测试,没法看代码diff(结合代码diff做测试)来提高效率。对于特点不了解,可能也会导致自己漏掉部分内容。

3.设计能力。不要认为设计能力就是开发工程师的事情,拥有好的设计能力,就可以在设计评审的时候多提意见,促进开发工程师使用好的设计,不仅对开发有好处,对测试也是很有好处的。这样才能防患于未然,不仅自己的劳动力,也节省团队的劳动力。

4.对业务的理解。对业务的理解越充分,就越能够理解最终用户的需求,促进产品设计使用好的方式,促进产品成功。难道你想做一大堆不成功的项目么,那样是多么没有成就感的一件事啊。

5.自动化相关的考虑。随着项目越来越多,系统的测试项目也会积累的越来越多,每次有新功能了,难道要用手工来回归一下原有的case么。自动化测试是提高回归测试效率的唯一解决方案(如果你说还有解决方案就是不回归,我…),以高效率促进高质量,才是一个良性循环的发展方式啊。

嗯,以高效率促进高质量,我觉得很有很有道理。

结合代码diff做测试

2010年8月26日 闫鹏 6 条评论

代码diff与测试相结合,可以对测试起到较明显的促进作用,通常都是用在模块的功能升级使用。结合代码diff做测试,好处主要有下面几点:

1.提高旧有case回归的效率。从代码diff里面,可以看到旧有功能哪些地方没有任何改动,这样就可以增加信心,节省对旧有功能回归测试的工作量。当然,如果升级导致旧有功能的前置条件产生变化,还是需要注意的

2.明确case的重点。知道变动的内容,就可以根据修改的代码设置case,有针对性的测试。

3.辅助定位发现的问题。

4.直接发现bug或者代码设计上的问题。

5.测试人员的经验积累。经常看别人的代码diff,积累经验,就能加深自己对项目的理解,判断大致什么地方容易出现问题,提高职业素养。

diff少的时候,就可以直接发现问题了,这个非常有用,可以极大的提高效率。如果对被测对象的了解比较深入,通过查看diff即可知道新功能修改的是否到位,也可看出新增加的地方是否处理很山寨,不具备通用性等。另外比如实现细节上,看看申请的内存是否会溢出等也可以在这里处理。

有的时候,代码diff很多,看diff可以只关心重点内容:

1.if语句。新增或者修改的if标志着新旧功能点所走的不同逻辑分支。通过查看修改点的判断参数来设计case覆盖不同的分支。另外也要关注是否条件出现问题,程序走到错误分支的情况。

2.新增的函数。这种情况下,新增的函数通常都是为了满足新功能增加的部分逻辑,这时就设计case重点照顾一下新增的函数。

3.循环。比如修改了循环的条件,里面增加了break等也是属于流程分支改变的情况。

结合代码diff的作用很明显,但貌似总结这个的文章也很少,而实际用到的也是比较细小,零碎的东东,在这里先暂时列出一些,开启话题,逐渐补充吧。

分类: 程序员 标签:

什么样的测试用例是好的

2010年8月23日 闫鹏 1 条评论

作为测试人员,设计测试用例是干活的第二步(第一步当然是了解测试对象嘿嘿),这一步做的好与否,对后续工作起着决定作用,那么如何评价一个测试用例的好坏,或者说,设计的成功与否呢,本文大概讨论一下,抛砖引玉吧,记录在这里,看看是不是可以作为一次团队讨论的话题。

在此之前,我们需要明确测试工程师的工作原则:用最小的成本找出最多的问题。

1.用例覆盖程度

毫无疑问,这一点应该是最重要的,无需多说,覆盖率最大化是一套测试用例的最重要评价标准,如果漏测就杯具了。

2.用例是否已经达到工作量最小化

在满足用例覆盖程度最大化的前提下,应该尽量减小执行用例所需要的工作量。这些方面的方法有不少,如条件覆盖,分支覆盖,正交覆盖等方法。面对不同的测试对象,也有不同的方法来保证:对于网页背后的php逻辑,可以通过在网页上测试后,用一些工具比如xdebug来统计代码覆盖率;对于向外提供接口的server,采用的方式就是分析在外面暴露的接口设计用例,大致的通过接口参数来估计一下分支判断的情况。

3.用例的分类以及描述是否足够清晰

用例的分类,在这里是指相同类型的用例是否放在一起了。例如:接口类的用例,参数的取值范围是1-3,但是现在却传入4;数据类用例,状态机现在位于状态2,却要求状态跳转到无法到达的4;逻辑类用例,正常功能的产出等。将相同类型的用例放在一起,有助于理清思路,清楚了解用例设计是否完备。

用例的描述,是指描述的清晰程度是否能够形成文档。例如上面参数取值范围的例子,用例这样写:“传入错误的值”或者“传入非1-3的值”,明显没有写成“传入值4”有效。这与写程序一样,总是写闭区间的范围而不是开区间。

4.用例是否表明了测试目的

写明用例的测试目的,对文档的易于理解性和工作交接的好处不言而喻,现代软件工程不可能只有一个人在做事情,项目于人员的变动也是难免的。在过程中留下足够的信息,可以在后续工作提高很多效率。

5.测试用例的易于维护性

如果被测对象有所升级,测试用例的说明或者脚本是不是容易维护呢?例如在有状态机的情况下,测试用例之间是相互依赖的(即需要一定的执行顺序),这样被依赖的用例修改后,后端不需要同步根据修改。而如果用例之间没有相互依赖关系(如用例是自己造的数据,不是依赖于前端的产出),可能一旦有变化,就需要修改这两个。当然,这两种情况不能绝对的说哪种好,是需要看实际使用时候的情况进行取舍的。不过,通过一些系统性的工具支持,也会出现一种做法绝对性的好于另外一种的情况,情况很多,做法也有很多,在这里就不多说了。

说了这么多,其实这个第二步,还是严重依赖于第一步的,如果对测试对象的需求,实现等都不了解,设计用例也就无从谈起了。

分类: 程序员 标签:

sql运行简单封装

2010年7月23日 闫鹏 1 条评论

作为一个QA,我本来是很少写代码的,不过这段代码用的次数比较多,每次用的时候都改一些,比较烦,所以整理了一个通用的,作为个人代码库的第一块石头吧
升级于2010-7-26,修复bug

class sql_runner{

	private static $_arr_self = array();

	private $_addr = false;
	private $_user = false;
	private $_passwd = false;
	private $_db_connection = false;

	static function get_instance($addr, $user, $passwd){
		$key = $addr.'#'.$user.'#'.$passwd;
		if(false === array_key_exists($key, self::$_arr_self)){
			sql_runner::$_arr_self[$key] = new sql_runner($addr, $user, $passwd);
		}

		return sql_runner::$_arr_self[$key];
	}

	private function __construct($addr, $user, $passwd){
		$this->_addr = $addr;
		$this->_user = $user;
		$this->_passwd = $passwd;
	}

	public function run_sql($sql){

		if(false === $this->_db_connection || !mysql_ping($this->_db_connection)){
			UB_LOG_DEBUG("connecting db $this->_addr, $this->_user");
			$this->_db_connection = mysql_connect($this->_addr, $this->_user, $this->_passwd);

			if(false === $this->_db_connection){
				UB_LOG_FATAL("connect db failed: ".mysql_error());
				return false;
			}else{
				mysql_set_charset('latin1', $this->_db_connection);
			}
		}

		$result = false;
		$result = mysql_query($sql, $this->_db_connection);
		if(false === $result){
			UB_LOG_FATAL("[$sql] execute failed:". mysql_error($this->_db_connection)."\n");
			return false;
		}

		$result_arr = array();
		if(is_resource($result)){
			while($row = mysql_fetch_assoc($result)){
				$result_arr[] = $row;
			}
			mysql_free_result($result);
		}

		UB_LOG_DEBUG("[$sql] execute succed, selected result:".print_r($result_arr, true));
		return $result_arr;
	}

}

/**for log functions*/
if(!function_exists('UB_LOG_DEBUG')){
	function UB_LOG_DEBUG($log){
		print($log);
	}
}
if(!function_exists('UB_LOG_FATAL')){
	function UB_LOG_FATAL($log){
		print($log);
	}
}
分类: 程序员 标签: ,

sigterm sigint sigkill 区别

2010年5月20日 闫鹏 6 条评论

我看网上应该有不少搜索这个区别的问题,但是回答的都不全面,其中sigterm与sigint尤其有一点区别比较重要,但大都没有提及,今天我就遇到了这个问题,纠结了20分钟才搞明白咋回事。

首先,对于说这几个信号都是终止程序运行的说法不太准确,因为程序收到信号后,如果不对信号处理,就会导致程序退出,但如果程序捕获信号进行处理,按照它的逻辑,它是不一定会退出的。

在这三个信号中,sigkill是不能被捕获的,程序收到这个信号后,一定会退出。这就是kill -9一定能保证将程序杀死的原因。

下面说一下sigterm与sigint的区别,其中有一点区别区别很多文章都没有提及,也是我写这篇blog的原因(如果人家都写了,我就不用写了呗)

信号 产生方式 对进程的影响
sigint 通过ctrl+c将会对当进程发送此信号 信号被当前进程树接收到,也就是说,不仅当前进程会收到信号,它的子进程也会收到
sigterm kill命令不加参数就是发送这个信号 只有当前进程收到信号,子进程不会收到。如果当前进程被kill了,那么它的子进程的父进程将会是init,也就是pid为1的进程

下面这两个代码片段就能够验证这种情况(注意使用pcntl的时候,一定要declare ticks,要不然会杯具的发现函数没有被调用,进程不退出,信号发过去没有作用。php手册竟然没有强调这一点):

文件:loadhelper.php

#为了pcntl能够截获信号
declare(ticks = 1);

$arr_processes = array();

function terminate($signo){
    echo "aaaaaaaaaaa\n";
}

pcntl_signal(SIGTERM, "terminate", true);
pcntl_signal(SIGINT, "terminate", true);

foreach($argv as $key => $operation){

    if(0 === $key){
        continue;
    }   

    $pipes = array();
    $process = proc_open($operation, array(), &$pipes);
    if(false === $process){
        exit(-1);
    }
    $arr_processes[] = $process;
}

while(true){
    sleep(100);
}

文件:child.php

declare(ticks=1);

pcntl_signal(SIGINT, "terminate");
pcntl_signal(SIGTERM, "terminate");

function terminate($signo){
    echo "test_child\n";
}

while(true){
    sleep(100);
}

使用命令php loadhelper.php “php test.php”可以启动这个测试。
1.输入ctrl+c发送sigint可以看到,父进程与子进程的terminate都得到了执行,都有输出,但父进程不会退出,因为子进程还没有退出
2.通过kill向父进程的pid发送sigterm,可以看到,只有父进程输出

遗留问题:

父进程(loadhelper)接受到一次信号后,如果在terminate函数中调用exit,它还是不能退出的,因为还有子进程没有退出。但是从此以后它就不能再接收信号了(子进程还是能够接收到sigint),可能是exit使进程进入了待回收状态,具体还 需要后续在分析一把。

分类: 程序员 标签: ,

php中getopt的缺陷及修复

2010年5月17日 闫鹏 没有评论

在这里,我不得不再一次感叹php语言库函数的山寨与不专业。getopt函数就是一个典型的例子,通常用的时候,大家可能觉得没有什么,但在某些情况 下,就真的让人很囧。一个简单的函数,稍微多花几分钟就弄得更好一些了,但这个语言有个随意的开端,就有个随意的实现啊。

在linux中,使用getopt时候,有两种情况:

1.取得的参数解析成字符串:“php test_arg.php -c abc”,这种情况c参数取得的结果就是abc这个字符串

2.取得的参数解析成数组:“php test_arg.php -c abc -c abc123”,这种情况c参数取得的结果就是包含abc与abc123的数组

但是遇到这种情况呢:“php test_arg.php -c abc*”?由于linux的shell已经帮程序做了输入参数的解析,这时候c参数得到的既不是abc*这个结果也不是一个数组,而是被shell展开成了很多文件名后的第一个。

可能getopt用的童鞋很少,但这种山寨的设计,实在太让人憋屈了,自己花个10分钟写一个就比它的要好,为了避免大家重复劳动,分享一个代码片段

   function mygetopt(){
        global $argv;
        $result = array();

        $current_key = false;
        foreach($argv as $opt){

            $matches = array();
            if(1 === preg_match("/^-{1,2}(.*)$/", $opt, $matches)){
                $current_key = $matches[1];

                if(false === isset($result[$current_key])){
                    $result[$current_key] = false;
                }
            }else if (false !== $current_key){

                if(false === $result[$current_key]){
                    $result[$current_key] = $opt;
                }else{
                    if(false === is_array($result[$current_key])){
                        $result[$current_key] = array($result[$current_key]);
                    }
                    $result[$current_key][] = $opt;
                }
            }
        }   

        return $result;
    }

为了方便使用,将新版本的getopt函数设置为不接受任何参数,但是解析的结果可以输出所有的参数内容。因为php官方的getopt函数使用后,也无非是对输出的数组进行foreach之后进行switch,还不如方便点,直接解析所有呢。除了这一点,这个getopt函数的输出结果与php官方的完全一致

php官方getopt函数参考文档:http://cn.php.net/manual/en/function.getopt.php

分类: 程序员 标签: , ,