Software Testing - TestNG必知必会

分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击http://www.captainbed.net

TestNG有几个显著且好用的特点。

  • 大名鼎鼎的注解功能

  • 测试组的概念

  • 多线程测试

  • 支持依赖测试方法,并行测试,负载测试,局部故障

那TestNG有以上这些特点,怎么集成进你的框架并使用呢?

第一步:你需要确认已经配置了Java环境。

第二步:

1、项目中配置了maven,则引入以下依赖就行

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.14.3</version>
</dependency>

2、如果没有maven,则可以直接引入TestNG.jar即可

扫描二维码关注公众号,回复: 12763346 查看本文章

第三步:编写测试业务逻辑,在代码中插入TestNG的注解

第四步:在TestNG对应的配置文件testng.xml或build.xml中配置测试信息

第五步:运行TestNG即可

前面一直提到注解的好处,那我们就从运用最广泛的@Test注解开始说起。被@Test标注的方法或者类则是我们对应的一条测试用例。如下面的代码显示,runTest1、runTest2分别是我们自己写的两条测试用例。


package com.tester;

import org.testng.annotations.Test;

public class TestInterface {

    @Test
    public void runTest1() {
        System.out.println("@Test - runTest1");
    }

    @Test
    public void runTest2() {
        System.out.println("@Test - runTest2");
    }
}

@Test就这么简单?No,它的精华在于它的以下几个参数。

  • groups:组测试,一个test属于哪一个组,可以跑一个特定组的所有test

  • dependsOnMethods、dependsOnGroups:依赖测试,一个test依赖于另一个test的执行结果

  • expectedExceptions:异常测试

  • dataProvider:参数化测试,将参数传入该test

  • enabled:忽略测试,不执行该test

  • timeOut、threadPoolSize、invocationCount、successPercentage:并发测试,设置并发测试的各种参数

  • alwaysRun:如果为true的话,不管怎样都会运行

这次先介绍TestNG的一个创新,组测试。它不存在于JUnit框架,它允许你执行一个测试组内的所有测试用例。你不仅可以声明一个测试用例属于哪个组,你也可以指定一组包含其他组。然后,TestNG可调用和要求包括一组特定的群体(或正则表达式),而排除另一个集合。这就给了我们最大的灵活性,如何分区测试,如果想运行两套不同的测试背靠背,不需要重新编译任何东西,只需要运行不同的组即可。


package com.tester;

import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;

public class TestGroup {

    @Test(groups = "appium-test")
    public void runAppium() {
        System.out.println("runAppium()");
    }

    @Test(groups = "appium-test")
    public void runAppium1() {
        System.out.println("runAppium1()");
    }

    @Test(groups = "interface-test")
    public void testConnectOracle() {
        System.out.println("testConnectOracle()");
    }

    @Test(groups = "interface-test")
    public void testInterface() {
        System.out.println("testInterface()");
    }
}

以上代码,我们可以看到,声明了2个测试组appium-test、interface-test ,每个测试组下面分别有2个测试用例。还有如下代码所示,直接将整个类归为一个测试组的情况。


package com.tester;

import org.testng.annotations.Test;

@Test(groups = "selenium-test")
public class TestSelenium {

    public void runSelenium() {
        System.out.println("runSelenium()");
    }

    public void runSelenium1() {
        System.out.println("runSelenium()1");
    }
}

那怎么运行呢?上面提到需要配置对应的testng.xml,我们将对应的testng.xml写成如下。


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="TestAll">
    <!-- 只跑selenium-test这个测试组 -->
    <test name="selenium">
        <groups>
            <run>
                <include name="selenium-test" />
            </run>
        </groups>

        <classes>
            <class name="com.tester.TestSelenium" />
            <class name="com.tester.TestGroup" />
        </classes>
    </test>
</suite>

当然,一个测试方法或者一个类,也可以同时属于多个组。

@Test(groups = {"appium","uiautomator"})
public void testUI() {
    System.out.println("testUI");
}

接着来说@Test的这个注解的参数。

@Test除了groups这个参数外,还有一个更重要的两个参数dependsOnMethods、dependsOnGroups,拥有这两个参数,我们就可以做依赖测试。

何为依赖测试?有时候需要在特定的顺序下调用测试案例,或分享一些数据和方法之间的状态。这时候我们可以用dependsOnMethods或dependsOnGroups声明,被注解的方法只有在被依赖的方法执行成功后执行。

package com.tester;

import org.testng.annotations.Test;

public class App {

