本文只为整理思路,供自己日后参考。现在就从从一个执行文件a.out的运行开始,自上而下地分析linux是如何运行一个执行文件的。
1、首先,需要了解一下a.out这个目标文件。a.out在linux下是ELF(Executable Linkable Format)文件格式,该目标文件由一个文件头、代码段、数据段(已初始化)、从定位信息区、符号表及符号名字字符串构成,如下左图所示,经过链接后生成执行文件如下右图所示,需要说明的是1).bss段在目标文件和执行文件中并不占用文件的空间,但是它在加载时占用地址空间;2)链接后各个段在虚拟空间上的地址就确定了,并且linux下,ELF可执行文件默认从地址0x080480000开始分配。
我们知道在linux下运行一个程序只要在shell中执行 ./a.out 这个命令就OK了,剩下的事情操作系统会替我们完成。但是操作系统到底做了什么,它是怎么做的呢,接下来就来解析一下。
2、linux系统中每个程序都运行在一个进程上下文中,这个进程上下文有自己的虚拟地址空间。当shell运行一个程序时,父shell进程生成一个子进程,它是父进程的一个复制品。子进程通过execve系统调用启动加载器。加载器删除子进程已有的虚拟存储段,并创建一组新的代码、数据、堆、栈段,新的堆和栈被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小组块,新的代码和数据段被初始化为可执行文件的内容,最后将CUP指令寄存器设置成可执行文件入口,启动运行。
执行完上述操作后,其实可执行文件的真正指令和数据都没有别装入内存中。操作系统只是通过可执行文件头部的信息建立起可执行文件和进程虚拟内存之间的映射关系而已。现在程序的入口地址为0x08048000,刚好是代码段的起始地址。当CPU打算执行这个地址的指令时,发现页面0x8048000~0x08049000(一个页面一般是4K)是个空页面,于是它就认为是个页错误。此时操作系统根据虚拟地址空间与可执行文件间的映射关系找到页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,并在虚拟地址页面与物理页面间建立映射,最后把文件中页面拷贝到物理页面,进程重新开始执行。该过程如下图所示:
3、这里比较难理解的就是这个分页机制,讲到分页机制,就不得不提linux的分段与分页机制,这也是这篇文章的重点。我们先来看一张图:
这张图展示了虚拟地址进过分段、分页机制后转化成物理地址的简单过程。其实分段机制是intel芯片为兼容以前产品而保留下来的,然后linux中弱化了这一机制。下面我们先简单介绍一下分段机制:
分段提供了隔绝各个代码、数据和堆栈区域的机制,它把处理器可寻址的线性地址空间划分成一些较小的称为段的受保护地址空间区域。如果处理器中有多个程序在运行,那么每个程序可分配各自的一套段。此时处理器就可以加强这些段之间的界限,并确保一个程序不会通过访问另一个程序的段而干扰程序的执行。为了定位指定段中的一个字节,程序必须提供一个逻辑地址,该地址包括一个段选择符和一个偏移量。实模式下,段值还是可以看作地址的一部分,段值位XXXXh表示以XXXX0h开始的一段内存。而保护模式下,段值仅仅变成了一个索引,只想一个数据结构的一个表项,该表项中定义了段的起始地址、界限、属性等内容。cs、ds等寄存器中存的就是这个段选择符,用段选择符中的段索引在GDT或LDT表中定位相应的段描述符,把段描述符中取得的段基地址加上偏移量,就形成了一个线性地址。
得到了线性地址之后,我们再来看看分页机制如何把它转换成物理地址。处理器分页机制会把线性地址空间(段已映射到其中)划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上。分页与分段最大的不同之处在于分页是用来固定长度的页面(一般为4KB)。如果仅适用分段地址转换,那么存储在物理内存中的一个数据结构将包含器所有部分。但如果适用了分页,那么一个数据结构就可以一部分存储在物理内存中,而另一部分保存在磁盘中。
处理器把线性地址转换成物理地址和用于产生页错误异常的信息包含在存储与内存中的页目录和页表中。也变可看作简单的4K为单位的物理地址数组。线性地址的高20位构成这个数组的引索值,用于选择对应页面的物理基地址。线性地址的低12位给出 了页面中的偏移量。页表中的页表项大小为32位。由于只需要其中20位来存放页面的物理基地址,因此剩下的12位可用于存放诸如页面是否存在等属性信息。如果线性地址引索的页表项被标注为存在,我们就从页面中取得物理地址。如果表项中不存在,那么访问对应物理页面时就会产生异常。
页表含有2^20(1M)个表项,而每项占用4个字节。如果作为一个表来存放的话,最多将占用4MB内存。因此为了减少内存占用量,80x86适用了两级表。由此,高20位线性地址到物理地址的转换也被分成两步进行,每部适用其中10个比特。
第一级表称为页目录。它被存放在1页4k 页面中,具有2^10(1k)个4字节长度的表项。这些表项指向二级表。它们由线性地址最高10位作为引索。
第二级表称为页表,长度也是1个页面。线性地址高10位获取指向第二级页表的指针,再加上中间10位,就可以在相应页表中获得物理地址的高20位。而为地址的低12位就是线性地址的低12,这样就组成了一个完整的32位物理地址。分段、分页的整个过程可见下面这张图:
# mysql -u root -p //默认无密码,登陆数据库
# mysql> use mysql; //使用mysql数据库
#
# mysql> update user set password=PASSWORD(‘newpassword’) where user=’root’;
//更新密码
#
# mysql> flush privileges; //刷新MySQL系统权限的相关表 也可以理解为刷新缓存
#
# mysql> exit
看见 行匹配 和更改为3说明成功。
之前用的mysqladmin方式更改密码的缺点是:
仅能更改一行记录。这样会导致 root用户在 mysql 表里的3行(localhost {HOSTNAME} 127.0.0.1)记录有不同的密码,会对以后使用root用户操作带来隐患。
摘要: 1.评测项目简介 此次评测主要针对Android系统的各款机型的性能状况,涵盖目前主流的基于Android衍生出的国产操作系统,通过对比,得出Android系统各款机型的性能评测对比分析结果。本次评测从以下几个方面进行对比分析: 工具选型: 博为峰自主研发的终端性能评测工具,该工具评测指标均按照相关国际标准构造。 2.被评测的手机操作系统名称操作系统版本手机型号CPU内存Android4....
阅读全文
前两篇文章讲解了一些关于单元测试的基本理论知识,接下来我们应该理论联系实践,在实践中体会单元测试带给我们的便利!
环境:VS2008,2010版本!
目录:
1.前言
2.单元测试框架
3.断言(Assert)
4.测试异常
5.忽视测试
6.数据驱动测试
7.单元测试的利器 → Moles技术
1.前言
一个完整的测试必须符合以下几点:
A) 考虑到各种情况,准备测试所需要的各种数据,这一步是测试的关键所在!
B) 调用要测试的方法!
C) 验证被测试方法的行为跟预期的是否一致!
D) 完成验证测试之后清理各种资源!
2.单元测试框架
测试框架的DLL文件名为: Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
在VS自动创建单元测试项目就会自动引用这个DLL了!
测试类的基本结构如图:
基本的测试类结构就是这样的!
3.断言(Assert)
在单元测试代码里断言是无处不在的,我们应该合理的使用断言来验证数据!
它是一个静态类,主要有下面几种方法用来验证函数的结果跟我的预期是否一致!
① Assert.AreEqual
主要是验证函数产生的影响值或返回的值跟预期是否一致,这个方法不适合验证返回的数据集以及集合之类的数据,主要针对字符串,数字等等的单一类型,它还有个泛型重载,这个是比较好的,建议多使用,它还有第三个参数,是一个string型的message,基本上不会用到!
☆ Note:不要把参数的含义搞混,第一个参数是你的期望值(Except),第二个参数是函数返回或影响程序产生的实际值(Actual),不要把两个颠倒过来,因为如果运行正确没有什么大碍,如果运行产生错误,有的时候就会看错掉,造成判断失误,要注意了,这是一个规范!
例子:
Assert.AreEqual<string>("a", "a", "cheng xu yuan");
② Assert.AreNotEqual
没什么要讲的,情况跟上面相反,主要是验证实际值跟期望值不相等的情况!
③ Assert.AreSame
判断实际值跟预期值的类型是否一致!
④ Assert.AreNotSame
跟上面正好相反,测试类型不一致!
⑤ Assert.IsNotNull,Assert.IsNull,Assert.IsTrue,Assert.IsFalse
看这些方法名就知道什么意思了!
⑥ Assert.IsInstanceOfType,Assert.IsNotInstanceOfType
判断指定的对象是否是指定的类型!
⑦ Assert.Fail
迫使断言失败,不管前面的断言是否都成功了,但测试结果是错误的,因为我强制断言失败了!
总结:一个好的测试案例,里面的断言至少是大于一个的,这样才能验证数据的准确性,保证验证数据的严谨性!
例:如果实际值是个DataSet一般测试流程为:①判断是否为“null” → ②判断是否为“Empty”(验证是否有数据) →③ 接下来再验证数据的一致性,所以验证DataSet的基本流程就是这样的!
/// <summary> /// Modify. /// </summary> [TestMethod()] [RollBack()] public void UpdateBondAssessApplicationConfiguration_ModifyData_DataUpdated() //注意命名规范,下一篇会着重讲解! { AssessApplicationConfigurationDataSet assessConfigurationDataSet = target.GetBondAssessApplicationConfiguration(); assessConfigurationDataSet.CM_LookupConfiguration[0].Description = "This is my modify"; AssessApplicationConfigurationDataSet actual = target.UpdateBondAssessApplicationConfiguration(assessConfigurationDataSet); AssessApplicationConfigurationDataSet newAssessAppDataSet = target.GetBondAssessApplicationConfiguration(); Assert.IsNotNull(actual); //第一步 验证是否为Null Assert.IsTrue(actual.CM_LookupConfiguration.Rows.Count > 0); //第二步 验证是否为Empty Assert.IsTrue(newAssessAppDataSet.CM_LookupConfiguration[0].Description == "This is my modify");//第三步 验证数据的一致性 Assert.IsTrue(CompareToTable(newAssessAppDataSet.CM_LookupConfiguration, actual.CM_LookupConfiguration)); } |
4.测试异常 → ExpectedException(异常属性)
当代码中有抛出异常的情况时,我们应该对这个异常的准确性进行测试,首先要捕获这个异常,然后再跟我预期定义的异常进行比较就行了!
在VS自带的测试框架中提供了处理异常的测试!
这个异常属性有两个构造函数重载:
第一个参数:函数中出现异常的类型 → [ExpectedException(typeof(NullReferenceException))]
第二个参数:异常所提示的信息(Message) → [ExpectedException(typeof(NullReferenceException),"Don't is null.")]
总结:在测试一些非法数据,边界值,异常测试是非常有用的,一旦发现异常,后面的一切断言和代码将跳过,然后系统将会把异常和你预期的异常进行比对,一致则表示通过,反之有错误!
/// <summary> /// Is null. /// </summary> [TestMethod()] [RollBack()] [ExpectedException(typeof(NullReferenceException))] //第一种只定义了异常类型! public void UpdateLookupChequeNumberRegion_UpdateChequeNumberRegionAndDataSetIsNull_ThrowException() { LookupChequeNumberRegionDataSet actual = target.UpdateLookupChequeNumberRegion(null); } /// <summary> /// Add. /// </summary> [TestMethod()] [RollBack()] //第二种定义了预期的异常类型还定义了异常信息! [ExpectedException(typeof(BusinessException), "The category and code combination you have entered already exist. Please enter a different category and code combination.")] public void UpdatePolicyConsideration_AddPolicyTheCODEIsSame_ThrowException() { string newGUID = Guid.NewGuid().ToString(); CodeTableDataSet expectedDataSet = target.GetPolicyConsideration(); expectedDataSet.T_IC_CODE.AddT_IC_CODERow(GetNewRow(expectedDataSet, null, ref newGUID)); CodeTableDataSet actual = target.UpdatePolicyConsideration(expectedDataSet); }
|
5.忽视测试 → Ignore属性
添加这个属性表明现在这个测试案例在运行时将不会被执行,跳过此方法!
[TestMethod()] [Ignore()] //运行单元测试时将忽视这个测试案例 public void GetBondDebt_InputValidClientID_RecordFound() { int clientCoreID = GetClientIDForSomeCondition(); DebtDataSet actual = target.GetBondDebt(clientCoreID); Assert.IsNotNull(actual); Assert.IsTrue(actual.Debt.Rows.Count > 0); Assert.IsTrue(CompareToDataSetAndList(actual, clientCoreID)); } |
6.数据驱动测试
在上一篇提到过当你的数据量很大的时候,有一种解决方案是采用数据驱动测试,把我们需要用来测试的数据放在文件中,然后运行测试,让测试代码去读取文件中的数据!
其实它也有一定的局限性,所以在合理的场合中合理的使用将减轻我们的工作量,这个判断只能给为看官去判断了!
当前支持Sql Server ,Oracle,CSV,XML等等文件,下面我就介绍下CSV和XML文件的使用方法!
①CSV作为数据文件
我们写一个简单不能再简单的的加法运算方法来作为示例:
public int Add(int numberOne, int numberTwo) { int one = numberOne; int two = numberTwo; int three = numberOne + numberTwo; return three; } |
a) 首先要创建连接字符串,具体步骤如下:
I,
II,
III,
通过上面步骤的操作,就会进入选择文件的界面,按照提示即可完成,当然前提你的数据文件要准备好,完成之后会出现如下代码:
/// <summary> ///Add 的测试 ///</summary> [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "|DataDirectory|\\number.csv", "number#csv", DataAccessMethod.Sequential), //这个是连接字符串,你也可以手动写,不要按照上面步骤操作,当然要写对单词和“注意文件的路径”! DeploymentItem("MyTest\\number.csv"), TestMethod()] public void AddTest1() { Program target = new Program(); int numberOne = Convert.ToInt32(this.testContextInstance.DataRow["One"]); //获取数据! int numberTwo = Convert.ToInt32(this.testContextInstance.DataRow["Two"]); ; int expected = Convert.ToInt32(this.testContextInstance.DataRow["Except"]); int actual = target.Add(numberOne, numberTwo); Assert.AreEqual(expected, actual); } |
点击运行你的测试,程序会通过你设置的路径去文件里面一行一行的读取数据,然后验证数据,如果其中有一行数据报错,那么整个测试也就是失败了,所以保证数据的正确性很重要!
注意:CSV默认会以Excel的方式打开,但是它里面的数据摆放有一个规则,就是以逗号的形式呈现,所以我还是建议大家使用记事本来添加数据!
②使用XML作为数据文件
其实使用方法跟上面的一样,尤其注意的是你XML文件里面数据的格式!
<table> //一定要设置根节点,如果不设置在文件选择的时候会报错! <my> <price>1</price> <number>1</number> <total>1</total> </my> <my> <price>2</price> <number>2</number> <total>4</total> </table> |
注意:怎么来获取数据呢?Convert.ToInt32(this.testContextInstance.DataRow["price"]) 通过这样的方式来获取数据,还有在选择文件时,要确认是否选中了一个数据源文件,这个不要疏忽了!
③使用数据库作为数据文件很简单,配置一下连接字符串,设置一下路径就好了,在此就不讲解了!
7.单元测试的利器 → “Mole”技术
首先想感谢下项目组的Tian Mi大哥是他把这项技术带给我们的,谢谢他的无私奉献!
“Mole”文件的下载地址:http://www.kuaipan.cn/index.php?ac=file&oid=29568238492847207
①应用环境:
所属模块依赖于系统的其它模块,依赖于系统的一些配置环境,还有就是调用第三方接口或服务等等的场景!
在这样的场景下我们的测试是不能直接调用第三方接口或服务的,所以我们要制造一个虚拟的环境,当我们去调用接口时,Mole技术会拦截我们调用的方法,从而转向我们自己制造的虚拟环境,那么就不会直接调用第三方接口和服务了!
②基本的操作流程不讲解了,如果你看了http://www.cnblogs.com/hwade/archive/2010/11/26/Moles.html这篇文章就明白了,我就将一些在写测试时遇到的几个小问题!
③如果“Moles”结束后,项目编译的时候报错,具体的错误我也不怎么知道了,但是我在网上搜了很久才找到答案的!
步骤:点击你的Moles文件,你会发现他是个XML配置文件,修改它的属性就好了,如:
<Moles xmlns=http://schemas.microsoft.com/moles/2010/ DisableCache="true" > //增加一个DisableCache = "true"这个属性,如果遇到问题,基本都是这个问题,这样解决就Ok了! <Assembly Name="Foundation.FinanceManagement.BusinessService" /> </Moles>
|
③有“Ref”参数的方法应该怎么写?
如果参数中ref参数,那么就不能按一般的方式写了,应该这样写:
④Moles的基本语法
Moles的内部原理没有明白,只懂的怎么去用它,基本的语法可以模仿上面的写法!
因为你把一个DLL Moles之后并编译之后会生成一个新的DLL,这里面就是虚拟环境场所,而且类名称都是以“M”开头的!
语法:命名空间 + Moles + M + 你Moles掉的类名 + Allinstance + 选择你要拦截的方法名 = (instance(必填参数,如果方法没参数,也必要添个)) =>
{
//里面写你的逻辑,制造你自己的虚拟环境,如果方法有返回值,可以自己指定返回值的!
};
好了,关于在实践中的单元测试就这么多了,其实最重要的还是你考虑测试的角度要多样化,就是考虑问题要全面,还有代码的简洁性,重构,封装等等,下一篇将是对这方面的着重讨论!
相关文章:
走进单元测试二:测试需要从哪些方面着手
走进单元测试四:单元测试背后的思考和感悟
前言
笼统的来说测试条件无非就是两个方面:① 正向测试 ,② 反向测试!
如果单从这两个方面来思考,肯定出现丢三落四的情况,也就是说不全面,所以应该在上面两种情况的基础上再进行具体划分,那么只要我们能够遵循这些条件基本上就能做到全面(如果能做到,大约80%的问题应该都解决了),于是就出现了下面要说的六个方面内容!
前辈们把这些测试条件总结为:Right – BICEP
1.Right - 做正确的事,可以说是“正向测试”
这种测试前期任务是要准备足够的正确数据(前提是要保证数据的正确性,这个很重要),运行代码后返回的值或产生的影响是要跟自己的预期是一致的!
注意:如果准备的数据太大或容易丢失,建议把它放在数据文件中,然后让单元测试读取这个文件,这种方法会在下一篇会说到!
2.B - 边界条件(Boundary)
边界条件是测试里面的重中之重,必须要有足够的认识和重视!
而它又被分为七个方面的子条件,下面就让我们来一一熟悉它!
①一致性(Conformance)
数据是否符合我规定的格式(也可以说是非法字符吧)!
案例:比如我传入的参数文件名需要的格式是:文件名 + 日期(yy-mm-dd) + 扩展名,那么我就要写一个测试传入的文件名为 :“sa#$#$#$#”这样的格式!
②有序性(Ordering)
这方面主要是对涉及到数组和集合的数据,而且对数据的顺序有严格要求的函数,需要对它们里面数据的顺序进行测试!
比如:点菜系统菜谱中每道菜的顺序,或者去银行办理业务的排队系统等等!
③范围,区间性(Range)
值是否存在于一个最大值和一个最小值之间,主要是对值类型的数据做的测试!
这里面还有一个重要的测试点是 → 对数组,集合,以及Table,DataSet中的索引值进行测试,比如索引值不能为负,不能超出索引的范围等等情况!
比如:一个通过ID来搜索信息的函数,应该对这个ID进行最大值和最小值的测试!
④引用,耦合性(Reference)
这方面主要是:代码是否引用了一些不受本身代码控制的外部因素(比如:调用第三方接口,调用其它模块的接口等等)!
对于这些情况我们是没有办法控制的,所以在测试的时候只能模拟,而在模拟时我们会用到“Mole”技术,让它来帮助我们创建一个模拟环境(下一篇会介绍)!
比如:有的项目会调用银行接口,这种情况下只能先创造一个虚拟银行接口,然后再进行测试!
⑤存在性(Exist)
固定的测试,如Null,Empty,非零等等,这些都是必须考虑的!
⑥基数性(Cardinality)
对于这个测试说起来还是蛮难理解,这个测试只有在特定的场合下才会去考虑它!
它遵循一个原则:“0-1-N”!
⑦时间性(Timer)
对时间比较有依赖的软件或系统应该在这个方面着重测试!
主要考虑:事情是否按时间的顺序执行,是否在正确的时间执行,是否出现执行事情延误了!
相对时间:网站超时,数据更新超时等等!
绝对时间:不同的client间的时间是否同步!
并发问题在时间性测试中比较重要!
3.I - 反向关联(Inversion)
在准备数据或者验证数据时的一种反向思维,涉及到个人的思维方式问题了!
比如:有个函数对数据库进行了操作,但是它没任何返回值也没有任何提示,如果你是对正确的数据进行了测试,那么你要怎么知道测试结果跟你的预期一致呢,这里你就应该去查找数据库,看数据库里面的数据是否有真的改动,这就是一种反向的思维方式!
4.C - 交叉检查(Cross)
用一种数量检查另一种数量(需要考虑的情况不是很多)!
5.E - 强制产生错误(Error)
通过代码强制产生软件在运行过程中出现的特殊情况!
可以参考下面几种测试方面:内存耗光,磁盘用满,断电,正在执行更新数据时出现断网现象,网络负载严重导致瘫痪,系统时间出现导致和国际时间不一致等等一些情况!
6.P - 性能特性(Property)
性能测试工具的使用,没具体研究过性能测试工具,知道的朋友可以说下你们的经验!
进行压力测试,一点一点的加大数据量,10000条,100000条,1000000条这样进行压力测试!
总结:本人对反向关联和交叉检查这两个测试条件不是很理解,知道的朋友可以留言给我,我会把它补充到文章中去!
相关文章:
走进单元测试一:初认Unit Test
走进单元测试三:实战单元测试
前言:在公司写单元测试已经有两个多月了(思想上有过纠结),说实话有点像赶鸭子上架,在项目收尾的时候才做,很明显它的作用已经是名副其实了,而且还找像我这样的新手来写(一开始我都不怎么熟悉业务流程),所以现在一直努力学好单元测试,写好它,把自己的事做好!
我也是在读程序员修炼之道 - 单元测试这本书之后,再阅读了园子中的各个文章后的感悟把,写出来跟大家一起分享!
电子书下载地址:http://www.kuaipan.cn/file/id_29568238492847284.htm
测试是贯穿整个整个软件工程的始末,做好测试对软件的质量会有一个质的保证,减少查找BUG的工作量,所以作为一个开发者需要了解各种测试流程以及核心思想,然而这次的单元测试是我们开发者必须要具备的技能,这次就让我们走进单元测试(Unit Test)!
1.个人对单元测试的初识
①坚持的开发中编写单元测试,并把它培养成一种习惯!
②写出高效的单元测试,这种能力需要在实践中慢慢积累!
③提高对单元测试的认识高度,把它和编码工作同等对待!
2.什么是单元测试?
通俗讲单元测试就是检查一个函数执行后它的返回结果或者它对系统数据造成的影响(或者其它方面的影响)是否跟你的期望一致,也就是为了证明代码的行为和我期望的一致!
3.我们为什么要使用单元测试?
①最直接的原因是保证我们函数的正确性,如果这个函数在没有保证正确的情况下就被上层代码调用,那么随着项目的深入,调用层次会越来越深,就很容易产生严重的BUG问题,从而增加开发难度,降低开发效率!
②根本原因是减轻我们开发人员的工作量,使我们的工作变的轻松(这只是一个相对说法)!
4.单元测试的内涵
如果把单元测试上升到一定程度后,它可以把我们的代码变的更加完美和简洁!
5.单元测试的本质
请记住一点,不是为工作而编写单元测试,单元测试是方便我们开发人员的,可以使我们的工作变的轻松!
单元测试可以减少我们花在解决不必要的BUG之上(并不是说没有BUG,而是说减少不必要的BUG),而把大量时间专注于业务需求上!
6.函数的行为和预期的一致吗?
如果测试只考虑在正确的环境下造成正确的影响,那么这样的单元测试是不及格的!
做测试就要考虑全面,各个方面都要涉及的到,如:环境因素(也可以是系统所处在的环境),各种异常,边界值等等,所以尽可能的考虑特殊情况,做到做到百密而无一疏(尽自己的最大努力达到)!
7.需要依赖单元测试吗?
答案是肯定的!
当你很自信的认为你写的函数是绝对正确的且没有测试代码做为依据的时候往往会出现意想不到的错误,因为你会疏忽了其它的一些情况,所以编写单元测试来保证我们函数的准确性是非常有必要的!
注:后面会说明测试应该测哪些情况!
8.单元测试干了什么,作用是什么?
①最直接的是保证了函数的正确性(这个大家都知道)!
②还有我们可以根据单元测试来判断此函数是用来干什么的,也就说单元测试类似于一个可执行文档,其它开发人员可以通过看单元测试就会明白你测试的函数是用来干嘛的!
9.如何进行单元测试?
这边我们使用的VS2008,2010自带的单元测试框架!
① 使用VS自带的Unit Test,简单易学!
② 测试要全面!
③ 保证所有测试都能通过,不管旧的还是新的测试代码,都要通过!
④ 保证所有测试没有对系统中任何模块产生影响(这个很重要)!
⑤ 及时运行测试代码,查看运行结果,保证系统的运行正常!
10.不要为没写测试代码找借口
一般情况的看来当你写的一个函数已经不太需要修改了,你就应该编写这个函数的单元测试代码!
大多数情况下开发人员的大量时间都是修改BUG,如果能尽早的做单元测试将会减轻你的工作量(虽然不可能没有BUG,但写Unit Test却对你是有益无害的),即使在以后的时间里出现BUG,我想你能很快的定位产生BUG的位置!
最后写Unit Test千万不要放在项目末期,如果此时写单元测试的人还是一个刚进项目新手的话,写单元测试就是扯淡,这样的方式是不能体现单元测试的核心观念的,而我就刚好处于这个状态,真的很后怕,所以还需要多多加油,努力了解系统流程!
11.如果真的没有时间写单元测试,请思考下面几个问题?
①对于你所编写的代码,你花在调试上面的时间有多少?
②你目前认为你的代码正确无比,但很有可能在系统中却存在严重隐患,你是否花了很多时间来查找这些隐患?
③对于一个新的BUG,你花了多长时间来定位这个BUG在源码中的位置?
总结:随着项目的深入,你的函数会被调用的越来越深,那么特殊情况就会经常发生,万一出现什么情况你将会耗费很多的精力来解决它,另一方面,适当的单元测试代码会很大程度上减少你的工作量,这是经过实践检验的!
总结:好了基本的单元测试思想就这么多了,希望园友们能指点一二,单元测试系列将持续更新中!
相关文章:
走进单元测试二:测试需要从哪些方面着手
1.创建Android项目:AndroidJUnitDemo。
2.创建被测试的类:Apps。包含两个被测试的方法:methodA()、methodB(),代码如下:
package com.android.junit; /** * 被测试的类 */ public class Apps { public void methodA() { System.out.println("---Hello!"); } public void methodB(String name) { System.out.println("---Hello " + name + "!"); } } |
3.创建测试类:AndroidJUnitTester
Android测试类需要继承android.test.AndroidTestCase,代码如下
package com.android.junit; import com.android.junit.Apps; import android.test.AndroidTestCase; /** * Android JUnit测试类 */ public class AppsTester extends AndroidTestCase { Apps a = null; protected void setUp() throws Exception { a = new Apps(); } // methodA()测试方法 public void testMethodA() { a.methodA(); } // methodB()测试方法 public void testMethodB() { a.methodB("Android"); } } |
4.配置AndroidManifest.xml文件
1) <uses-libraryandroid:name="android.test.runner" />
说明:与<activity>位置同级
2) <instrumentationandroid:targetPackage="com.android.junit"
android:name="android.test.InstrumentationTestRunner"
android:label="AndroidJUnit测试" />
说明:与<application>标签同级,targetPackage属性与上面mainfest的package属性内容相同即可 -->
单元覆盖测试
覆盖测试是衡量软件质量的一个重要的指标,是一种”白合”测试方法,覆盖的标准有逻辑覆盖,循环覆盖,基本路径覆盖。其中逻辑测试包括语句覆盖,判定覆盖,条件覆盖,判定/条件覆盖,条件组合覆盖和路径覆盖。
覆盖测试必须拥有程序的规格说明书和程序清单,以程序的内部结构为清单设计测试用例。覆盖测试主要使用在具有高可靠性要求的软件领域(军工企业,航空航天软件,工业控制软件).覆盖测试需要借助一定的工具软件,工具软件具有的功能特点:
分析软件内部接口,帮助定制覆盖策略及设计测试用例
与适当的编译器结合,对被测软件实施自动插桩,以便在其运行过程中生成覆盖信息并收集这些覆盖信息
根据收集的覆盖信息计算覆盖率(Coverage),帮助测试人员找到未被覆盖的软件部分,以改进测试用例,提高覆盖率
对主流开发语言的支持,因为对不同的开发语言来说,测试工具的实现的方式和内容差别是较大的。目前测试工具主要支持的开发语言是:标准C,C++,Visual C++,Java等,
进行动态覆盖测试的三项任务:1.设计测试用例;2.对被测代码进行插桩;3.收集覆盖信息并进行分析。
2.1覆盖测试工具介绍
xUnit基本不支持覆盖测试(白盒测试中重要的内容)。覆盖测试工具实现的技术方式和具体的语言是紧耦合的。不同语言使用的覆盖测试的策略是不一样的。实例如下:
Java(EclEmma+JUnit,JCoverage)
NET(NCover+NUnit)
CppUnit框架很难提供覆盖测试的功能,c/c++测试覆盖测试是通过插桩实现的.C/C++覆盖测试工具:gcov
2.2JUnit下的覆盖工具EclEmma
时至今日,IT人士更加关注测试的重要性。 测试驱动开发(TDD)被证明是一种能有效提高软件质量的方法,测试优先原则,EclEmma2006年入围Eclipse Community Awards Winners 决赛
2.2.1EclEmma
Emma是Sourceforge上的开源项目,某种程度上,EclEmma是Emma的图形开发工具。
Eclipse,OpenJDK,JUnit,Ant,CVS或者Subversion,Emma -- java开发成套开源的解决方案。
Emma的同类产品:Coverlipse,Cobertura,Quilt,JCoverage.
EclEmma的特点:快速的开发和测试周期,非常丰富覆盖信息分析以及非入侵方式。
EclEmma:一个封装Emma覆盖工具的Eclipse的插件。Eclemma的官方地址:http://sourceforge.net/projects/eclemma
EclEmma使用不同颜色标识的源代码测试的情况,绿色-完整执行,红色-根本没有执行,黄色-部分被执行,其视图能够分层显示代码的覆盖测试率。
2.2.2EclEmma环境建立
EclEmma的安装是就是Eclipse插件的安装,共有这样三种方式安装.
1.Eclipse自身的Software Update机制.
选中菜单栏中Help/Install New Softeware,在如下图中对话框中填入插件的站点的信息。
本人是在Ubuntu12.04下使用eclipse的,如果是在windows下的话,具体操作可以参考[2]。
缺点是:当安装了许多插件之后,eclipse的plugins中会充满各种旧的或废弃的插件,不便于更新和管理
2.蛮力法安装
将插件的压缩包解压,然后将其中的plugins和features与eclipse安装包中的同名文件夹合并,这样就可以在Eclipse中使用新插件。实际上第一个方法自动安装也是使用的这种方法,只不过由机器自动改成了手动。缺点同上。
3. links文件
首先从eclemma官方站点下载最新的eclemmaEclipse插件包:http://sourceforge.net/projects/eclemma/files/?source=navbar
在eclipse安装的目录下新建一个links文件夹,将相应的插件(例如eclemma-2.2.0)包拷贝到links文件夹下,并在links下新建一个eclemma.link空白的文本,在起中写入:path=/usr/lib/eclipse/links/eclemma-2.2.0 。注意path=xxx是eclemma在系统中的绝对路径, 由于我使用的Linux系统,在windows下可能是D:\xxx\eclipse\links\eclemma-2.2.0。
重新启动一下eclipse就可以看到eclemma被安装上的标志,最左边的就是Eclemma的图标。
备注: 上面的步骤在windows上已经算是安装完成了,但是在Ubuntu下好像不太行,至少我自己的实践表明是这样的,Ubuntu下还要在加上两步:
进入eclemma-2.2.0文件夹中,在其中建立一个eclipse文件夹并将eclemma目录下其他的文件移到 eclipse目录下
进入eclipse目录,在其中建立一个空的.eclipseextension的隐藏文件(注意"."不可少),并为这个文件加上执行权限。具体的命令如下:
cd /usr/lib/eclipse/links/eclemma-2.2.0/ sudo mkdir eclipse sudo mv * eclipse/ cd eclipse/ vi .eclipseextension chmod +x .eclipseextension |
注意命令中是否需要使用sudo提升权限视情况而定, 如果不是类似 /usr/lib这样的只有root权限才可以操作的目录的话,就不需要使用sudo。
2.2.3 EclEmma的使用
关于Eclemma的使用,网上一堆,不再赘述,推荐一篇文章:
使用Eclemma进行覆盖测试:http://www.ibm.com/developerworks/cn/java/j-lo-eclemma/
2.3C++的覆盖测试工具gcov
gcov是GNU/GCC的工具组件,可以C/C++代码覆盖率的测试工具,使用便捷,不要配置,不是可视界面,只支持CLI命令行界面.如果要使用可视界面,使用Ggcov。
2.3.1gcov环境
gcov是gcc的内部工作组件。利用gcc内部的工具进行代码插桩。
2.3.2gcov测试功能及使用流程
grov的基本功能:可以查看测试时代码执行的覆盖率信息,支持函数覆盖,语句覆盖和分支覆盖等覆盖测试内容,帮助我们分析测试程序中的缺陷。使用该工具还可以查看程序在某个分析执行的频率,进而分析程序的性能。
Gcc的覆盖测试工具gcov,gcov必须和gcc编译器联合使用(需要在程序中插桩,参考百度百科程序插桩的解释)。使用步骤:
在编译时加上”-ftest-coverage-fprofile-arcs”,a.out(设置了-o选项就是输出的文件名)和.gcda文件;
执行./a.out时,根据源程序的语句,条件,路径,边界等因素,以及设计好的输入测试用例数据 ,执行的信息保存到.gcno和.gcda文件中-->gcov-b xxx.c-->xxx.c.gcov(里面显示了每条语句执行的次数,没有执行的语法则以“####”标注)
注意:使用gcov时,每次重新编译,统计数据都会清空,所以项目测试必须在确定的版本上进行。
gcov的其他的选项命令可以查看gcov的在线文档(man gcov)
/*Program: testgcov.c -- interpert the workflow of gcov */ #include<stdio.h> int main(){ printf("Hello,World\n"); } |
编译执行:gcc-o testgcov -ftest-coverage -fprofile-arcs testgcov.c
./testgcov--->执行程序,各种执行语句将会被写到testgcov.gcda和testgcov.gcno中
gcov-b testgcov.c --->testgcov.c.gcov详细的覆盖信息
testgcov.c.gcov 详细信息:
-: 0:Source:testgcov.c -: 0:Graph:testgcov.gcno -: 0:Data:testgcov.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:/*Program: testgcov.c -- interpert the workflow of gcov */ -: 2:#include<stdio.h> 1: 3:int main(){ 1: 4: printf("Hello,World\n"); 1: 5:} |
注:gcov -b查看分支执行的频率,-f查看每个函数执行的情况。
2.3.3gcov覆盖测试应用举例
/*Program:autosell.c -- design the input test case and get the code coverage of program */ #include<stdio.h> void welcome(); void nochange(int num5coins); void getcoin(int *coin); void pushbutton(int *button); void process(int* coin,int* button,int* num5coins); int main(){ int coin=0,button=0,num5coins=2; int i; for(i=0;i<10;++i){ welcome(); nochange(num5coins); getcoin(&coin); pushbutton(&button); process(&coin,&button,&num5coins); } return 0; } void welcome(){ system("clear");//library function,which printf("welcome to this auto selling machine!\n\n"); } void nochange(int num5coins){ if(num5coins == 0) printf("No change now!\n"); } void getcoin(int* coin){ int flagredo; do{ printf("Please pitch your coin(5-5角,10-1元):"); scanf("%d",coin); if(*coin!=5 && *coin!=10){ printf("Wrong coin!Return this coin.\n"); flagredo=1; } else flagredo=0; }while(flagredo); } void pushbutton(int *button){ int flagredo; do{ printf("Please select your drink(1-orange juice,2-beer):"); scanf("%d",button); if(*button!=1 && *button!=2){ printf("Wrong input,Please re-select.\n"); flagredo=1; } else flagredo=0; } while(flagredo); } void process(int* coin,int* button,int* num5coins){ if(*coin == 10 && *num5coins ==0){ printf("No change!\n"); printf("Return 1 yuan coin.\n"); } else { if(*coin == 10){ if(*button ==1) printf("Please take your orange juice.\n"); else printf("Please take you beer\n"); (*num5coins)--; printf("Return 5 jiao coin.\n"); } if(*coin == 5){ if(*button ==1) printf("Please take your orange juice.\n"); else printf("Please take you beer\n"); (*num5coins)++; } } printf("\nPress Enter to continue"); getchar();getchar(); *coin =0; *button=0; } |
编译命令:gcc-o autosell -fprofile-arcs -ftest-coverage autosell.c
执行程序(./autosell)随便输入一些测试用例:
测试结果:
显示总体覆盖率:gcov autosell.c
File'autosell.c'
Linesexecuted:94.55% of 55
Creating'autosell.c.gcov'
显示函数覆盖信息:gcov-f autosell.c
Function'process'
Linesexecuted:90.00% of 20
Function'pushbutton'
Linesexecuted:100.00% of 9
Function'getcoin'
Linesexecuted:100.00% of 9
Function'nochange'
Linesexecuted:75.00% of 4
Function'welcome'
Linesexecuted:100.00% of 4
Function'main'
Linesexecuted:100.00% of 9
File'autosell.c'
Linesexecuted:94.55% of 55
Creating'autosell.c.gcov'
显示分支信息:gcov-b autosell.c
File'autosell.c'
Linesexecuted:94.55% of 55
Branchesexecuted:100.00% of 28
Takenat least once:92.86% of 28
Callsexecuted:87.50% of 24
Creating'autosell.c.gcov'
具体的autosell.c.gcov信息自行查看
参考资料:
[1]软件测试实验指导教程/蔡建平, 清华大学出版社, 2009.11
[2]软件测试学习:测试覆盖率工具EclEmma安装与使用:http://liangruijun.blog.51cto.com/3061169/803473
[3]使用Eclemma进行覆盖测试:http://www.ibm.com/developerworks/cn/java/j-lo-eclemma/
相关文章:
软件测试实验学习笔记系列4--CppUnit
软件测试实验学习笔记系列6--图形界面测试(GUI)
随着移动客户端产品的快速发展,整体的节奏也在加速,无线事业部技术团队要在1-2周时间内确保一个可以上线的版本而不出大的问题,作为测试人员我们希望跟团队一起在最后能响亮的对产品质量喊出“YES”,而不是“NO”。
作为产品质量最后的一道把关者,要如何在短时间的快速迭代中高效保证质量? 质量问题只有在测试环节才被重视和关注么?那么其他环节测试又能做些什么呢?
在最初面对效率与质量,似乎就像鱼和熊掌一样不能兼得的时候,曾不止一次问自己团队类似的问题。在过去的1年里我们也摸索了很多,遇到过很多困难,确实发现了不少问题。最终尝试了这种全过程质量保障的方式,在此想跟大家分享下:为什么我们要跳出单一的测试环节,从整个项目研发的全过程出发去关注效率与质量。
产品在进入开发阶段后除了编码引入的bug外,整个过程(打包-提测-发布-监控)都有可能引入质量和用户体验问题。而这些过程引入的问题让我们的技术人员精力分散,效率降低,进而导致质量有所下降。
举个例子,在打包环节我们曾遇到过这样的问题:
1、以前要测试IOS-App,没有mac设备的童鞋必须要找开发人员线下要测试包;
2、IOS-App想要搞内测版,每次必须整理好工程代码打成压缩包上传;
3、android-App虽没有设备限制大家可以自主打包,但由于太自由了,导致相互测试与调试的包都不够一致,结果要验证多次。
4、android客户端发布前的几百个渠道包,依靠某程序员单机打包得耗上半天;
这些问题不是引入bug的罪魁祸首,但却直接影响了找出bug、修复bug的效率。大家希望能有办法让项目组能尽可能的专注。所以我们将它关注起来,出现了摩天轮上的打包系统(摩天轮即为阿里无线内部以提升质量和效率为主的一个工具化平台)。项目打包通过界面配置和系统自动触发的方式,不但将之前投入到这些工作中的人力解放出来,也避免了以前开发/测试获取包信息对应不一致,排查问题对不上座的现象。目前已支持全集团40多个app产品的系统打包。
当然提测阶段仍然是我们关注做多的一环节,遇到的各类问题更是层出不穷。我们都知道移动应用的测试,除了client端之外,纵向受网络和服务端的干扰,横向受各种厂商型号的设备兼容性影响。
再一起来看看下面我们曾遇到的问题:
1、客户端的测试要如何去便捷覆盖服务端逻辑?
2、如何从客户端更直观的去检测隐藏在后端的逻辑?
3、不同的机型到底要覆盖多少才会有底,具体又要如何去选择覆盖?
4、移动平台的多样性带来的自动化维护成本,如何才能快速投入产出?
5、稳定性方面的测试投入耗时较多,测试任务繁重。
随着移动客户端产品的快速发展,整体的节奏也在加速,无线事业部技术团队要在1-2周时间内确保一个可以上线的版本而不出大的问题,作为测试人员我们希望跟团队一起在最后能响亮的对产品质量喊出“YES”,而不是“NO”。
作为产品质量最后的一道把关者,要如何在短时间的快速迭代中高效保证质量? 质量问题只有在测试环节才被重视和关注么?那么其他环节测试又能做些什么呢?
在最初面对效率与质量,似乎就像鱼和熊掌一样不能兼得的时候,曾不止一次问自己团队类似的问题。在过去的1年里我们也摸索了很多,遇到过很多困难,确实发现了不少问题。最终尝试了这种全过程质量保障的方式,在此想跟大家分享下:为什么我们要跳出单一的测试环节,从整个项目研发的全过程出发去关注效率与质量。
产品在进入开发阶段后除了编码引入的bug外,整个过程(打包-提测-发布-监控)都有可能引入质量和用户体验问题。而这些过程引入的问题让我们的技术人员精力分散,效率降低,进而导致质量有所下降。
举个例子,在打包环节我们曾遇到过这样的问题:
1、以前要测试IOS-App,没有mac设备的童鞋必须要找开发人员线下要测试包;
2、IOS-App想要搞内测版,每次必须整理好工程代码打成压缩包上传;
3、android-App虽没有设备限制大家可以自主打包,但由于太自由了,导致相互测试与调试的包都不够一致,结果要验证多次。
4、android客户端发布前的几百个渠道包,依靠某程序员单机打包得耗上半天;
这些问题不是引入bug的罪魁祸首,但却直接影响了找出bug、修复bug的效率。大家希望能有办法让项目组能尽可能的专注。所以我们将它关注起来,出现了摩天轮上的打包系统(摩天轮即为阿里无线内部以提升质量和效率为主的一个工具化平台)。项目打包通过界面配置和系统自动触发的方式,不但将之前投入到这些工作中的人力解放出来,也避免了以前开发/测试获取包信息对应不一致,排查问题对不上座的现象。目前已支持全集团40多个app产品的系统打包。
当然提测阶段仍然是我们关注做多的一环节,遇到的各类问题更是层出不穷。我们都知道移动应用的测试,除了client端之外,纵向受网络和服务端的干扰,横向受各种厂商型号的设备兼容性影响。
再一起来看看下面我们曾遇到的问题:
1、客户端的测试要如何去便捷覆盖服务端逻辑?
2、如何从客户端更直观的去检测隐藏在后端的逻辑?
3、不同的机型到底要覆盖多少才会有底,具体又要如何去选择覆盖?
4、移动平台的多样性带来的自动化维护成本,如何才能快速投入产出?
5、稳定性方面的测试投入耗时较多,测试任务繁重。
版权声明:本文出自51Testing软件测试网电子杂志第三十期投稿文章中。51Testing软件测试网及相关内容提供者拥有51testing.com内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像,否则将追究法律责任。
单元测试
从软件测试V模型,单元测试是软件测试的基础,四个方面看出单元测试的重要性:
1.时间方面—系统集成节约很多的时间
2.测试效果--单元测试是测试阶段基石,能够发现深层次的问题
3.测试成本--单元测试阶段问题容易发现
4.产品质量--单元测试好坏影响软件产品的质量.
图1:软件测试V模型
软件代码越來越多,软件单元越來越多,单元代码越來越复杂。现代编程语言是的单元测试自动化实现变得可能。XUnit框架是eXtreme Programming(XP) 的核心概念。
单元测试工具的一个重要的功能就是测试自动化,测试自动化的基础就是测试框架。最典型和最流行的单元测试是以JUnit测试框架为基础的xUnit测试框架。
XUnit根据语言不同可以分为:JUnit(Java),CppUnit(C++),DUnit(Delphi),NUnit(.Net),PhpUnit(PHP)
按类型不同可以分为:JFCUnit(GUI测试),HttpUnit(对web应用访问进行测试),JWebUnit(对HttpUnit的抽象封装),StrutsTestCase(基于JUnit的Struts应用测试框架),HtmlUnit
1xUnit单元测试框架
测试软件开发过程中必不可少的一部分,是软件质量保证的重要手段。测试对象:代码,需求文档,设计文档.代码测试:单元测试,集成测试,系统测试和验收测试。eXtremeProgramming—单元测试—xUnit测试框架。TDD以测试为开发中心,XP开发者大多使用开源的测试框架-xUnit家族。
Xunit框架的主要优点是提供了一个自动测试的解决方案。自动测试概念很重要,有了自动测试,我们就可以在开发软件项目中实施持续集成。
1.1xUnit介绍
KentBeck—Smalltalk Sunit—Erich Gamma-- Junit
框架结构表现的概念--->xUnit:一些简单的编写测试的规则--->移植到三十多种语言中(Python,C++,.NET,Html, JavaScript ,Ruby等)
xUnit是一个基于测试的单元测试框架,运行测试用例,反馈测试结果以及记录测试日志的一系列基础软件设施。TestCase,TestSuite,TestResult,xUnit.framework.Assert,TestResult,TestListener,TestRunner.
图2,xUnit 测试框架
Xunit的原则:
先编写测试代码,然后编写符合测试的代码
测试代码不需要覆盖所有的细节,但是需要覆盖主要的功能和可能出错的地方
发现Bug后,首先编写对应的测试用例,然后在进行调试
不断总结出现Bug的原因,对其他代码也编写相应的测试用例
每次编写完代码后,运行所有以前的测试用例,验证对以前的代码的影响,并把这种影响尽早消除
不断维护测试代码,保证代码变动后能通过所有测试
xUnit测试框架包括的4大要素:
测试Fixtures:是一组认定为被测对象或被测程序单元测试成功的预订条件或预期结果的设定。Fixture就是被测试的目标。
测试集(TestSuite):一组测试用例,这些测试用例要求相同的测试Fixtures,从而保证不会出现管理的上的混乱
测试执行:单元测试的执行-setUp()-->执行测试动作-->tearDown()
测试断言(Assert):实际上就是验证被测程序在测试中的行为或状态的一个宏或者函数,断言失败会引发异常,终止测试的执行。
测试用例的编写:初始化Fixture-->按照某个测试功能和流程对Fixture进行操作-->验证结果是否正确-->对Fixture相关的资源进行释放
1.2Junit单元测试工具
JUnit---SourceForge项目---IBMCommon Public License ---官网:www.junit.org
ASP,C++,c#,Effel,Delphi,Perl,PHP,Python,REBOL,Smalltalk,Visual Basic。
xUnit的目标:1.开发人员写测试代码,提供一个测试框架,减少重复性劳动2.创建并保留测试,将测试集中在一起,用已有的测试创建新的测试。
Junit的好处:
可以使测试代码与产品代码分开
针对某个类的测试代码,通过较少的改动便可以将其应用于另一个类的测试
易于集成到测试人员的构建中,JUnit和Ant的结合实施增量开发
Junit是公开源代码的,可以进行二次开发
可以方便的对JUnit进行扩展
Junit测试编写的原则:
简化测试的编写,这种简化包括测试框架的学习和实际测试单元的编写
使测试单元保持持久性
可以利用既有的测试来编写相关的测试
Junit的特征:
使用断言方法来判断期望值和实际值的差异,返回Boolean值
测试驱动设备使用共同的初始化变量或实例
测试包结构便于组织和集成运行
支持图形交互模式和文本交互模式
JUnit共有7个包,核心的包就是junit.framework和junit.runner。Framework包负责整个测试对象的架构,Runner包负责测试驱动。
JUnit有4个重要的类:TestSuite,TestCase,TestResult,TestRunner,前三个属于Framework包,后一个类在不同的环境下是不同的。后一个类在不同的环境中是不同的,文本环境和图形界面
TestResult:负责收集TestCase所执行的结果,客户可以预测的Failure和没有预测的Error。同时负责将测试结果转发给TestListener(该接口由TestRunner集成)进行处理
TestRunner:客户对象调用的起点,负责对整个测试流程进行跟踪。能够显示返回的测试结果,并报告测试的进度
TestSuite:负责包装和运行所有的TestCase
TestCase:客户测试类所要继承的类,负责测试时对客户类进行初始化以及测试方法的调用.
两个重要的接口:Test,TestListener
Test:包含两个方法:run(),countTestCases()-->用于对测试动作特征的提取
TestListener:addError(),addFailure(),startTest(),endTest()-->用于对测试结果的处理以及测试驱动开发的动作的特征的提取。
Junit框架组成:TestCase(测junit.swingui.TestRunner试用例),TestSuite(测试集),TestResult(测试结果描述与记录),TestListener(事件监听者),TestFailure(测试失败),AssertionFailedError.
Junit和Eclipse的集成的处理可以参考《JUnit in Action》
Junit的命令行处理:java junit.textui.TestRunner xxx
1.3JUnit实例
1.3.1. 开发工具:
Eclipse、JUnit、Ant
1.3.2. 编码实现三角形类型判断程序:
class Triangle{ private double a, b, c; public Triangle ( double a, double b, double c ){ this.a = a; this.b = b; this.c = c; } /* 返回值为 1 表示是等边三角形 返回值为 2 表示是等腰三角形 返回值为 3 表示是其他种类三角形 返回值为-1 表示不能构成三角形 */ public int type (){ //to be added } } |
1.3.3、基于 JUnit 设计测试用例,使得满足如下条件
(1)包含不能构成三角形的测试用例
(2)包含构成等边三角形的测试用例
(3)包含构成等腰三角形的测试用例(提示:需要三个测试用例)
(4)包含构成其他种类三角形的测试用例
(5)三角形有一条边是非正数
4、编写 ant 脚本驱动测试用例
包括测试用例执行和测试报表生成
备注:个人使用的JVM是Open JDK6.0,eclipse是3.8 Juno,JUnit使用的是4.11
判断三角形的java程序:
package demo2; public class Triangle { private double a,b,c; public Triangle(double a,double b,double c){ this.a =a; this.b =b; this.c =c; } /* * return value description: * 1.等边三角形 * 2.等腰三角形 * 3.其他三角形 * -1.不是三角形 */ public int type(){ if(isTriangle()){ if(a==b&&b==c){ return 1; }else if(a==b||b==c||c==a){ return 2; }else return 3; } else return -1; } //auxiliary method which is used to predicate weather three number consist of a triangle or not private boolean isTriangle(){ if(Math.abs(a-b)<c&&Math.abs(a-c)<b&&Math.abs(b-c)<a &&(a+b>c)&& (a+c >b)&& (b+c >a) ) return true; return false; } } |
单个的测试用例:
package demo2; import static org.junit.Assert.*; import org.junit.Test; public class TriangleTest { @Test public void testType(){ Triangle triangle = new Triangle(12,12,4); assertEquals(2,triangle.type()); } } |
参数化的测试用例:
package demo2; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(value = Parameterized.class) public class ParameterTestTriangle { private double expected; private double valueOne; private double valueTwo; private double valueThree; @Parameters //Double is a class type,double is a basic type. public static Collection<Object []> getTestParameters(){ return Arrays.asList(new Object [][]{ {2,12.3,13.5,13.5}, {2,10,10,4}, {2,34,5,34}, {1,6.0,6.0,6.0}, {1,23,23,23}, {-1,1.0,4.0,9.0}, {-1,10,3,15}, {3,3.0,4.0,5.0}, {3,12,24,33}, }); } public ParameterTestTriangle(double expected, double valueOne, double valueTwo,double valueThree){ this.expected= expected; this.valueOne= valueOne; this.valueTwo= valueTwo; this.valueThree= valueThree; } @Test public void testType(){ Triangle triangle = new Triangle(valueOne,valueTwo,valueThree); assertSame("期待类型和测试类型不同!",(int)expected,triangle.type()); } } |
结合上述的两个测试用例的测试用例集:
package demo2; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(value = Suite.class) @SuiteClasses(value={ ParameterTestTriangle.class,TriangleTest.class}) public class TestSuite { } |
编写的ant脚本如下:
<project name ="first build.xml" default ="report"> <target name="clean" > <delete dir="dist"/> <delete dir="bin/demo"/> <delete dir="report"/> </target> <target name="compile" depends="clean"> <javac srcdir="src" destdir="bin" includes="demo2/*.java" includeantruntime="true" classpath ="lib\junit.jar"/> </target> <target name="test" depends="compile"> <mkdir dir="report/html"/> <junit printsummary="yes" haltonerror="false" haltonfailure="false" fork="yes" > <formatter type="plain" usefile="false"/> <formatter type="xml"/> <test name="demo2.TestSuite" todir="report"/> <classpath> <pathelement location="bin"/> <pathelement location="lib\junit.jar"/> </classpath> </junit> <junitreport todir="report"> <fileset dir="report"> <include name="TEST*.xml"/> </fileset> <report format="frames" todir="report/html"/> </junitreport> </target> <target name="report" depends="test"> <echo>Hello world</echo> </target> </project> |
运行结果截图:
单个测试用的运行截图
参数化测试用例的运行截图:
测试用例集的运行:
ant脚本运行
1.首先cd 到相应的java project的目录下 cd java project/testSoftware
2. 输入ant 或者ant -f build.xml
3 参看结果:
Buildfile: /home/xiajian/project file/java project/testSoftware/build.xml clean: [delete] Deleting directory /home/xiajian/project file/java project/testSoftware/report compile: test: [mkdir] Created dir: /home/xiajian/project file/java project/testSoftware/report/html [junit] Running demo2.TestSuite [junit] Testsuite: demo2.TestSuite [junit] Tests run: 10, Failures: 0, Errors: 0, Time elapsed: 0.097 sec [junit] Tests run: 10, Failures: 0, Errors: 0, Time elapsed: 0.097 sec [junit] [junit] Testcase: testType[0] took 0.002 sec [junit] Testcase: testType[1] took 0 sec [junit] Testcase: testType[2] took 0 sec [junit] Testcase: testType[3] took 0 sec [junit] Testcase: testType[4] took 0 sec [junit] Testcase: testType[5] took 0 sec [junit] Testcase: testType[6] took 0 sec [junit] Testcase: testType[7] took 0 sec [junit] Testcase: testType[8] took 0.001 sec [junit] Testcase: testType took 0 sec [junitreport] Processing /home/xiajian/project file/java project/testSoftware/report/TESTS-TestSuites.xml to /tmp/null2022323935 [junitreport] Loading stylesheet jar:file:/usr/share/ant/lib/ant-junit.jar!/org/apache/tools/ant/taskdefs/optional/junit/xsl/junit-frames.xsl [junitreport] Transform time: 564ms [junitreport] Deleting: /tmp/null2022323935 report: [echo] Hello world BUILD SUCCESSFUL |
ant脚本在当前项目下生成了一个report文件夹,其中有相应的测试用例执行的报告
备注:关于这里的JUnit的实例的代码,我放到了在百度网盘上创建了一个公共链接,有兴趣的可以研究一下,这实例位于testSoftware/src/demo2的中.网盘连接:http://pan.baidu.com/share/link?shareid=1294418402&uk=556148328
在导入项目时可能出现的问题:
原先我是在OpenJDK 7的环境下编写这个测试程序的,后来从Ubuntu12.10换到Ubuntu12.04LTS后,系统安装的JDK 是Open JDK 6,所以导入项目时出现了java.lang.UnsupportedClassVersionError: demo2/ParameterTestTriangle : Unsupported major.minor version 错误,百度了一下找到了问题的原因:是因为使用高版本的JDK编译的Java class文件试图在较低版本的JVM上运行. 解决方法:
一定要清理干净已经生成的文件
替换成正确的JDK--修JDK的jre以及相的编译器(修改的方法是右击项目属性build path和build compiler中修改)
修改JRE:
修改编译器:
参考资料:
[1]软件测试实验指导教程/蔡建平, 清华大学出版社, 2009.11
[2] JUnit实战(第二版)/Peter Tahchiev等著,王魁译, 人民邮电出版社,2012.4
[3]软件测试v模型的图片来源:http://leochael.blog.163.com/blog/static/1309571200741181733167/
[4]出现的java错误:http://zhidao.baidu.com/question/455404569.html
相关文章:
软件测试实验学习笔记系列2 -- lint,splint的使用
软件测试实验学习笔记系列4--CppUnit