TA的每日心情 擦汗 3 天前
签到天数: 1042 天
连续签到: 4 天
[LV.10]测试总司令
1 测试积点
上篇我们从多个角度分析了选择TestNg的理由,并且也了解了TestNg的运行时生命周期,本篇我们来详细的学习@Test注解,以及各个参数的使用
@Test注解的基本使用
前面我们已经创建了好几个案例,并且每个案例的测试 方法上都加上了 @test 注解,用来标识当前方法是测试方法,而加了 @Test 注解的方法,则是最简单的TestNg测试方法了,现在我们来编写一个最基本的Test:
@Test
public void test(){
System.out.println("test");
}
运行以后即可看到输出了我们想要的结果-->test
@Test注解参数
我们来进入Test注解类,来看看这个注解是如何定义的吧:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface Test {
String[] groups() default {};
boolean enabled() default true;
/** @deprecated */
@Deprecated
String[] parameters() default {};
String[] dependsOnGroups() default {};
String[] dependsOnMethods() default {};
long timeOut() default 0L;
long invocationTimeOut() default 0L;
int invocationCount() default 1;
int threadPoolSize() default 0;
int successPercentage() default 100;
String dataProvider() default "";
Class<?> dataProviderClass() default Object.class;
boolean alwaysRun() default false;
String description() default "";
Class[] expectedExceptions() default {};
String expectedExceptionsMessageRegExp() default ".*";
String suiteName() default "";
String testName() default "";
/** @deprecated */
boolean sequential() default false;
boolean singleThreaded() default false;
Class retryAnalyzer() default Class.class;
boolean skipFailedInvocations() default false;
boolean ignoreMissingDependencies() default false;
int priority() default 0;
}
可以看到Test注解的定义@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR}) 则是代表了此注解可以定义的范围,即可以使用在构造方法、普通方法以及类上。而此注解中定义了大量的参数和方法,我们来看看这些参数分别是做什么的吧:
groups代表组,即可以将同一个功能或者一个连续的操作方法定义为一个组,运行时可以完全按照组来运行
enabled代表是否启用当前方法,默认为true,即为启用当前测试方法
parameters代表参数,可以使用当前注解给测试方法传递参数
dependsOnGroups代表依赖的组,即如果当前方法运行之前,必须要依赖某些方法执行完毕,我们可以将这一部分的方法设置为一个组,可以将这个组设置为依赖组,测试运行的时候会优先运行依赖的组,再去运行当前测试方法
dependsOnMethods代表依赖的方法集合,即如果当前方法运行之前,需要依赖一个方法执行完毕或者传递结果,可以将需要依赖的方法设置进来,测试运行时会按照依赖传递优先级运行
timeOut代表测试方法的运行超时时间,可以设置对应的时间,用来测试当前方法是否能在指定时间内正确执行完毕,单位为毫秒
invocationTimeOut与上一个参数一样都是设置方法的超时时间,但是不同的是,此参数设置的是调用方法的超时时间,即别的方法调用当前方法的时候,指定时间内必须返回,否则视为调用超时
invocationCount代表当前方法允许被调用的次数,此参数可以指定当前测试方法被调用的次数,默认情况下,值为1,代表一次运行中当前方法只会被调用1次
threadPoolSize代表开启多少个线程运行当前测试方法,此参数可以指定线程池的线程数,用来模拟性能测试 和并发测试,默认为0,即不开启单独线程,使用主线程
successPercentage代表当前测试方法运行成功的百分比,一般我们测试过程中,可能会受网络或者性能的影响,导致部分测试不成功,这个时候我们就可以指定此参数,来限制测试成功百分比
dataProvider是指定特殊的内容提供者的方法名
dataProviderClass指定内容提供者所在的类名
alwaysRun指的是当前方法是否无论什么情况都会运行,如果指定为true,则代表即使此方法依赖的方法或者组运行失败,此方法依然会尝试运行,默认为false
description代表当前测试方法的描述说明
expectedExceptions指的是当前测试方法可能会抛出某些异常,可以使用当前参数指定具体的异常,并且将这些异常排除,则被排除的异常出现,当前测试方法依然算成功运行
expectedExceptionsMessageRegExp
expectedExceptionsMessageRegExp指得是通过设置此参数,可以用来匹配测试方法中异常的消息是否一致
suiteName指的是当前测试方法运行的时候指定所属的套件名称
testName指的是当前测试方法运行的时候指定的测试用例 的名称
sequential指的是如果当前参数为true,则当前测试类的所有测试方法都会按照定义的顺序来执行
singleThreaded如果设置为true,则此测试类上的所有方法都保证在同一个线程中运行,即使当前正在使用 parallel =“methods”运行测试。此属性只能在类级别使用,如果在方法级别使用,它将被忽略。注意:此属性曾被称为顺序(现在已经弃用)
retryAnalyzer指的是测试重试机制,即当前测试方法如果失败,可以指定此参数,当失败的时候会按照指定的值进行一定次数的重试
skipFailedInvocations指的是当此方法运行过程中有失败的时候,是否跳过失败的方法继续运行,默认为false
ignoreMissingDependencies
ignoreMissingDependencies指的是找不到指定的依赖的时候是否依然继续执行,默认为false
priority参数指定了当前测试方法的优先级,较低的优先级则会优先运行,最低为0,默认优先级为0
常见的参数实例
接下来,我们通过一些实例来看看常见的注解参数的使用
如何在测试中报告异常
在早期开发中,传统的报告错误的方式就是利用返回码,例如返回-1代表运行失败等,但是此种方式会存在一些问题,例如当调用者拿到返回值以后需要大量的if分支来判断当前成功还是失败,并且每一次不一定有合适的错误码来与之对应,往往会导致错误码与错误并不匹配的情况。所以针对此种情况,后来又出现了通过异常信息来获取具体的错误信息的方式,此种方式把之前的错误码的弊端解决了,但是如果在java 测试过程中优雅的处理测试异常呢?假设此时有个需求:预定一个航班,如果第一次尝试失败,即抛出异常,在junit3中的处理方式为:
@Test
public void shouldThrowIfPlaneIsFull() {
Plane plane = createPlane();
plane.bookAllSeats();
try {
plane.bookPlane(createValidItinerary ( ), null);
fail(MThe reservation should have failed");
}
catch(ReservationException ex) {
//success, do nothing: the test will pass
}
}
try-catch是我们最常见的处理方式,那么如果我们需要测试抛异常的时候作为此条测试用例成功的条件呢?难道每次都靠try-catch来处理吗?在testNG中有更优雅的处理方式:
@Test(expectedExceptions = ReservationException.class) public void shouldThrowIfPlanelsFull() {
plane plane = createPlane();
plane. bookAHSeats ();
plane.bookPlane(createValidItinerary(), null);
}
@Test 注解中设置一个 expectedExceptions 参数,将期望抛出的异常标记出来,如果运行的时候出现了这个异常,那么则视为我们当前测试用例测试成功,比起前面的确优雅了不少。如果这个时候我返回的异常都是RuntimeException,但是我希望根据msg来确定是不是我希望触发的某种异常场景,又该如何呢?这个时候就需要 expectedExceptionsMessageRegExp 参数登场了,只要设置固定的返回异常信息,或者对应的正则表达式即可,最后返回的异常类型匹配上以后,会进行异常信息的正则匹配,只有都匹配上的异常才会视为测试成功
多线程与并发运行测试
早期开发主要使用单线程,依赖机器的硬件性能提升用户体验,但是到了后来多核心算力成为主流,所以几乎所有的程序都支持多线程运行,同样的java程序在单线程测试下往往表现很好,不会出现问题,但是当用户过多往往会发现未知的问题,我们该如何模拟多线程下的测试用例场景呢?别担心,testNg中加入了并发模块支持,从而来证明一些场景下是否为线程安全的。我们先来看一个经典的单例写法:
public class Singleton { private static Singleton instance = null;
public static Singleton getlnstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这是一个经典的单例的写法,看起来似乎能保证Singleton类实例仅被实例化一次,但是事实真的如此吗?我们来模拟一下多线程运行的并发测试:
private Singleton singleton;
@BeforeClass
public void init() {
singleton = new Singleton();
}
@Test(invocationCount = 100, threadPoolSize = 10)
public void testSingleton(){
Thread.yield();
Assert.assertNull(instance);
Singleton p = singleton.getlnstance();
}
可以看到我们在 @Test 注解上设置了 invocationCount 参数,表示此方法被运行100次,并且设置了
threadPoolSize 参数,代表同时开启10个线程运行此方法(无论多少个线程,运行的总数都是100次),我们来看看结果:
==================================================
Concurrent testing
Total tests run: 100, Failures: 5, Skips: 0
==================================================
可以看到,我们的断言居然有5个没有成功的,可见此单例在多线程下的确是不安全的
稳定性测试与可靠性测试
在测试的过程中,往往我们还会遇到需求,比如某一部分接口的调用时间不稳定,需要我们测试一下具体的稳定性或者当前接口的可靠性,这个时候我们就需要 timeOut 和 successPercentage 参数出场了,例如我们有一个接口我们必须保证在10s内调用100次这个接口成功率在98%以上,否则这个接口就是不合格的,测试代码即可如下编写所示:
//测试10s内运行一百次方法调用成功率是否超过98%
@Test(timeOut = 10000, invocationCount = 1000,
successPercentage = 98)
public void waitForAnswer() {
while (1 success) {
Thread.sleep(1000);
}
}
我来回答