    @Test(dependsOnMethods = { "method1" })
    public void method2() {
        System.out.println("This is method 2");
    }
    
    @Test
    public void method1() {
        System.out.println("This is method 1");
    }

}

method2的注解中声明了dependsOnMethods = { "method1" },即method2必须在method1成功运行通过后才会执行,如果method1执行失败,method2将不会执行。在我们的接口自动化框架中,比如我们的一些接口依赖于登录态,那我们的依赖测试就能发挥大大的作用了。我们可以将这些接口的方法声明依赖登录接口,等登录接口成功后,我们拿到了登录态,才去执行下面的接口。

上面讲到了组测试,将组测试和依赖测试结合,那就是dependsOnGroups了。

package com.tester;

import org.testng.annotations.Test;

// 声明这个类中的方法都是deploy组的
@Test(groups = "deploy")
public class TestServer {
    @Test
    public void deployServer() {
        System.out.println("Deploying Server...");
    }

    // 只有deployServer通过后才跑
    @Test(dependsOnMethods = "deployServer")
    public void deployBackUpServer() {
        System.out.println("Deploying Backup Server...");
    }
}
package com.tester;

import org.testng.annotations.Test;

public class TestDatabase {

    //属于db组
    //只有deploy组全部通过后才跑
    @Test(groups="db", dependsOnGroups="deploy")
    public void initDB() {
        System.out.println("This is initDB()");
    }

    //属于db组
    //只有initDB方法通过后才会跑
    @Test(dependsOnMethods = { "initDB" }, groups="db")
    public void testConnection() {
        System.out.println("This is testConnection()");
    }
}

上面代码中,就是dependsOnMethods和dependsOnGroups的混合使用。TestServer这个类中的所有方法属于deploy组,initDB方法只有当deploy组全部通过后才会跑,testConnection只有在initDB通过后才会跑。

说完依赖测试,我们来说异常测试expectedExceptions。如果你预期到测试过程中会跑出异常,则可以加上expectedExceptions做预期异常测试。

package com.tester;

import org.testng.annotations.Test;

public class TestRuntime {

    @Test(expectedExceptions = ArithmeticException.class)
    public void divisionWithException() {
        int i = 1 / 0;
        System.out.println("After division the value of i is :"+ i);
    }
}

上述代码中,除0肯定会跑出ArithmeticException,我们可以声明异常,从而去做预期的异常测试。

有时,我们编写的代码并没有准备就绪,并且测试用例要测试该方法/代码是否失败(或成功)。我们就可以用(enabled = false)禁用此测试用例。


package com.tester;

import org.testng.Assert;
import org.testng.annotations.Test;

public class TestIgnore {

    @Test // 默认 enable=true
    public void test1() {
        Assert.assertEquals(true, true);
    }

    @Test(enabled = true)
    public void test2() {
        Assert.assertEquals(true, true);
    }

    @Test(enabled = false)
    public void test3() {
        System.out.println("俺没有执行");
        Assert.assertEquals(true, true);
    }
}

声明了enabled = false的test3方法不会执行。

我们还有个参数跟这块相反,就是alwaysRun。声明了alwaysRun = true的案例,不管怎么样都会执行。


package com.tester;

import org.testng.Assert;
import org.testng.annotations.Test;

public class TestAlwaysRun {

    @Test // 默认 enable=true
    public void test1() {
        Assert.assertEquals(true, false);
    }

    @Test(dependsOnMethods = { "test1" },alwaysRun = true)
    public void test2() {
        Assert.assertEquals(true, true);
    }
}

上述代码中,test2依赖于test1,但是test1中断言true和false相等,必然会失败。如果是依赖测试,test2必然不会执行。但是test2声明了alwaysRun = true,所以test2还是会执行。

在TestNG有个非常有趣的功能就是参数测试。在大多数情况下,你会遇到这样一个测试场景,业务逻辑需要巨大的不同数量的测试。参数测试,允许开发人员运行同样的测试,一遍又一遍使用不同的值。比如我们的接口自动化框架,大家都知道我们的接口测试需要用给入参赋予不同的值去测试接口的业务逻辑是否正确,这时候你用TestNG就非常非常方便。

TestNG让你直接传递参数测试方法两种不同的方式:使用testng.xml、数据提供程序。

使用xml传递参数

通过xml传递参数就需要运用到@Test注解中的参数@Parameter。

创建一个名称为:TestParameterXML.java,其代码如下所示。

