目录
【写在前面】
前文链接:Java入门(二):进阶
2. Java进阶
2.1 面向接口编程
2.1.1 定义
(1)在一个面向对象的系统中,系统的各种功能是由许多不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来说不重要。
而各个对象之间的协作关系则成为系统设计的关键。
小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的。
面向接口编程就是指按照这种思想来编程。
(2)面向接口编程中的“接口”,可以说是一种从软件架构的角度,从一个更抽象的层面上,指那种用于隐藏具体底层类和实现多态性的结构部件。
(3)面向接口编程中的“接口”,是一种思想层面的用于实现多态性,提高软件灵活性和可维护性的架构部件,而具体语言中的“接口”是将这种思想中的部件具体实施到代码里的手段。
2.1.2 关于接口的深入理解
(1)接口从更深层次的理解,是定义(规范,约束)与实现(名实分离的原则)的分离。
(2)接口的本身反应了系统设计人员对系统的抽象理解。
(3)接口应该有两类:
第一类是对一个体的抽象,它可对应为一个抽象体(abstract class)。
第二类是对一个体某一方面的抽象,即形成一个抽象面(interface)。
一个抽象体可能有多个抽象面。
抽象体与抽象面是有区别的。
2.1.3 特征
(1)接口中的方法可以有参数列表和返回类型,但不能有任何方法体(jdk8后可有)。
(2)接口中可以包含字段,但是会被隐式的声明为static和final。
(3)接口中的字段只是被存储在该接口的静态存储区域内,而不属于该接口。
(4)接口中的方法可以被声明为public或不声明,但结果都会按照public类型处理。
(5)实现一个接口时,需要将被定义的方法声明为public类型,否则为默认访问类型,Java编译器不允许这种情况。
(6)如果没有实现接口中所有方法,那么创建的依然是一个接口。
(7)扩展一个接口来生成新的接口应使用关键字extends, 实现一个接口使用implements。
2.1.4 接口和抽象类的区别
(1)接口是公开的,里面不能有私有的方法和变量,是让别人使用的。而抽象类是可以有私有方法和私有变量的。
(2)实现接口时,一定要实现接口里定义的所有方法。而实现抽象类时,可以有选择地重写需要用到的方法。一般的应用里,最顶级的是接口,然后是抽象类实现接口,最后才到具体类实现。
(3)接口可实现多继承,但一个类只能继承一个超类,类可以通过继承多个接口实现多重继承。
(4)接口还有 标识(里面没有任何方法,如Remote接口) 和 数据共享(里面的变量全是常量) 的作用。
2.1.5 选择接口还是抽象类
(1)如果要创建不带任何方法定义的成员变量的基类,那么就应该选择接口而不是抽象类。
(2)如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量时,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现,该类就仍是抽象类。
牢记:抽象类是对一种事物的抽象,即抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性,行为,但是接口确实对类局部(行为)进行抽象。
2.1.6 补充
(1)定义方法时,形参可以传类,也可以传接口。
接口不是类,但我们也可以把接口当作类来理解。
抽象类和接口的使用方式很像,接口其实是解决java无法多继承的东西。
抽象类是一个面的关系,里面可以定义好多个点(多方面功能),体现是方法。
接口是一个点的关系,理论上来讲,一个接口就定义一个点相关的方法。
比如:
抽象类File, 它里面有:open, close, write(str), write(str, str), read(str), read(str, str)多个不同的点。
如果是接口,理论上我们需要设计四个接口来实现File抽象类具备的能力:
Iopen
Iclose
Iwrite: write(str), write(str, str)
Iread: read(str), read(str, str)
当接口太多时,我们也可以像抽象类那样来设计接口。
(2)面向接口编程
所谓的接口,就是公有的意思,那么在其里面定义的方法和变量,都是public并且是void的。
接口不是类,但是接口也有继承关系。接口跟抽象类很像,可理解为特殊的类,实现多继承的类。
理论理解清楚后,实际应用时,不一定强制全都按照理论来。
接口链条。。。的过程
正常的接口:a = new A; a. Func1. (白盒,需要有源码,但有时候没有源码)
(3)定义一个接口,然后定义一个方法,方法的形参可以是这个接口。这个方法可用于只接收或存放实现这个接口的对象。
2.2 常用关键字
2.2.1 static
(1)作用:用来修饰是类的方法或类的变量
(2)不带static的方法或变量:
public void xxx (){}表示实例方法,而实例的方法或实例的变量只能通过实例来调用。
(3)有static和无static的区别:
1)在类中声明一个变量或方法,不加static的话,在main函数中调用该方法的话,需要先实例化这个类,得到对象,然后通过对象来调用该变量或方法。
通常,同一个类的实例之间,不相互影响。即改变某个实例中的某个变量的值,不会影响到另一个实例里这个变量的值。
2)如果该变量或方法有static定义的话,那这个变量或方法是属于类的,而不是某个实例。此时可以通过类名或者实例名直接调用该变量或方法。通过类或者某个实例调用该static变量或方法,做更改后,则所有实例中该变量或方法都会相应的变化。 (有点儿全局变量的意思)
简言之:Static变量的变化会影响所有实例,而普通实例变量的变化只会影响本实例。
一般工具类提供的方法,都定义成static,因为可以直接通过类.方法使用。
2.2.2 super (略,前文已解释)
2.2.3 this(略,前文已解释)
2.2.4 final(略,前文已解释)
2.2.5 synchronized(略,前文已解释)
2.3 方法的封装
2.3.1 设计目的
常用的代码进行复用,提升效率。
可以把代码保护起来,不让其他人看源码。
2.3.2 关键
返回值的设计,即调用你这个方法,能返回什么内容?
做方法设计时,最重要的就是,把可能导致变化的内容作为参数提取出来!!!
利用第三方jar包来解决更复杂的问题。
JDK提供的默认api只能解决一些基础问题,我们要解决一些实际问题就需要使用第三方jar包。
Jar包的本质就是更加方便解决实际问题,我们在开发代码的第一想法是有没有现有的jar包可用。
2.3.3 利用jar包解决问题的思路
(1)下载jar包
(2)看官方实例demo, 查看基础功能的实现方式,然后再配合自己的业务去实现功能(自己写,或者引入其他jar包)
2.3.4 常用的jar包
(1)Web selenium
Android 或 iOS 自动化,用Appium
(2)接口httpclient
(3)Commons系列 (http://commons.apache.org )!!!重点
比如:StringUtils,FileUtils (里面几个api对文件的操控非常重要),FilenameUtils
(4)Spring
(5)Spring boot
2.4 HttpClient
(1)接口的本质:启动协议,直接调用后端的代码
基于安全的考虑,UI > 某种协议 > 后端的代码
很多微服务没有ui, 则某种协议 > 后端的代码
(每一种协议都有jar包便于发送,典型的是http, https)
(2)只要有接口文档,只要有jar包有方法,就会调用。
http 是通过httpclient来使用的。
有一些协议是能在网上找到。
有一些协议是公司私有的,稀有的,所以做接口测试的时候,需要公司给出jar包。
Httpclient
客户端 > fiddler > 服务器
Fiddler里设置端口跟客户端(浏览器一致),才能抓住。
补充:有的后端代码有协议,有的没有。如果没有,就不能通过协议来调用后端代码,需要做白盒测试。通常由开发人员来做。
2.5 Java测试框架
2.5.1 Junit3
(1)定义
JUnit是由Eric就 Gamma和Kent Beck编写的一个回归测试框架(regression testing framework)。
JUnit是一套框架,继承TestCase类,就可以用JUnit进行自动化测试了。
Junit测试是程序员测试,即白盒测试。程序员知道被测试软件如何完成功能,完成什么功能。
(2)JUnit3.8
使用JUnit3.x版本进行单元测试时,测试类必须要继承于TestCase父类。测试方法需要遵循:
public的。
void的。
无方法参数的。
方法名称必须以test开头。
说明:
1)不同的Test Case之间一定要保持完全的独立性,不能有任何的关联。
2)要掌握好测试方法的顺序,不能依赖于测试方法自己的执行顺序。
3)setUp()是执行每个用例之前都要执行的方法。
tearDown()是执行每个用例之后都要执行的方法。
4)JUnit3的用例执行顺序是:
setUp() > case1 > tearDown() setUp() > Case2 > tearDown()...
5)JUnit3提供了一些辅助函数,他们用来帮助我们确定被测试的方法是否按照预期的效果正常工作,通常,这些辅助函数被称为断言。
(3)常用断言
assertEquals
函数原型:assertEquals([String message], expected, actual)
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
expected: 期望值,通常是用户指定的内容
actual:是被测试的代码返回的实际值
assertTrue
函数原型:assertTrue([String message], Boolean condition)
该断言用来验证给定的布尔型值是否为真,假如结果为假,则验证失败。
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
condition: 待验证的布尔型值
assertFalse
函数原型:assertFalse([String message], Boolean condition)
该断言用来验证给定的布尔型值是否为假,假如结果为真,则验证失败。
assertNull
函数原型:assertNull([String message], Object object)
该断言用来验证给定的对象是否为null,假如不为null,则验证失败。
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
object: 待验证的对象
assertNotNull
函数原型:assertNotNull([String message], Object object)
该断言用来验证给定的对象是否为非null,假如为null,则验证失败。
assertSame
函数原型:assertSame([String message], expected, actual)
该断言用来验证exptected参数和actual参数所应用的是否是同一个对象,假如不是,则验证失败。
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
expected: 期望值
actual:是被测试的代码返回的实际值
assertNotSame
函数原型:assertNotSame([String message], expected, actual)
该断言用来验证exptected参数和actual参数所应用的是否是同一个对象,假如所引用的对象相同,则验证失败。
Fail
函数原型:Fail([String message])
该断言会使测试立即失败,通常用在测试不能到达的分支上(比如异常)
参数说明
message: 可选消息,假如提供,将会在发生错误是报告这个消息
注意!!!
Junit中, assertEquals()和assertSame(0的区别:
1)提供的接口数量不完全相同:
assertEquals支持boolean, long, int等 Java primitiveType变量
assertSame仅支持Object
2)比较的逻辑不同,结果可能不同:
assertEquals利用被比较对象提供的比较逻辑来进行比较。
assertSame是对象直接比较。
使得同样的条件下,两者的运行结果不一定相同。
(4)环境搭建(Eclipse中)
首先,创建项目,准备好要被调用执行的类(真正实现业务的类)。
其次,选中 Project > 右键 Properties > Java Build Path > Libraries > Junit,选JUnit3
然后,创建一个Junit Case, new > JUnit Test Case。选择“Class Under test”可以选择要进行单元测试的类。
接着,根据需求编辑单元测试类。
最后,运行单元测试类。
(5)代码示例(略)
2.5.2 Junit4的应用
(1)概述
JUnit4是JUnit框架有史以来的最大改进。
目标是利用Java5的Annotation(注解)特性简化测试用例的编写。
(2)新特性
1)使用junit4.x版本进行单元测试时,测试类不用继承TestCase父类。因为, junit4.x全面引入了Annotation来执行我们编写的测试。
2)junit4.x版本,引用了注解的方式进行单元测试。
3)junit4.x版本我们常用的注解:
@Before:与junit3.x中的setUp()方法功能一样,在每个测试方法之前执行。
@After:与tearDown()方法一样。
@BeforeClass:在所有方法执行之前执行
@AfterClass:在所有方法执行之后执行
说明:
1)JUnite4的执行顺序:
@BeforeClass > @Before > @Test1 > @After > @Before > @Test2 > @After ...... > @AfterClass
2)@Test(timeout = xxx):
设置当前测试方法在一定时间内运行完,否则返回错误
3)@Test(expected = Exception.class):
设置被测试的方法是否有异常抛出。抛出异常类型为: Exception.class
4)@Ignore:
注释掉一个测试方法或者一个类,被注释的方法或类,不会被执行。
(3)环境搭建
首先,创建项目,准备好要被调用执行的类(真正实现业务的类)。
其次,选中 Project > 右键 Properties > Java Build Path > Libraries > Junit,选JUnit4。
然后,创建一个Junit Case, new > JUnit Test Case,选择“Class Under test”可以选择要进行单元测试的类。
接着,根据需求编辑单元测试类。
最后,运行单元测试类。
(4)示例代码:略
(5)JUnite4的高级应用
1)Junit4的执行逻辑
JunitBuilder编译器会构造Test运行器,如BlockJUnit4ClassRunner, 其会通过自己的构造器,把当前测试类传到runner里。
运行ParentRunner.run()方法,run方法会获取测试类的Description信息, Description信息会包含测试类的信息,然后执行classBlock(RunnerNotifier), 这个方法获取Statement信息,首先构造一个childrenInvoker, 然后在Statement的evaluate()方法调用runChildren()方法,用来真正的执行test方法,这个步骤会等到测试真正执行后开始做。现在是先获取Statement会处理三种注解: @Before, @After, @Rule, 把标注这些注解的方法分别放在集合里,以便后面能处理这些方法。
准备工作后,执行步骤2里从classBlock(RunNotifier)获取到的Statement的evaluate()方法,这个方法用来对Statement来说是开了一个口,用户可以自定义Statement的方法,不过在这里,evaluate()主要用来执行步骤2调用的runChildren()方法。
runChildren()方法的作用是:遍历测试类的所有测试方法(getFilteredChildren),开启线程调度fScheduler, 调度线程给每一个测试方法,然后执行Runnable.run()方法,让 测试执行器可以执行测试类的测试方法(runChild)
执行测试方法首先会判断方法是否含有@Ignore注解,如果有,则忽略它的测试。如果没有,那么执行runLeaf()方法。这个方法是真正我们执行测试的开始。
RunLeaf(Statement, Description, RunNotifier), 首先Statement是methodBlock(FrameworkMethod) 产生, FrameworkMethod存着当前要执行的测试方法,在这个方法里,用了反射机制。这时Junit4会构造RunRules,会把Statement, Desctription apply()到MethodRules, TestRule进去,也就是,我们在外部构造的带有@Rule, @Before, @After等就在这里执行。
如:
@Rule
public final TestRule testRule = new CommonRule();
此时,我们就可以在我们新构建的CommonRule类里面的apply()方法,做一些对test的预处理,比如预处理连接数据库字符串,读取方法上元数据的信息等。
然后就真正执行RunLeaf(Statement, Description, RunNotifier),通过传进来的参数构建 EachTestNotifier, 执行fireTestStarted, 然后再打开对话, statement.evaluate(), 这个执行过程中,会用到InvokeMethod类,然后调用InvokeMethod.evaluate(),最后调用Method.invoke()方法真正执行了test实体,测试开始了。
备注:真正开始测试前,我们可以用apply()进行一些处理,同样的,我们可以在apply()方法中,创建用户Statement, 这样就能在evaluate()方法中,做一些操作,比如执行脚本,日志等。
2)Runner 运行器
当把测试代码提交给JUnit框架后,框架如何运行代码?通过Runner.
在JUnit中有很多个Runner, 它们负责调用测试代码,每一个Runner都有各自的特殊功能,要根据需要选择不同的Runner来运行测试代码。
JUnit中有一个默认的Runner, 如果我们没有指定,那么系统自动使用默认Runner来运行代码。
举例:下面两段代码含义是一样的(略)
从上述例子可以看出,
要指定一个Runner,需要使用@RunWith标注,并且把你所指定的Runner作为参数传递给它。
另,@RunWith是用来修饰类的,而不是用来修饰函数的。
只要对一个类指定了Runner, 那么这个类中的所有函数都被这个Runner来调用。
3)新断言 assertThat
一般匹配符
(1) assertThat(testedNumber, allOf(greaterThan(8),lessThan(16)));
allOf匹配符标识如果接下来的所有条件必须都成立测试才通过,相当于“与”
(2) assertThat(testedNumber, anyOf(greaterThan(8),lessThan(16)));
anyOf匹配符标识如果接下来的所有条件只要有一个成立则测试通过,相当于“或”
(3) assertThat(testedNumber, anything());
anything匹配符表明无论什么条件,永远为true
(4) assertThat(testedString, is("developerWorks"));
is匹配符表明如果前面待测的object等于后面的object, 则测试通过
(5) assertThat(testedString, not("developerWorks"));
not匹配符和is匹配符正好相反
字符串相关匹配符
(1) assertThat(testedString, containsString("developerWorks"));
(2) assertThat(testedString, endsWith("developerWorks"));
(3) assertThat(testedString, startsWith("developerWorks"));
(4) assertThat(testedValue, equalTo(expectedValue));
(5) assertThat(testedString, equalToIgnoringCase("developerWorks"));
数值相关匹配符
(1) assertThat(testedDouble, closeTo(20.0, 0.5));
closeTo匹配符表明如果所测试的浮点数在20.0+-0.5范围之内,则测试通过
(2) assertThat(testedNumber, greaterThan(16.0));
(3) assertThat(testedNumber, lessThan(16.0));
(4) assertThat(testedNumber, greaterThanOrEqualTo(16.0));
(5) assertThat(testedNumber, lessThanOrEqualTo(16.0));
collection相关匹配符
(1) assertThat(mapObject, hasEntry("key", "value"));
hasEntry匹配符表明如果测试的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项,则测试通过
(2) assertThat(iterableObject, hasItem("key", "value"));
hasItem匹配符表明如果测试的迭代对象iterableObject含有元素element,则测试通过
(3) assertThat(mapObject, hasKey("key"));
hasKey匹配符表明如果测试的对象含有键值key,则测试通过
(4) assertThat(mapObject, hasValue("key"));
hasValue匹配符表明如果测试的对象含有元素值value,则测试通过
备注:使用assertThat,要引用下面语句:
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.CoreMatchers.*
4) 参数化测试
(1)代码例子:略
(2)分析
第一步:首先要为这种测试专门生成一个新的类,而不能与其他测试共用同一个类。这里我们定义了一个SquareTest类。然后,要为这个类指定一个Runner, 而不能使用默认的Runner了,因为特殊的功能要用特殊的Runner。@RunWith(Parameterized.class)这条语句就是为这个类指定了ParameterizedRunner。
第二步,定义了一个待测试的类,并且定义了两个变量,一个用于存放参数,一个用于存放期待的结果。然后,定义测试数据的结合,就是上述的data()方法,该方法可以任意命名,但是必须使用@Parameters标注进行修饰。
这里需要注意其中的数据:是一个二维数组,数据两两一组,每组中的这两个数据,一个是参数,一个是你预期的结果。比如第一组{2,4}中:2是参数,4是预期结果。这两数据顺序无所谓。
然后,是构造函数,其功能就是对先前定义的两个参数进行初始化。这里要注意参数的顺序,要和上面的数据集合的顺序保持一致。(比如都是先参数后结果)
第三步,写一个简单的测试用例。。。
5) 打包测试 Suite
JUnit提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大方便了我们的测试工作。
代码举例:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(...{CalculatorTest.calss, SquareTest.calss})
public class AllCalculatorTests...{
}
解释:
第一个注解:需要一个特殊的Runner, 因此需要向@RunWith注解传递一个参数Suite.calss。
第二个标注解:用来表明这个类是一个打包测试类,把需要打包的类作为参数传递给该注解即可。
有了这两个注解之后,就已经完整的表达了所有的含义,因此下面的类无关紧要,随便起个类名,内容为空。
6) 控制用例执行顺序
定义:JUint是通过@FixMethodOrder注解(annotation)来控制测试方法的执行顺序的。@FixMethodOrder注解的参数是org.junit.runners.MethodSorters对象
分类:在枚举类org.junit.runners.MethodSorters中定义三种顺序类型:
MethodSorters.JVM:按照JVM得到的方法顺序,即代码中定义的方法顺序。
MethodSorters.DEFAULT:默认的顺序,以确定但不可预期的顺序执行
MethodSorters.NAME_ASCENDING:按方法名字母顺序执行
7) Rule
定义:JUnit4中包含两个注解 @Rule和@ClassRule,用于修饰Field或返回Rule的Method。
Rule是一组实现了TestRule接口的共享类,提供了验证,监视TestCase和外部资源管理等能力。
简单的说,就是提供了测试用例执行过程中一些通用功能的共享的能力,使我们不必重复编写一些功能类似的代码。JUnit用于标注Rule的注解包括@Rule和@ClassRule, 区别在于作用域不同。
@Rule的作用域是测试方法,@ClassRule则是测试Class。
JUnit提供了以下几个Rule实现,必要时也可以自己实现Rule:
Verifier:验证测试执行结果的正确性
ErrorCollector:收集测试方法中出现的错误信息,测试不会中断,如果有错误发生,测试结束后会标记失败
ExpectedException:提供灵活的异常验证功能
Timeout:用于测试超时的Rule
ExternalResource:外部资源管理
TemporaryFolder:在JUnit的测试执行前后,创建和删除新的临时目录
TestWatcher:监视测试方法生命周期的各个阶段
TestName:在测试方法执行过程中提供获取测试名字的能力。
代码举例:自定义一个rule,实现循环执行一个方法。。。(略)
2.5.3 Junit3和Junit4的区别
略
2.5.4 TestNG
(1)定义
TestNG是一个开源自动化测试框架,类似于JUnit, 但又不是JUnit的扩展,灵感来源于JUnit和NUnit, TestNG涵盖了整个核心的JUnit4功能,还引入一些新功能。
TestNG表示下一代(Next Generation),目的是由于JUnit,尤其是用于测试集成多类时。
很大程度借鉴了Java注解来定义测试。
TestNG和JUnit4最大的不同就是:TestNG通过配置文件对用例进行运行时的配置管理。另外还有依赖测试和多线程测试。
(2)TestNG和JUnit4的相同点:
使用annotation,并且大部分annotation相同。
都可以进行单元测试(Unit Test)。
都是针对Java的测试工具。
(3)TestNG和JUnit4的不同点:
JUnit只能进行单元测试,TestNG可以进行单元测试,功能测试,端到端测试,集成测试。
TestNG需要一个额外的xml配置文件,配置测试的class, method甚至package。
TestNG的运行方式更灵活:命令行,ant和IDE。JUnit只能使用IDE。
TestNG的annotation更加丰富。
如果测试套件运行失败,JUnit4会重新运行整个测试套件。TestNG运行失败时,会创建一个xml文件说明失败的测试,利用这个文件执行程序,就不会重复运行已经成功的测试。
(4)TestNG比JUnit4灵活性的体现:
1)JUnit4必须把@BeforeClass修饰的方法声明为public static, 这就限制了该方法中使用的变量必须是static, 而TestNG中的@BeforeClass修饰的方法可以跟普通函数一样。
2)JUnit4测试的依赖性非常强,测试用例间有严格的先后顺序,前一个测试不成功,后续所有的依赖测试都会失败。TestNG利用@Test的dependsOnMethods属性来应对测试依赖性问题,某个方法依赖的方法失败,它将被跳过,而不是标记为失败。
3)对于n个不同参数组合的测试,JUnit4要写n个测试用例,每个测试用例完成的任务基本是相同的,只是受测方法的参数有所改变。TestNG的参数化测试只需要一个测试用例,然后把所需要的的参数加到TestNG的xml配置文件中。好处是参数和测试代码分离,直接修改参数即可,并且只需修改参数而已,不需要重新编译测试代码。
4)为了测试无法用String或原语值的复杂参数化类型,TestNG提供的@DataProvider使他们映射到某个测试方法。
5)Junit4的测试结果通过Green/red bar体现,TestNG的结果,除了Green/Red bar, 还有Console窗口和test-output文件夹,对测试结果的描述更加详细,方便定位错误。
(5)环境搭建
首先,安装插件 TestNG for Eclipse, 安装后重启。
其次,创建一个TestNG用例,设置用例选用的标签。得到框架的基础代码(跟JUnit4很像)
然后,完成代码编写。
最后,运行用例:右键>Run As>TestNG Test
(6)配置文件
1)xml配置文件:
TestNG通过配置文件来完成Suite功能,即:一次运行多个测试类,在项目根目录testng下,编辑xml文件,testng.xml,xml文件名自定义。举例(略)。
在配置好的xml文件中,右键选择TestNG Suite,完成多用例的执行
2)配置文件中的常用标签
<suite> 套件,根标签,通常由几个<test组成>
属性
name:套件的名称,必须属性
verbose:运行的级别或详细程度
parallel: 是否运行多线程来运行这个套件
thread-count:如果启用多线程,用于指定开户的线程数
annotations:在测试中使用的注释类型
time-out:在本测试中的所有测试方法上使用的默认超时时间
<test> 测试用例,name为必须属性。选择测试脚本可以从包,类,方法三个层级进行。
<packages> 用例中包含的包,包中所有的方法都会执行,子标签为<package name="packageName">
<package> 测试包,name为必须属性
<classes> 用例中包含的类,子标签为<classname="className">
<class> 测试类,其中属性name为必须属性
<methods> 指定测试类中包含或排除的方法,子类为 <include> <exclude>
<include> 指定需要测试的方法,name为必须属性
<exclude> 指定不需要测试的方法,name为必须属性
<groups> 指定测试用例中要运行或排除运行的分组,字标签为 <run>, <run>下包含<include>, <exclude>标签, 他们的name指定运行,不运行的分组
3)通过java命令执行suite
方法:使用命令 java -cp 完成suite的运行
java -cp 和 -classpath一样。是指定类运行所依赖其他类的路径,通常是类库和jar包,需要全路径到jar包,多个jar包之间的连接符为: window上分号“;”, Linux上冒号“:”
如:
java -cp 你的testngjar包所在目录和(到jar文件名);你要测试类的bin目录
org.testng.TestNG 你的配置文件所在目录\testng.xml
4)测试用例常用注解
@BeforeSuite:在该套件的所有测试都运行在注释的方法之前,仅运行一次(套件测试是一起运行的多个测试类)
@AfterSuite:在该套件的所有测试都运行在注释方法之后,仅运行一次
@BeforeClass:在调用当前类的第一个测试方法之前运行,仅运行一次
@AfterClass:在调用当前类的第一个测试方法之前=后运行,仅运行一次
@BeforeTest:注释的方法将在属于<test>标签内的类的所有测试方法运行之前运行
@AfterTest:注释的方法将在属于<test>标签内的类的所有测试方法运行之后运行
@BeforeGroups:配置方法将在之前运行组列表。此方法保证在调用属于这些组中的任何一个的第一个测试方法之前不久运行。
@AfterGroups:配置方法将在之前运行组列表。此方法保证在调用属于这些组中的最后一个测试方法之后不久运行。
@BeforeMethod:注释方法将在每个测试方法之前运行
@AfterMethod:注释方法将在每个测试方法之后运行
@Test:将类或方法标记为测试的一部分
@Parameters:描述如何将参数传递给@Test方法
@DataProvider:标记一种方法来提供测试方法的数据。注释方法必须返回一个Object[][],其中每个Object[]可以被分配给测试方法的参数列表。要从该DataProvider接收数据的@Test方法需要使用与此注释名称相等的dataProvider名称
@Factory:将一个方法标记为工厂,返回TestNG将被用作测试类的对象。该方法必须返回Object[]
@Listeners:定义测试类上的侦听器
5)常用断言
assertEquals
函数原型:assertEquals([String message], expected, actual)
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
expected: 期望值,通常是用户指定的内容
actual:是被测试的代码返回的实际值
assertTrue
函数原型:assertTrue([String message], Boolean condition)
该断言用来验证给定的布尔型值是否为真,假如结果为假,则验证失败。
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
condition: 待验证的布尔型值
assertFalse
函数原型:assertFalse([String message], Boolean condition)
该断言用来验证给定的布尔型值是否为假,假如结果为真,则验证失败。
assertNull
函数原型:assertNull([String message], Object object)
该断言用来验证给定的对象是否为null,假如不为null,则验证失败。
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
object: 待验证的对象
assertNotNull
函数原型:assertNotNull([String message], Object object)
该断言用来验证给定的对象是否为非null,假如为null,则验证失败。
assertSame
函数原型:assertSame([String message], expected, actual)
该断言用来验证exptected参数和actual参数所应用的是否是同一个对象,假如不是,则验证失败。
参数说明:
message: 可选消息,假如提供,将会在发生错误是报告这个消息
expected: 期望值
actual:是被测试的代码返回的实际值
assertNotSame
函数原型:assertNotSame([String message], expected, actual)
该断言用来验证exptected参数和actual参数所应用的是否是同一个对象,假如所引用的对象相同,则验证失败。
assertArrayEquals
函数原型:assertArrayEquals([String message], expectedArray, resultArray)
说明
断言预期数组和结果数组相等。
数组的类型可能是int, long, short, char, byte or java.lang.Object
6)依赖
TestNG的依赖主要包括:方法的依赖和组的依赖。
如果希望不论前置方法是否成功运行,有依赖的后置方法都会得到运行,那么需要在TestNG方法注解上,加一个alwaysRun=true属性。
dependsOnMethods
在被依赖的方法运行完成之后运行当前方法,如果依赖方法测试不通过,那么当前方法也不会继续运行了,依赖的方法可以有多个。
dependsOnGroups
在被依赖的组运行完成之后运行当前组,如果依赖组中的方法没有测试能过,那么当前的方法也不会继续运行了,依赖组可以有多个。
7)用例并发测试
TestNG有多种并发方式:test级的并发,class级的并发,方法的并发,实例的并发
tests级别
不同test tag下的用例可以在不同的线程执行,相同的test tag下的用例只能在同一个线程中执行。 <suite name="Testing Parallel Test" parallel="tests" thread-count="5">
classes级别
不同class tag下的用例可以在不同的线程执行,相同的class tag下的用例只能在同一个线程中执行。 <suite name="Testing Parallel Test" parallel="classes" thread-count="5">
methods级别
所有用例都可以在不同的线程去执行
<suite name="Testing Parallel Test" parallel="methods" thread-count="5">
instance级别
<suite name="My suite" parallel="instances" thread-count="5">
解释:
实践中,很多时候我们在测试类中通过dependOnMethods/dependsOnGroups方式,给很多测试方法的执行添加了依赖,以达到期望的执行顺序。
TestNG能在多线程情况下依然遵循既定的用例执行顺序去执行。
有时我们需要对一个测试用例,比如HTPP接口,执行并发测试,即一个接口的反复调用。
在@Test标签中指定threadPoolSize和invocationCOunt可以实现该需求。
threadPoolSize表明用于该调用该方法的线程池容量。
invocationCount表示该方法总计需要被执行的次数。
8)用例失败重试
用例失败重试即用例失败后重新执行用例,需要重写IRetryAnalyzer接口中的retry方法,使用例失败重试3次。
示例代码:略
9)自定义监听器
Testng虽然提供了不少强大的功能和灵活的选项,但不能解决所有的问题。而监听器就是用来定制额外的功能来满足我们的需求。
以下是TestNG提供的几种监听器:
IAnnotationTransformer
IAnnotationTransformer2
IHookable
IInvokedMethodListener
IMethodInterceptor
IReporter
ISuiteListener
ITestListener
重点介绍:ITestListener
如果要在测试方法执行成功,失败或者跳过时指定不同后续行为,就可以用这个监听器。
该监听器要求实现的方法如下:
onTestSuccess(ITestResult):void
onTestFailure(ITestResult):void
onTestSkipped(ITestResult):void
onTestFailedWithTimeout(ITestResult):void
onTestFailedButWithinSuccessPercentage(ITestResult):void
getAllTestMethods():ITestNGMethod[]
onStart(ITestContext):void
onFinish(ITestContext):void
可以重写这些方法,来实现我们的具体需求。示例代码:略
这样大家就可以在测试运行的过程中加入自己想添加的功能了。比如可以把测试结果写入到自定义的测试报告中等等。
补充:
(1)Junit3 > junit4(testng的出现,打破了junit4的垄断) > junit5
(2)关于方法的顺序执行,建议是定义好规则。再加上框架自带的顺序,结合来控制顺序。
比如
test1_场景1
test2_场景2......
(3)注意:断言后面的代码,一旦断言失败,后面的代码将不被执行。