package com.tester;

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class TestParameterXML {

    @Test
    @Parameters({ "dbconfig", "poolsize" })
    public void createConnection(String dbconfig, int poolsize) {

        System.out.println("dbconfig : " + dbconfig);
        System.out.println("poolsize : " + poolsize);
}

创建一个名称为:testng.xml 的文件, 其代码如下所示


<?xml version="1.0" encoding="UTF-8"?>
<suite name="test-parameter">

    <test name="example1">

        <parameter name="dbconfig" value="db.properties" />
        <parameter name="poolsize" value="10" />

        <classes>
            <class name="com.yiibai.TestParameterXML" />
        </classes>

    </test>
    
</suite>

首先,我们看到testng.xml中,我们声明了两个<parameter>,分别是dbconfig和poolsize,这个和TestParameterXML.java中@Parameters及createConnection中的两个参数命名是对应的,这样我们就可以xml中定义的值传入方法中打印使用。

通过@DataProvider传递参数

刚刚通过xml传递参数,相对来说较笨重,我们可以用@DataProvider更灵活的传递参数。

创建一个名称为:TestParameterDataProvider.java 的文件, 其代码如下所示

package com.tester;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class TestParameterDataProvider {

    @Test(dataProvider = "provideNumbers")
    public void test(int number, int expected) {
        Assert.assertEquals(number + 10, expected);
    }

    @DataProvider(name = "provideNumbers")
    public Object[][] provideData() {

        return new Object[][] { { 10, 20 }, { 100, 110 }, { 200, 210 } };
    }
    
}

@Test(dataProvider = "provideNumbers"和@DataProvider(name = "provideNumbers")

中的名字对应即可。用@DataProvider 声明数据集,那么对应的匹配的方法就能拿到数据集里的参数,按顺序对应。该运行的代码结果如下:


[TestNG] Running:
  \Users\abc\AppData\Local\Temp\testng-eclipse--1925148879\testng-customsuite.xml

PASSED: test(10, 20)
PASSED: test(100, 110)
PASSED: test(200, 210)

===============================================
    Default test
    Tests run: 3, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.XMLReporter@1b40d5f0: 13 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@6ea6d14e: 34 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter2@4563e9ab: 7 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 0 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@2aaf7cc2: 69 ms
[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@45c8e616: 4 ms

当然@DataProvider不仅仅支持基本类型,还执行对象参数。

package com.tester;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class TestParameterDataProvider2 {

    @Test(dataProvider = "dbconfig")
    public void testConnection(Map<String, String> map) {

        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println("[Key] : " + entry.getKey() + " [Value] : " + entry.getValue());
        }
    }

    @DataProvider(name = "dbconfig")
    public Object[][] provideDbConfig() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("jdbc.driver", "com.mysql.jdbc.Driver");
        map.put("jdbc.url", "jdbc:mysql://localhost:3306/test");
        map.put("jdbc.username", "root");
        map.put("jdbc.password", "123456");
        return new Object[][] { { map } };
    }
}

那我们最后的运行结果如下:


[TestNG] Running:
  \Users\abc\worksp\testng\ParameterTest\src\main\java\com\yiibai\testng.xml

path => F:\worksp\testng\ParameterTest\db.properties
drivers : com.mysql.jdbc.Driver
connectionURL : jdbc:mysql://localhost:3306/test
username : root
password : 123456

Tue May 02 23:15:52 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

===============================================
test-parameter
Total tests run: 1, Failures: 0, Skips: 0
===============================================

最后还有一种用法就是结合TestNG的ITestContext一起使用,能打开新的大门。


package com.tester;

import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class TestParameterDataProvider4 {

    @Test(dataProvider = "dataProvider", groups = {"groupA"})
    public void test1(int number) {
        Assert.assertEquals(number, 1);
    }

    @Test(dataProvider = "dataProvider", groups = "groupB")
    public void test2(int number) {
        Assert.assertEquals(number, 2);
    }

    @DataProvider(name = "dataProvider")
    public Object[][] provideData(ITestContext context) {
        Object[][] result = null;

        for (String group : context.getIncludedGroups()) {

            System.out.println("group : " + group);

            if ("groupA".equals(group)) {
                result = new Object[][] { { 1 } };
                break;
            }

        }

        if (result == null) {
            result = new Object[][] { { 2 } };
        }
        return result;

    }
}

创建一个名称为:testng.xml 的文件, 其代码如下所示


<?xml version="1.0" encoding="UTF-8"?>
<suite name="test-parameter">

    <test name="example1">

        <groups>
            <run>
                <include name="groupA" />
            </run>
        </groups>

        <classes>
            <class name="com.tester.TestParameterDataProvider4" />
        </classes>

    </test>

</suite>

我们的xml中声明运行groupA,则进入if ("groupA".equals(group))这个判断。最后运行结果如下:


[TestNG] Running:
  \Users\abc\worksp\testng\ParameterTest\src\main\java\com\yiibai\testng4.xml

group : groupA

===============================================
test-parameter
Total tests run: 1, Failures: 0, Skips: 0
===============================================

并发测试

并发测试,其实就是多线程的模式运行所有的test,这样可以获得最大的运行速度,最大限度的节约执行时间。当然,并发运行也是有代价的,就是需要我们的代码是线程安全的,且运行的场景是不排他的。建议是在测试代码中,尽可能地避免使用共享变量。如果真的用到了,要慎用synchronized关键字来对共享变量进行加锁同步。否则,难免你的用例执行时可能会出现不稳定的情景。当然我们也可以用这个结合selenium做压力测试,结合接口自动化框架做压力测试等等。那让我们一一道来。

1、在测试方法中直接指定

有些时候,我们需要对一个测试用例,比如一个http接口,执行并发测试,即一个接口的反复调用。TestNG中也提供了优雅的支持方式,在@Test标签中指定threadPoolSize和invocationCount。这是在测试方法中直接指定的一种实现方式。

package com.tester;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;


public class TestClass1 {
    private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
    @BeforeClass
    public void beforeClass(){
        System.out.println("Start Time: " + df.format(new Date()));
    }

    @Test(enabled=true, dataProvider="testdp", threadPoolSize=2, invocationCount=5, successPercentage=98)
    public void test(String dpNumber) throws InterruptedException{
        System.out.println("Current Thread Id: " + Thread.currentThread().getId() + ". Dataprovider number: "+ dpNumber);
        Thread.sleep(5000);
    }

    @DataProvider(name = "testdp", parallel = true)
    public static Object[][]testdp(){
        return new Object[][]{
            {"1 "},
            {"2 "}
        };
    }

    @AfterClass
    public void afterClass(){
        System.out.println("End Time: " + df.format(new Date()));
    }
}

其中invocationCount=5代表这个测试方法需要执行5次,threadPoolSize=2代表开启了2个线程去执行,successPercentage=98代表成功率超过98%就算成功。

@DataProvider(name = "testdp", parallel = true),parallel属性默认false,表示使用该数据源的测试方法不能并发执行。parallel属性设置为true,表示使用该数据源的测试方法可以并发执行。

我们先来看下运行结果,具体如下:

Start Time: 2019-10-10 14:10:43
[ThreadUtil] Starting executor timeOut:0ms workers:5 threadPoolSize:2
Current Thread Id: 14. Dataprovider number: 2
Current Thread Id: 15. Dataprovider number: 2
Current Thread Id: 12. Dataprovider number: 1
Current Thread Id: 13. Dataprovider number: 1
Current Thread Id: 16. Dataprovider number: 1
Current Thread Id: 18. Dataprovider number: 1
Current Thread Id: 17. Dataprovider number: 2
Current Thread Id: 19. Dataprovider number: 2
Current Thread Id: 21. Dataprovider number: 2
Current Thread Id: 20. Dataprovider number: 1
End Time: 2019-10-10 14:10:58

该线程池与dp的并发线程池是两个独立的线程池。这里的线程池是用于起多个method,而每个method的测试数据由dp提供,如果这边dp里有5组数据,那么实际上10次执行,每次都会调5次接口,这个接口被调用的总次数是10*5=50次。threadPoolSize指定的5个线程中,每个线程单独去调method时,用到的dp如果也是支持并发执行的话,会创建一个新的线程池(dpThreadPool)来并发执行测试数据。

2、通过testng.xml设置


package com.tester;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ParallelClassesTestOne
{
    @BeforeClass
    public void beforeClass() {
        long id = Thread.currentThread().getId();
        System.out.println("Before test-class. Thread id is: " + id);
    }
    @Test
    public void testMethodOne() {
        long id = Thread.currentThread().getId();
        System.out.println("Sample test-method One. Thread id is: " + id);
    }
    @Test
    public void testMethodTwo() {
        long id = Thread.currentThread().getId();
        System.out.println("Sample test-method Two. Thread id is: " + id);
    }
    @AfterClass
    public void afterClass() {
        long id = Thread.currentThread().getId();
        System.out.println("After test-class. Thread id is: " + id);
    }
}

package com.tester;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ParallelClassesTestTwo
{
    @BeforeClass
    public void beforeClass() {
        long id = Thread.currentThread().getId();
        System.out.println("Before test-class. Thread id is: " + id);
    }
    @Test
    public void testMethodOne() {
        long id = Thread.currentThread().getId();
        System.out.println("Sample test-method One. Thread id is: " + id);
    }
    @Test
    public void testMethodTwo() {
        long id = Thread.currentThread().getId();
        System.out.println("Sample test-method Two. Thread id is: " + id);
    }
    @AfterClass
    public void afterClass() {
        long id = Thread.currentThread().getId();
        System.out.println("After test-class. Thread id is: " + id);
    }
}

我们新建一个testng.xml

<suite name="Test-class Suite" parallel="classes" thread-count="2" >
  <test name="Test-class test" >
    <classes>
      <class name="com.tester.ParallelClassesTestOne" />
      <class name="com.tester.ParallelClassesTestTwo" />
    </classes>
  </test>
</suite>

在Eclipse中选中该文件并且以TestNG测试套件方式运行它。你将会在控制台中看到以下输出信息:


Before test-class. Thread id is: 10
Before test-class. Thread id is: 9
Sample test-method One. Thread id is: 9
Sample test-method One. Thread id is: 10
Sample test-method Two. Thread id is: 10
After test-class. Thread id is: 10
Sample test-method Two. Thread id is: 9
After test-class. Thread id is: 9

因为我们testng有test,class,method级别的并发,可以通过在testng.xml中的suite tag下设置以下几种:

<suite name="Testng Parallel Test" parallel="tests" thread-count="5">

说明:在当前测试规划的执行过程中,为每个测试用例的执行使用单独的线程(该测试用例中的测试方法共享一个线程),最多并发4个线程。

<suite name="Testng Parallel Test" parallel="classes" thread-count="5">

说明:在当前测试规划的执行过程中,为每个测试类的执行使用单独的线程(该测试类中的测试方法共享一个线程),最多并发4个线程。

<suite name="Testng Parallel Test" parallel="methods" thread-count="5">

说明:在当前测试规划的执行过程中,为每个测试方法的执行使用单独的线程,最多并发4个线程。

注解运行的顺序

  • @BeforeSuite: 被注释的方法将在所有测试运行前运行

  • @AfterSuite: 被注释的方法将在所有测试运行后运行

  • @BeforeTest: 被注释的方法将在测试运行前运行

  • @AfterTest: 被注释的方法将在测试运行后运行

  • @BeforeGroups: 被配置的方法将在列表中的gourp前运行。这个方法保证在第一个属于这些组的测试方法调用前立即执行

  • @AfterGroups: 被配置的方法将在列表中的gourp后运行。这个方法保证在最后一个属于这些组的测试方法调用后立即执行

  • @BeforeClass: 被注释的方法将在当前类的第一个测试方法调用前运行

  • @AfterClass: 被注释的方法将在当前类的所有测试方法调用后运行

  • @BeforeMethod:被注释的方法将在每一个测试方法调用前运行

  • @AfterMethod: 被注释的方法将在每一个测试方法调用后运行

Testng一个方法的生命周期

  1. @BeforeSuite(执行一次)

  2. @BeforeClass(执行一次)

  3. @BeforeMethod(N个Test 方法执行N次)

  4. @Test Test方法(此注解可能在类上表示多个,在方法表示一个)

  5. @AfterMethod(N个Test 方法执行N次)

  6. @AfterClass(执行一次)

  7. @AfterSuite(执行一次)

testng.xml简介

testng.xml是以xml记录所有测试的文件。它描述了测试套件的运行时定义,也是testng中运行测试的最大工作单元。虽然没有testng.xml文件,测试也很容易被执行。但是随着测试代码的增长,testng.xml提供了方便用来存放所有运行时的配置,如设置有关类,测试,方法,参数,分组的包含与排除等。

Testng.xml的主要结构:根标签是,标签包含一个或多个标签,标签包含一个或多个标签,标签包含一个或多个标签。

  • Suite:由一个XML文件表示,可以包含一个或者多个Test,suite可以通过标签来运行其他testng的xml文件

  • Test:表示一个测试,可以包含一个或者多个TestNG类

  • TestNG类:就是一个简单的Java类,至少包含一个TestNG annotation

  • Method:一个普通的Java方法,其前由@Test标记

  • Groups:TestNG可以将不同的Method分类到不同的Groups中,也可以将Class分类到不同的Groups中

猜你喜欢

转载自blog.csdn.net/chimomo/article/details/115000604
今日推荐