Moco+HttpClient+TestNg+MyBatis+Mysql接口測試框架

接口框架實(shí)現(xiàn)整體步驟:

resource層:
底層是application.properties配置路徑文件
databaseconfig.xml配置數(shù)據(jù)庫文件
mapper文件夾中是sqlmapper.xml的配置文件
testng.xml配置文件 測試類的配置文件
還有moco的接口文件,json格式的,需要一個jar包依賴

utils層:
configfile類是從application.properties配置文件中獲取url和參數(shù)信息房匆,string、json漫萄、map等類型
databaseutil類是加載database.xml文件,處理從數(shù)據(jù)庫中獲取數(shù)據(jù)信息

model層:
對應(yīng)的實(shí)體類是與數(shù)據(jù)庫的表一致盈匾、還可以定義接口名的枚舉類和參數(shù)url的枚舉類

config層:
配置測試報告意系,extenttestngrepoter監(jiān)聽器類

case層:
寫具體的接口類牙躺,用httpclient去實(shí)現(xiàn)每個接口的調(diào)用和結(jié)果驗證
接口與接口之間的數(shù)據(jù)依賴可以通過testng的數(shù)據(jù)依賴去傳遞。

一、創(chuàng)建maven項目颤霎,配置pom文件

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20170516</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.14</version>
        </dependency>
        <dependency>
            <groupId>com.relevantcodes</groupId>
            <artifactId>extentreports</artifactId>
            <version>2.41.1</version>
        </dependency>
        <dependency>
            <groupId>com.vimalselvam</groupId>
            <artifactId>testng-extentsreport</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.aventstack</groupId>
            <artifactId>extentreports</artifactId>
            <version>3.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.9.10</version>
        </dependency>

二唇撬、創(chuàng)建application.properties文件

test.url=http://localhost:8086

#登錄接口
login.uri=/demo/login
#更新用戶信息接口
updateUserInfo.uri=/demo/updateUserInfo
#獲取用戶列表接口
getUserList.uri=/demo/getUserInfo
#獲取用戶信息接口
getUserInfo.uri=/demo/getUserInfo
#添加用戶接口
addUser.uri=/demo/addUser

三筋栋、創(chuàng)建databaseConfig.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 注冊對象的空間命名 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 1.加載數(shù)據(jù)庫驅(qū)動 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- 2.數(shù)據(jù)庫連接地址 -->
                <property name="url" value="jdbc:mysql://localhost:3306/dbgirl"/>
                <!-- 數(shù)據(jù)庫用戶... -->
                <property name="username" value="rootname"/>
                <!-- 數(shù)據(jù)庫密碼... -->
                <property name="password" value="passname"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 注冊映射文件:java對象與數(shù)據(jù)庫之間的xml文件路徑咒彤! -->
    <mappers>
        <mapper resource="mapper/SQLMapper.xml"/>
    </mappers>
</configuration>

四、在resource下創(chuàng)建mapper文件夾劈伴,創(chuàng)建SQLMapper.xml文件密末,先不寫內(nèi)容
testng.xml文件也可以先建立


image.png

五、利用springboot中的spring-data-jpa創(chuàng)建實(shí)體類,創(chuàng)建數(shù)據(jù)庫表严里,可以參考在《springboot學(xué)習(xí)(四)》中的代碼新啼。
也可以手動去創(chuàng)建數(shù)據(jù)庫表。
六刹碾、在java下創(chuàng)建cases燥撞、config、model迷帜、utils文件夾物舒,分別放不同的內(nèi)容
現(xiàn)在model中創(chuàng)建對應(yīng)數(shù)據(jù)庫表的實(shí)體類,代碼如下:
只舉其中一個表的例子,字段值要與數(shù)據(jù)庫表的值一致

package com.course.model;

import lombok.Data;

@Data
public class User {
    private Integer id;  //id是主鍵戏锹,并且會自增冠胯,調(diào)接口時不需要傳參數(shù)id,傳其他參數(shù)就可以
    private String userName;
    private String password;
    private Integer age;
    private String sex;
    private Integer permission;
    private Integer isDelete;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", permission=" + permission +
                ", isDelete=" + isDelete +
                '}';
    }
}

該代碼中沒有寫get和set方法景用,是因為用了lombok可以自動省略get和set方法

下面會用到logincase的model類涵叮,補(bǔ)充一下源碼:

package com.course.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "loginCase")
public class loginCase {
    @Id
    @GeneratedValue
    private Integer id;
    private String userName;
    private String password;
    private String expected;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getExpected() {
        return expected;
    }

    public void setExpected(String expected) {
        this.expected = expected;
    }
}

在model中寫一個枚舉類,把接口寫成枚舉類型伞插,保證傳進(jìn)來的接口名稱正確

package com.course.model;

public enum InterfaceName {
    LOGIN,UPDATEUSERINFO,GETUSERLIST,GETUSERINFO,ADDUSER
}

七、在config文件中創(chuàng)建ExtentTestNGIReporterListener類盾碗,測試報告會用到
Maven pom文件中添加依賴:

<dependency>
    <groupId>com.relevantcodes</groupId>
    <artifactId>extentreports</artifactId>
    <version>2.41.1</version>
</dependency>
<dependency>
    <groupId>com.vimalselvam</groupId>
    <artifactId>testng-extentsreport</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>com.aventstack</groupId>
    <artifactId>extentreports</artifactId>
    <version>3.0.6</version>
</dependency>

在resource中增加testng.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="用戶管理系統(tǒng)測試套件">
    <test name="用戶管理系統(tǒng)測試用例">
        <classes>
            <class name="com.course.cases.LoginTest">
                <include name="LoginTrue" />
                <include name="LoginFalse" />
            </class>
            <class name="com.course.cases.AddUserTest">
                <include name="AddUser" />
            </class>
            <class name="com.course.cases.GetUserInfoTest">
                <include name="getUserInfo" />
            </class>
            <class name="com.course.cases.GetUserListTest">
                <include name="getUserList" />
            </class>
            <class name="com.course.cases.updateUserInfo">
                <include name="updateUserInfo" />
            </class>
        </classes>

    </test>
    <listeners>
        <listener class-name="com.course.config.ExtentTestNGIReporterListener"/>
    </listeners>
</suite>

<listener class-name="com.course.config.ExtentTestNGIReporterListener"/>
中ExtentTestNGIReporterListener類的路徑媚污,就是下面要在config文件夾下創(chuàng)建的ExtentTestNGIReporterListener類的路徑。

在config文件夾下增加監(jiān)聽類:不用改類中的內(nèi)容:

package com.course.config;

import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.TestAttribute;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;

import java.io.File;
import java.util.*;

public class ExtentTestNGIReporterListener implements IReporter {
        //生成的路徑以及文件名
    private static final String OUTPUT_FOLDER = "test-output/";
    private static final String FILE_NAME = "index.html";

    private ExtentReports extent;

    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        init();
        boolean createSuiteNode = false;
        if(suites.size()>1){
            createSuiteNode=true;
        }
        for (ISuite suite : suites) {
            Map<String, ISuiteResult> result = suite.getResults();
            //如果suite里面沒有任何用例廷雅,直接跳過耗美,不在報告里生成
            if(result.size()==0){
                continue;
            }
            //統(tǒng)計suite下的成功、失敗航缀、跳過的總用例數(shù)
            int suiteFailSize=0;
            int suitePassSize=0;
            int suiteSkipSize=0;
            ExtentTest suiteTest=null;
            //存在多個suite的情況下商架,在報告中將同一個一個suite的測試結(jié)果歸為一類,創(chuàng)建一級節(jié)點(diǎn)芥玉。
            if(createSuiteNode){
                suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
            }
            boolean createSuiteResultNode = false;
            if(result.size()>1){
                createSuiteResultNode=true;
            }
            for (ISuiteResult r : result.values()) {
                ExtentTest resultNode;
                ITestContext context = r.getTestContext();
                if(createSuiteResultNode){
                    //沒有創(chuàng)建suite的情況下蛇摸,將在SuiteResult的創(chuàng)建為一級節(jié)點(diǎn),否則創(chuàng)建為suite的一個子節(jié)點(diǎn)灿巧。
                    if( null == suiteTest){
                        resultNode = extent.createTest(r.getTestContext().getName());
                    }else{
                        resultNode = suiteTest.createNode(r.getTestContext().getName());
                    }
                }else{
                    resultNode = suiteTest;
                }
                if(resultNode != null){
                    resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName());
                    if(resultNode.getModel().hasCategory()){
                        resultNode.assignCategory(r.getTestContext().getName());
                    }else{
                        resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
                    }
                    resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
                    resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
                    //統(tǒng)計SuiteResult下的數(shù)據(jù)
                    int passSize = r.getTestContext().getPassedTests().size();
                    int failSize = r.getTestContext().getFailedTests().size();
                    int skipSize = r.getTestContext().getSkippedTests().size();
                    suitePassSize += passSize;
                    suiteFailSize += failSize;
                    suiteSkipSize += skipSize;
                    if(failSize>0){
                        resultNode.getModel().setStatus(Status.FAIL);
                    }
                    resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
                }
                buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL);
                buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP);
                buildTestNodes(resultNode,context.getPassedTests(), Status.PASS);
            }
            if(suiteTest!= null){
                suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
                if(suiteFailSize>0){
                    suiteTest.getModel().setStatus(Status.FAIL);
                }
            }

        }
//        for (String s : Reporter.getOutput()) {
//            extent.setTestRunnerOutput(s);
//        }

        extent.flush();
    }

    private void init() {
        //文件夾不存在的話進(jìn)行創(chuàng)建
        File reportDir= new File(OUTPUT_FOLDER);
        if(!reportDir.exists()&& !reportDir .isDirectory()){
            reportDir.mkdir();
        }
        ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
        // 設(shè)置靜態(tài)文件的DNS
        //怎么樣解決cdn.rawgit.com訪問不了的情況
        htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);

        htmlReporter.config().setDocumentTitle("api自動化測試報告");
        htmlReporter.config().setReportName("api自動化測試報告");
        htmlReporter.config().setChartVisibilityOnOpen(true);
        htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
        htmlReporter.config().setTheme(Theme.STANDARD);
        htmlReporter.config().setCSS(".node.level-1  ul{ display:none;} .node.level-1.active ul{display:block;}");
        extent = new ExtentReports();
        extent.attachReporter(htmlReporter);
        extent.setReportUsesManualConfiguration(true);
    }

    private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
        //存在父節(jié)點(diǎn)時赶袄,獲取父節(jié)點(diǎn)的標(biāo)簽
        String[] categories=new String[0];
        if(extenttest != null ){
            List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
            categories = new String[categoryList.size()];
            for(int index=0;index<categoryList.size();index++){
                categories[index] = categoryList.get(index).getName();
            }
        }

        ExtentTest test;

        if (tests.size() > 0) {
            //調(diào)整用例排序,按時間排序
            Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
                @Override
                public int compare(ITestResult o1, ITestResult o2) {
                    return o1.getStartMillis()<o2.getStartMillis()?-1:1;
                }
            });
            treeSet.addAll(tests.getAllResults());
            for (ITestResult result : treeSet) {
                Object[] parameters = result.getParameters();
                String name="";
                //如果有參數(shù)抠藕,則使用參數(shù)的toString組合代替報告中的name
                for(Object param:parameters){
                    name+=param.toString();
                }
                if(name.length()>0){
                    if(name.length()>50){
                        name= name.substring(0,49)+"...";
                    }
                }else{
                    name = result.getMethod().getMethodName();
                }
                if(extenttest==null){
                    test = extent.createTest(name);
                }else{
                    //作為子節(jié)點(diǎn)進(jìn)行創(chuàng)建時饿肺,設(shè)置同父節(jié)點(diǎn)的標(biāo)簽一致,便于報告檢索盾似。
                    test = extenttest.createNode(name).assignCategory(categories);
                }
                //test.getModel().setDescription(description.toString());
                //test = extent.createTest(result.getMethod().getMethodName());
                for (String group : result.getMethod().getGroups())
                    test.assignCategory(group);

                List<String> outputList = Reporter.getOutput(result);
                for(String output:outputList){
                    //將用例的log輸出報告中
                    test.debug(output);
                }
                if (result.getThrowable() != null) {
                    test.log(status, result.getThrowable());
                }
                else {
                    test.log(status, "Test " + status.toString().toLowerCase() + "ed");
                }

                test.getModel().setStartTime(getTime(result.getStartMillis()));
                test.getModel().setEndTime(getTime(result.getEndMillis()));
            }
        }
    }

    private Date getTime(long millis) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(millis);
        return calendar.getTime();
    }
}

要先把test-output文件夾展示出來敬辣,需要配置idea:
配置testng報告,進(jìn)入idea,按著圖中去點(diǎn)擊


image.png

選擇左側(cè)測試方法溉跃,點(diǎn)擊listener汰聋,勾選Use default reporters,然后點(diǎn)擊確定


image.png

右鍵執(zhí)行testng.xml文件喊积,就能看到test-output文件夾烹困,查看生成的報告:


image.png

右鍵點(diǎn)擊index.html選擇copy path,粘貼路徑在瀏覽器中查看報告信息乾吻。

八髓梅、在utils文件中創(chuàng)建configFile,從配置文件中利用resourcebundle取url绎签,代碼如下

package com.course.utils;

import com.course.model.InterfaceName;

import java.util.Locale;
import java.util.ResourceBundle;

public class configFile {
    //從application文件中取url
    private static ResourceBundle resourceBundle = ResourceBundle.getBundle("application", Locale.CHINA);

    public static String getUrl(InterfaceName name){
        //取域名和端口號
        String baseurl = resourceBundle.getString("test.url");
        String uri = "";
        //如果傳進(jìn)來的接口名稱正確枯饿,就返回相應(yīng)接口的url
        if(name == InterfaceName.LOGIN){
            uri = resourceBundle.getString("login.uri");
        }
        if(name == InterfaceName.UPDATEUSERINFO){
            uri = resourceBundle.getString("updateUserInfo.uri");
        }
        if(name == InterfaceName.GETUSERLIST){
            uri = resourceBundle.getString("getUserList.uri");
        }
        if (name == InterfaceName.GETUSERINFO){
            uri = resourceBundle.getString("getUserInfo.uri");
        }
        if(name == InterfaceName.ADDUSER){
            uri = resourceBundle.getString("addUser.uri");
        }

        //拼接url
        String testurl = baseurl + uri;
        return testurl;
    }
}


九、在utils中創(chuàng)建DatabaseUtil诡必,從databaseconfig.xml配置文件中取配置信息奢方,創(chuàng)建sqlsession并返回,在case類中就不用再寫獲取數(shù)據(jù)庫信息了

package com.course.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
/*
把sqlsession封裝起來爸舒,從數(shù)據(jù)庫中獲取數(shù)據(jù)
 */
public class DatabaseUtil {
    public static SqlSession getsqlSession() throws IOException {
        //獲取配置的資源文件
        Reader reader = Resources.getResourceAsReader("databaseConfig.xml");
        //得到SqlSessionFactory蟋字,使用類加載器加載xml文件
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
        //得到sqlsession對象,這個對象就能執(zhí)行配置文件中的sql語句啦
        SqlSession session = factory.openSession();

        return session;

    }
}

image.png

十扭勉、在cases文件夾下寫測試類鹊奖,先寫一個loginTest

package com.course.cases;

import com.course.config.TestConfig;
import com.course.model.InterfaceName;
import com.course.model.loginCase;
import com.course.utils.DatabaseUtil;
import com.course.utils.configFile;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.ibatis.session.SqlSession;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.io.IOException;

public class LoginTest {

    //加@BeforeTest注解,在用例執(zhí)行前先獲取接口的url
    @BeforeTest(groups = "loginTrue",description = "登錄接口")
    public void beforeTest(){
        //獲取接口地址
        TestConfig.loginuri = configFile.getUrl(InterfaceName.LOGIN);
        TestConfig.addUseruri = configFile.getUrl(InterfaceName.ADDUSER);
        TestConfig.getUserInfouri = configFile.getUrl(InterfaceName.GETUSERINFO);
        TestConfig.getUserListuri = configFile.getUrl(InterfaceName.GETUSERLIST);
        TestConfig.updateUserInfouri = configFile.getUrl(InterfaceName.UPDATEUSERINFO);

        TestConfig.defaultHttpClient = new DefaultHttpClient();
    }
    @Test(groups = "loginTrue", description = "登錄成功的接口")
    public void LoginTrue() throws IOException {
        //定義sqlsession對象涂炎,從DatabaseUtil類中調(diào)getsqlSession方法忠聚,直接獲取到sqlsession對象,可以執(zhí)行sql語句唱捣,不需要配置數(shù)據(jù)庫信息
        SqlSession sqlSession = DatabaseUtil.getsqlSession();
        //調(diào)selectone方法两蟀,通過mybatis中的sql語句查數(shù)據(jù)庫表,并返回結(jié)果賦值給logincase對象
        //sqlstatement與SQLMapper.xml中的id一致震缭,33是傳的參數(shù)id
        loginCase loginCase = sqlSession.selectOne("logincase",33);
        //打印查詢的結(jié)果赂毯,結(jié)果就是從數(shù)據(jù)庫中查詢到的結(jié)果
        System.out.println(loginCase.toString());
        System.out.println(TestConfig.loginuri);
    }

    //調(diào)數(shù)據(jù)庫表中第二行數(shù)據(jù),可以認(rèn)為是用戶名和密碼錯誤蛀序,登錄失敗的接口
    @Test(groups = "loginFalse",description = "登錄失敗的接口")
    public void LoginFalse() throws IOException {
        SqlSession sqlSession = DatabaseUtil.getsqlSession();
        loginCase loginCase = sqlSession.selectOne("logincase",36);
        System.out.println(loginCase.toString());
        System.out.println(TestConfig.loginuri);
    }

}

其中TestConfig.loginuri等屬性欢瞪,是在config文件夾下定義了一個TestConfig的類,給該類創(chuàng)建了多個uri名稱的屬性徐裸,這樣uri名稱就不用隨便再起遣鼓,直接從這個類中調(diào)用就行。

package com.course.config;

import org.apache.http.client.CookieStore;
import org.apache.http.impl.client.DefaultHttpClient;

public class TestConfig {
    public static String loginuri;
    public static String updateUserInfouri;
    public static String getUserListuri;
    public static String getUserInfouri;
    public static String addUseruri;

    public static DefaultHttpClient defaultHttpClient;
    public static CookieStore cookieStore;
}

image.png

十一重贺、執(zhí)行l(wèi)oginTest類時遇到問題:
報org.apache.ibatis.exceptions.PersistenceException:的錯誤
是因為mybatis沒有連接上數(shù)據(jù)庫骑祟,或者是數(shù)據(jù)庫和實(shí)體類的對應(yīng)關(guān)系沒有建立起來
需要修改databaseconfig.xml文件,數(shù)據(jù)庫url為:

<property name="url" value="jdbc:mysql://localhost:3306/dbgirl?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8"/>

之前該url是寫在yml中回懦,&符號不需要轉(zhuǎn)義,寫到xml中需要對 & 轉(zhuǎn)義成&

還需要加上
<typeAliases>
<typeAlias type="com.course.model.loginCase" />
<typeAlias type="com.course.model.addUserCase" />
</typeAliases>

要加到environments配置前面次企,要不然會報錯:
The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHand......

<typeAlias type="com.course.model.addUserCase" /> 要寫多個怯晕,哪個實(shí)體類對應(yīng)的有數(shù)據(jù)庫表都要單獨(dú)寫,要不然也會報錯缸棵。

同時mapper/SQLMapper.xml中的namespace也要寫對com.course.model

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.course.model">
    <select id="logincase" parameterType="Integer" resultType="com.course.model.loginCase">
            select * from loginCase where id=#{id}
    </select>
</mapper>

十二舟茶、HttpClient的學(xué)習(xí)總結(jié):

以上的框架中缺少moco接口,請參考《Moco接口框架應(yīng)用實(shí)戰(zhàn)》堵第。
下面會在前十一章搭建框架的基礎(chǔ)上吧凉,利用HttpClient去調(diào)接口驗證測試。
12.1 moco的接口:
登錄接口


image.png

還有一個調(diào)列表接口踏志,不再貼圖:

TestDologin 測試類:

public class TestDologin {


    //設(shè)置全局變量cookieStore
    public static CookieStore cookieStore;

//    //返回token值
//    public static String token;

    @BeforeTest(groups = "loginTrue")
    public void beforetest(){

        //獲取所有接口的uri
        TestConfig.doLoginuri = ConfigFile.GetUrl(InterfaceName.DOLOGIN);
        TestConfig.getMerchantListuri = ConfigFile.GetUrl(InterfaceName.GETMERCHANTLIST);
        //獲取所有接口的參數(shù)值,doLoginParams接口是post入?yún)son格式的阀捅,可以從配置文件中調(diào),getMerchantListuri中已拼接了接口
        TestConfig.doLoginParams = ConfigFile.GetParams(ParamsName.DOLOGINPARAMS);
    }
    
    /*
    從配置文件中獲取全部的參數(shù)轉(zhuǎn)換成String類型入?yún)?     */
    @Test(groups = "loginTrue", description = "登錄成功的接口")
    public void testdologin() throws IOException {
        System.out.println(TestConfig.doLoginuri);
        System.out.println(TestConfig.doLoginParams);

        DefaultHttpClient httpClient = new DefaultHttpClient();
        //該接口返回的有cookie针余,獲取cookie饲鄙,賦值給全局變量,下一個接口setcookiestore圆雁,下一個接口入?yún)r使用cookie
        cookieStore = httpClient.getCookieStore();

        //調(diào)moco的接口可以用這個
        HttpPost httpPost = new HttpPost(TestConfig.doLoginuri);
        //設(shè)置參數(shù)
        //使用json入?yún)⑷碳叮雲(yún)⑹侵形臅r不行。

        //設(shè)置請求頭header
        httpPost.setHeader("Content-Type","application/json;charset=gbk");
        //傳入?yún)?shù)

        StringEntity entity=new StringEntity(TestConfig.doLoginParams.toString(),"gbk");
        httpPost.setEntity(entity);

        //獲取返回結(jié)果
        HttpResponse httpResponse = httpClient.execute(httpPost);
        String result = EntityUtils.toString(httpResponse.getEntity());
        System.out.println(result);

    }
}

TestgetMerchantList測試類:

package com.course.cases;

import com.course.config.TestConfig;
import com.course.utils.DatabaseUtil;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.apache.ibatis.session.SqlSession;
import org.json.JSONArray;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import org.apache.http.client.CookieStore;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONObject;

public class TestgetMerchantList {

    public static int MerchantId;

    @Test(dependsOnGroups = "loginTrue",description = "獲取列表接口")
    public void TestgetMerchantList() throws URISyntaxException, IOException {
        System.out.println(TestConfig.getMerchantListuri);

        DefaultHttpClient httpClient = new DefaultHttpClient();

        //參數(shù)已經(jīng)拼接在url中
        HttpGet httpGet = new HttpGet(TestConfig.getMerchantListuri);



        //通過moco接口摸柄,可以使用setcookiestore方法從上一個接口獲取token值
        httpClient.setCookieStore(TestDologin.cookieStore);

        //調(diào)取真實(shí)的接口只能設(shè)置header颤练,傳值token
//        httpGet.setHeader("token",TestDologin.token);

        HttpResponse response = httpClient.execute(httpGet);
        /*
        下面要驗證幾種斷言方法:
        1、判斷接口返回是否是200
        2驱负、判斷一頁返回的門店列表有20條
        3、判斷從第5位開始評分由大到小
        4患雇、判斷與數(shù)據(jù)庫中獲取數(shù)據(jù)是否一致
         */

        //1跃脊、判斷接口返回是否是200
        Assert.assertEquals(response.getStatusLine().getStatusCode(),200,"接口返回200成功");

        String result = EntityUtils.toString(response.getEntity());
        System.out.println(result);

        JSONObject resultobject = new JSONObject(result);
        JSONObject p2pdataobject = resultobject.getJSONObject("p2pdata");
        JSONArray dataArray = p2pdataobject.getJSONArray("data");

        //2、判斷一頁返回的門店列表有20條
        Assert.assertEquals(dataArray.length(),20,"一頁返回門店列表有20條");

        List scorelist = new ArrayList();
        for (int i=0;i<dataArray.length();i++){
            String dataresult = dataArray.get(i).toString();
            JSONObject dataresultobject = new JSONObject(dataresult);
            scorelist.add(dataresultobject.getInt("score"));
        }
        //3苛吱、判斷從第5位開始評分由大到小
        for (int j=4;j<scorelist.size()-1;j++){
            if ((Integer)scorelist.get(j) >= (Integer)scorelist.get(j+1)){
                System.out.println("比對成功");

            }else{
                System.out.println("比對不成功");
            }

        }

        String dataresult = dataArray.get(1).toString();
        JSONObject dataresultobject = new JSONObject(dataresult);
        MerchantId = dataresultobject.getInt("id");
        System.out.println(MerchantId);

        //4酪术、判斷與數(shù)據(jù)庫中獲取數(shù)據(jù)是否一致
        SqlSession sqlSession = DatabaseUtil.getsqlsession();
        int marchantid = sqlSession.selectOne("getmarchantid",MerchantId);
        Assert.assertEquals(marchantid,MerchantId);

    }

}

總結(jié):入?yún)⒌奶幚矸椒ㄓ泻芏喾N:
get請求:
1、把參數(shù)拼接在url中

getMerchantListuri=getMerchantList?longitude=116.41090393066406&latitude=39.96870040893555

2翠储、部分參數(shù)拼接在url中绘雁,其他參數(shù)和url拼接到一起

        //url要拼接參數(shù)
        URIBuilder uriBuidler = new URIBuilder(TestConfig.getCarWashDetailuri);
        //需要從其他類中依賴的參數(shù)值
        uriBuidler.setParameter("merchantId", String.valueOf(TestgetMerchantList.MerchantId));

        HttpGet httpGet = new HttpGet(uriBuidler.build());

3、定義一個List<NameValuePair>援所,傳入?yún)?shù)

        URIBuilder uriBuilder = new URIBuilder(TestConfig.getCarWashDetailuritwouri);

        //第一種寫入?yún)?shù)的方法庐舟,定義一個List,類型是NameValuePair
        List<NameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("merchantId", String.valueOf(TestgetMerchantList.MerchantId)));
        urlParameters.add(new BasicNameValuePair("channel","6"));
        uriBuilder.setParameters(urlParameters);


        HttpGet httpGet = new HttpGet(uriBuilder.build());

post請求:
1、獲取object類型的參數(shù)
配置文件中這些寫:

doLoginParams={"phone": "15677777777","validateCode": "9612"}

寫一個工具類住拭,定義一個獲取object參數(shù)的方法

    //定義一個靜態(tài)的方法挪略,獲取post接口的參數(shù)值历帚,返回類型是Object類型
    public static Object GetParams(ParamsName name){
        if (name.equals(ParamsName.DOLOGINPARAMS)){
            Object doLoginParams = resourceBundle.getObject("doLoginParams");
            return doLoginParams;
        }
        return null;
    }

在httpclient中入?yún)⒅苯舆@樣寫:

TestConfig.doLoginParams = ConfigFile.GetParams(ParamsName.DOLOGINPARAMS);

        //傳入?yún)?shù)

        StringEntity entity=new StringEntity(TestConfig.doLoginParams.toString(),"gbk");
        httpPost.setEntity(entity);

注:直接配置文件中取String類型的參數(shù)值也行,不用傳object的也可以

2杠娱、配置文件中寫部分參數(shù)

doLoginParamstwo={"phone": "15677777777"}

工具類中寫挽牢,定義一個返回map類型參數(shù)的方法:

    //定義一個靜態(tài)的方法,獲取post接口的參數(shù)值摊求,返回類型為map禽拔,這樣有些參數(shù)可以依賴其他的接口,或者手動填寫參數(shù)
    public static Map<String,Object> GetParamswithMap(ParamsName name){
        if (name == ParamsName.DOLOGINPARAMSTWOPARAMS){
            String doLoginParams = resourceBundle.getString("doLoginParamstwo");
            return JSON.parseObject(doLoginParams);
        }
        return null;

    }

在httpclient中入?yún)⒅苯舆@樣寫:

TestConfig.doLoginParamstwo = ConfigFile.GetParamswithMap(ParamsName.DOLOGINPARAMSTWOPARAMS);

        //傳入?yún)?shù)
        Map<String,Object> map = TestConfig.doLoginParamstwo;
        map.put("validateCode",TestSendVerificationCode.validate_code);//也可以使用數(shù)據(jù)依賴傳參數(shù)


        StringEntity entity=new StringEntity(map.toString(),"gbk");
        httpPost.setEntity(entity);

十三室叉、TestNg的擴(kuò)展學(xué)習(xí)總結(jié):
1睹栖、添加依賴:

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

2、寫一個方法太惠,使用@Test注解

package com.course.LearnTestNgCases;

import org.testng.annotations.Test;

/*
test注解
 */
public class BasicAnnotation {
    @Test
    public void testcaseone(){
        System.out.println("這是最基本的注解磨淌,用來把方法標(biāo)記為測試的一部分");
    }
}

3、@BeforeMethod @AfterMethod注解

package com.course.LearnTestNgCases;

import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/*
test注解
 */
public class BasicAnnotation {
    @Test
    public void testcaseone(){
        System.out.println("這是最基本的注解凿渊,用來把方法標(biāo)記為測試的一部分");
    }
    @Test
    public void testcasetwo(){
        System.out.println("第二個測試方法");
    }
    @BeforeMethod
    public void beforemethod(){
        System.out.println("測試方法之前運(yùn)行");
    }
    @AfterMethod
    public void aftermethod(){
        System.out.println("測試方法之后運(yùn)行");
    }
}

測試結(jié)果為:


image.png

4梁只、@BeforeClass @AfterClass 注解

package com.course.LearnTestNgCases;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.testng.annotations.*;

/*
test注解
 */
public class BasicAnnotation {
    public BasicAnnotation(){
        System.out.println("kkkkkk");
    }
    @Test
    public void testcaseone(){
        System.out.println("這是最基本的注解,用來把方法標(biāo)記為測試的一部分");
    }
    @Test
    public void testcasetwo(){
        System.out.println("第二個測試方法");
    }
    @BeforeMethod
    public void beforemethod(){
        System.out.println("測試方法之前運(yùn)行");
    }
    @AfterMethod
    public void aftermethod(){
        System.out.println("測試方法之后運(yùn)行");
    }
    @BeforeClass
    public void beforeclass(){
        System.out.println("在類之前運(yùn)行");
    }
    @AfterClass
    public void afterclass(){
        System.out.println("在類之后運(yùn)行");
    }
}

測試結(jié)果:


image.png

5埃脏、@BeforeTest搪锣、@AfterTest注解

package com.course.LearnTestNgCases;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.testng.annotations.*;

/*
test注解
 */
public class BasicAnnotation {
    @Test
    public void testcaseone(){
        System.out.println("這是最基本的注解,用來把方法標(biāo)記為測試的一部分");
    }
    @Test
    public void testcasetwo(){
        System.out.println("第二個測試方法");
    }
    @BeforeSuite
    public void beforesuite(){
        System.out.println("beforesuite測試套件彩掐,在BeforeClass類之前運(yùn)行");
    }
    @AfterSuite
    public void aftersuite(){
        System.out.println("aftersuite測試套件构舟,在Afterclass類之后運(yùn)行");
    }
    @BeforeTest
    public void beforetest(){
        System.out.println("beforetest在測試之前運(yùn)行");
    }
    @AfterTest
    public void aftertest(){
        System.out.println("aftertest在測試之后運(yùn)行");
    }
    @BeforeClass
    public void beforeclass(){
        System.out.println("在類之前運(yùn)行");
    }
    @AfterClass
    public void afterclass(){
        System.out.println("在類之后運(yùn)行");
    }
    @BeforeMethod
    public void beforemethod(){
        System.out.println("測試方法之前運(yùn)行");
    }
    @AfterMethod
    public void aftermethod(){
        System.out.println("測試方法之后運(yùn)行");
    }

}

測試結(jié)果:


image.png

6、@BeforeSuite堵幽、@AfterSuite注解

package com.course.LearnTestNgCases;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.testng.annotations.*;

/*
test注解
 */
public class BasicAnnotation {
    @Test
    public void testcaseone(){
        System.out.println("這是最基本的注解狗超,用來把方法標(biāo)記為測試的一部分");
    }
    @Test
    public void testcasetwo(){
        System.out.println("第二個測試方法");
    }
    @BeforeMethod
    public void beforemethod(){
        System.out.println("測試方法之前運(yùn)行");
    }
    @AfterMethod
    public void aftermethod(){
        System.out.println("測試方法之后運(yùn)行");
    }
    @BeforeClass
    public void beforeclass(){
        System.out.println("在類之前運(yùn)行");
    }
    @AfterClass
    public void afterclass(){
        System.out.println("在類之后運(yùn)行");
    }
    @BeforeSuite
    public void beforesuite(){
        System.out.println("beforesuite測試套件,在BeforeClass類之前運(yùn)行");
    }
    @AfterSuite
    public void aftersuite(){
        System.out.println("aftersuite測試套件朴下,在Afterclass類之后運(yùn)行");
    }
}

測試結(jié)果:


image.png

總結(jié):
testng的注解執(zhí)行順序如下:
BeforeSuite > BeforeTest > BeforeClass > BeforeMethod > Test
測試套件 > 測試組 > 測試類 > 測試方法 >具體的測試方法

7努咐、套件測試:先創(chuàng)建3個類,其中一個是SuiteConfig類

package com.course.LearnTestNgCases.Suite;

import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;

/*
測試套件的配置
 */
public class SuiteConfig {

    @BeforeSuite
    public void beforesuite(){
        System.out.println("在測試套件前執(zhí)行");
    }

    @AfterSuite
    public void aftersuite(){
        System.out.println("在測試套件后執(zhí)行");
    }
}

另外兩個是LoginTest類和PayTest類

package com.course.LearnTestNgCases.Suite;

import org.testng.annotations.Test;

/*
測試類
 */
public class LoginTest {

    @Test
    public void logintest(){
        System.out.println("登錄成功");
    }
}

package com.course.LearnTestNgCases.Suite;

import org.testng.annotations.Test;

public class PayTest {
    @Test
    public void paytest(){
        System.out.println("支付成功");
    }
}

創(chuàng)建suite.xml 名稱可以隨意起殴胧,該文件中寫測試類執(zhí)行的順序:


image.png

與上面講的注解順序一致渗稍。
把suite.xml文件寫全,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="學(xué)習(xí)testng的suite測試套件">
    <test name="測試LoginTest類" >
        <classes>
            <class name="com.course.LearnTestNgCases.Suite.LoginTest">
                <include name="com.course.LearnTestNgCases.Suite.LoginTest.logintest"/>
            </class>
            <class name="com.course.LearnTestNgCases.Suite.SuiteConfig"/>
        </classes>
    </test>

    <test name="測試PayTest類">
        <classes>
            <class name="com.course.LearnTestNgCases.Suite.SuiteConfig"/>
            <class name="com.course.LearnTestNgCases.Suite.PayTest"/>
        </classes>
    </test>
</suite>
image.png

右鍵運(yùn)行suite.xml文件团滥,查看運(yùn)行結(jié)果:


image.png

正常情況下竿屹,這些寫會比較合理:

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="學(xué)習(xí)testng的suite測試套件">
    <test name="測試LoginTest類" >
        <classes>
            <class name="com.course.LearnTestNgCases.Suite.SuiteConfig"/>
            <class name="com.course.LearnTestNgCases.Suite.LoginTest"/>
            <class name="com.course.LearnTestNgCases.Suite.PayTest"/>
        </classes>
    </test>
</suite>

運(yùn)行結(jié)果:


image.png

8、忽略測試
@Test(enabled = false) 忽略測試
@Test(enabled = true) 執(zhí)行測試

package com.course.LearnTestNgCases;

import org.testng.annotations.Test;

public class IgnoreTest {
    @Test
    public void ignore1(){
        System.out.println("執(zhí)行測試");
    }
    @Test(enabled = false)
    public void ignore2(){
        System.out.println("enable=false 忽略測試");
    }
    @Test(enabled = true)
    public void ignore3(){
        System.out.println("enable=true 執(zhí)行測試");
    }
}

執(zhí)行結(jié)果:


image.png

9灸姊、testng組測試
9.1拱燃、方法分組
在方法上加@Test(groups="groupsname")進(jìn)行分組
@BeforeGroups(“groupsname”) 、@AfterGroups(“groupsname”)注解厨钻,在對應(yīng)的測試組執(zhí)行之前執(zhí)行扼雏,在對應(yīng)的測試組執(zhí)行之后執(zhí)行

package com.course.LearnTestNgCases.Groups;

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

public class TestGroups {

    @Test(groups = "server")
    public void testserver(){
        System.out.println("服務(wù)端測試用例");
    }
    @Test(groups = "client")
    public void testclient(){
        System.out.println("客戶端測試用例");
    }

    @BeforeGroups("server")
    public void beforeservergroups(){
        System.out.println("在服務(wù)端測試用例執(zhí)行之前執(zhí)行");
    }
    @AfterGroups("server")
    public void afterservergroups(){
        System.out.println("在服務(wù)端測試用例執(zhí)行之后執(zhí)行");
    }

    @BeforeGroups("client")
    public void beforeclientgroups(){
        System.out.println("在客戶端測試用例執(zhí)行之前執(zhí)行");
    }
    @AfterGroups("client")
    public void afterclientgroups(){
        System.out.println("在客戶端測試用例執(zhí)行之后執(zhí)行");
    }

}

groups組的名字要一致坚嗜。

執(zhí)行結(jié)果:


image.png

9.2 類分組
先建3個類,在每個類上加不同的groups诗充,在testng.xml中通過配置groups來分組執(zhí)行測試用例

image.png

groupclass1類,@Test(groups = "student"):

package com.course.LearnTestNgCases.Groups;

import org.testng.annotations.Test;

@Test(groups = "student")
public class groupclass1 {

    public void testgroupclass1(){
        System.out.println("這是student分組下的測試用例1");
    }
}

groupclass2類苍蔬,@Test(groups = "student")

package com.course.LearnTestNgCases.Groups;

import org.testng.annotations.Test;

@Test(groups = "student")
public class groupclass2 {
    public void testgroupclass2(){
        System.out.println("這是student分組下的測試用例2");
    }
}

groupclass3類,@Test(groups = "teacher"),和上面兩個類的分組不同

package com.course.LearnTestNgCases.Groups;

import org.testng.annotations.Test;

@Test(groups = "teacher")
public class groupclass3 {

    public void testgroupclass3(){
        System.out.println("這是teacher分組下的測試用例");
    }
}

新建一個xml蝴蜓,添加groups配置碟绑,只包含student組


image.png

右鍵執(zhí)行該xml,查看結(jié)果中只執(zhí)行了groupclass1和groupclass2兩個類茎匠,groupclass3類沒有執(zhí)行格仲。


image.png

加上teacher分組,再次執(zhí)行就能看到執(zhí)行了groupclass3類


image.png
image.png

10诵冒、異常測試:
執(zhí)行中有異常凯肋,期望測試結(jié)果就是異常,用@Test(expectedExceptions = RuntimeException.class)注解使測試用例執(zhí)行結(jié)果為正常

package com.course.LearnTestNgCases;

import org.testng.annotations.Test;

/*
異常測試汽馋,期望測試結(jié)果就是異常侮东,用@Test(expectedExceptions = RuntimeException.class)注解使測試用例執(zhí)行結(jié)果為正常
 */
public class Exceptiontest {

    @Test(expectedExceptions = RuntimeException.class)
    public void testExceptiontest(){
        System.out.println("執(zhí)行時會有異常,預(yù)期結(jié)果就是希望有異常豹芯,所以測試用例會執(zhí)行通過悄雅。本用例要拋出一個異常");


        //主動拋出一個異常或者制造一個異常
//        throw new RuntimeException();
        int a=10;
        int b=0;
        int c=a/b;//b為0铁蹈,肯定會報異常宽闲。

    }
}

11、依賴測試
11.1 依賴方法
dependsOnMethods = {"testfirst"}
代碼如下:

    @Test
    public void testfirst(){
        System.out.println("testfirst方法執(zhí)行通過后再執(zhí)行下面的方法握牧,執(zhí)行失敗不執(zhí)行下面的方法");
    }

    @Test(dependsOnMethods = {"testfirst"})
    public void testsencond(){
        System.out.println("testsencond方法依賴testfirst方法");

    }

11.2 依賴組
dependsOnGroups = "testthird"
代碼如下:


    @Test(groups = "testthird")
    public void testthird(){
        System.out.println("testthird方法執(zhí)行通過后再執(zhí)行下面依賴該組的方法容诬,執(zhí)行失敗不執(zhí)行下面的方法");
    }

    @Test(dependsOnGroups = "testthird")
    public void testfourth(){
        System.out.println("testfourth方法依賴testthird方法");
    }

12、參數(shù)化測試
12.1 參數(shù)化測試沿腰,xml文件參數(shù)化
在xml中寫入:


image.png

在類中寫入@parameter({"name","age"})注解放案,輸入?yún)?shù)


image.png

12.2 參數(shù)化測試,@DataProvider方法
先寫一個dataprovider方法矫俺,定義一個name值,返回類型必須是Object[][]或者Iterator<Object[]>掸冤,參數(shù)個數(shù)可以為一個或者多個

    //數(shù)據(jù)方法
    @DataProvider(name = "data")
    public Object[][] dataprovider(){
        Object[][] objects = new Object[][]{
                {"zhaowei",40,"kkk",180},
                {"suyoupeng",42,"jjj",170},
                {"linxinru",43,"ppp",160}
        };
        return objects;
    }

測試用例方法,@Test注解中要寫(dataProvider = "data")厘托,data名稱與上面定義的name值一致,參數(shù)值傳了3遍稿湿,執(zhí)行結(jié)果有三條記錄:

    //測試用例
    @Test(dataProvider = "data")
    public void testdataprovider(String name,int age,String address,int height){
        System.out.println("通過DataProvider傳過來的name是"+name+",age是"+age+",地址是"+address+",身高是"+height);
    }
image.png

定義兩個方法铅匹,分別傳入不同的值,通過dataprovider傳值
方法代碼如下:

    @Test(dataProvider = "datatwo")
    public void testdologin(String username,String password){
        System.out.println("登錄成功饺藤,用戶名是"+username+",密碼是"+password);
    }
    @Test(dataProvider = "datatwo")
    public void testgetmerchantlist(int merchantid,String merchantname,String merchantaddress){
        System.out.println("查詢商戶列表成功包斑,商戶id是"+merchantid+",商戶名稱是"+merchantname+",商戶地址是"+merchantaddress);
    }

dataprovider方法為:

    //數(shù)據(jù)方法二,根據(jù)不同的方法傳不同的值進(jìn)來
    @DataProvider(name = "datatwo")
    public Object[][] dataprovidertwo(Method method){
        Object[][] result = null;
        if (method.getName().equals("testdologin")){
             result = new Object[][]{
                    {"18600228000","123456"}
            };
        }else if (method.getName().equals("testgetmerchantlist")){
            result = new Object[][]{
                    {38159,"秦小姐洗車門店","北京市通州區(qū)"}
            };
        }
        return result;

    }

根據(jù)調(diào)Method類的getName方法獲取不同方法的名稱流礁,判斷不同的名稱返回不同的值。


image.png

擴(kuò)展@dataprovider的使用:
12.3 從另一個類中獲取數(shù)據(jù)依賴
第一個類中專門寫數(shù)據(jù)

package com.course.LearnTestNgCases.GroupsAndDataprovider;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/*
專門提供數(shù)據(jù)的類罗丰,在使用數(shù)據(jù)信息的地方用@Test(dataProvider = "data",dataProviderClass = class111.class)來引入
 */

public class class111 {

    @DataProvider(name = "data")
    public static Object[][] dataprovider(){
        Object[][] objects = new Object[][]{
                {"zhaowei",40,"kkk",180},
                {"suyoupeng",42,"jjj",170},
                {"linxinru",43,"ppp",160}
        };
        return objects;
    }

}

第二個類用@Test(dataProvider = "data",dataProviderClass = class111.class)來引入測試數(shù)據(jù)

package com.course.LearnTestNgCases.GroupsAndDataprovider;

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


public class class222 {

    //測試用例神帅,從class111類中引入測試數(shù)據(jù)
    @Test(dataProvider = "data",dataProviderClass = class111.class)
    public void testdataprovider(String name,int age,String address,int height){
        System.out.println("通過DataProvider傳過來的name是"+name+",age是"+age+",地址是"+address+",身高是"+height);
    }
}

12.4 返回類型為迭代器Iterator<Object[]>
在class111類中寫入如下代碼:

    @DataProvider(name="getmerchatidAndname")
    public static Iterator<Object[]> getmerchatidAndnameDataProvider() throws IOException {
        //定義Object[]類型的list
        List<Object[]> result=new ArrayList<Object[]>();
        //創(chuàng)建sqlsession的實(shí)例
        SqlSession session= DatabaseUtil.getsqlsession();
        //從數(shù)據(jù)庫中獲取數(shù)據(jù)信息
        List<Object> alldata=session.selectList("getmerchatidAndname");


       //從數(shù)據(jù)庫中獲取的數(shù)據(jù)類型是Object,該方法返回的類型是Object[],所以需要做類型轉(zhuǎn)換
        //用foreach循環(huán)對alldata中的每一條數(shù)據(jù)做形式轉(zhuǎn)換萌抵,并存儲到result中
        for (Object u : alldata) {
            //做一個形式轉(zhuǎn)換
            result.add(new Object[] { u });
        }
        //然后result的迭代器類型的數(shù)據(jù)
        return result.iterator();


        //下面的代碼與上面的代碼功能類似就是做一個形式轉(zhuǎn)換找御,從Object類型轉(zhuǎn)換成Object[]類型
        //生成一個迭代器,對迭代器中的每一個數(shù)據(jù)做形式轉(zhuǎn)換
        //迭代器的方法有hasNext()是否有值绍填,有值為真霎桅,沒值為假,it.next()獲取具體的值
//        Iterator it=alldata.iterator();
//        while(it.hasNext()){
//            result.add(new Object[] { it.next() });
//        }
//        return  result.iterator();

    }

然后在model層建一個實(shí)體類:

package com.course.model;

public class Cfwshop {
    private int id;
    private String shop_name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getShop_name() {
        return shop_name;
    }

    public void setShop_name(String shop_name) {
        this.shop_name = shop_name;
    }

    @Override
    public String toString() {
        return "Cfwshop{" +
                "id=" + id +
                ", shop_name='" + shop_name + '\'' +
                '}';
    }
}

再在SQLMapper.xml中寫sql語句,id與session.selectList("getmerchatidAndname");中的值一致讨永。

    <select id="getmerchatidAndname" resultType="com.course.model.Cfwshop">
        select id,shop_name from autosvc.cfw_shop LIMIT 10;
    </select>

再在測試類中調(diào)dataprovider數(shù)據(jù)類,入?yún)㈩愋褪荂fwshop cfwshop 實(shí)體類滔驶,通過get方法獲取具體值。

    //測試用例卿闹,從class111類中引入Iterator類型的測試數(shù)據(jù)
    //需要創(chuàng)建一個Cfwshop的實(shí)體類揭糕,這樣才能接收到具體的值
    @Test(dataProvider = "getmerchatidAndname",dataProviderClass = class111.class)
    public void testgetmerchatidAndname(Cfwshop cfwshop){
        System.out.println("商戶id為:"+cfwshop.getId()+",商戶名稱為:"+cfwshop.getShop_name());

    }

13、多線程測試

@Test(invocationCount = 10,threadPoolSize = 3)

package com.course.LearnTestNgCases.MultiThread;

import org.testng.annotations.Test;

/*
驗證使用多線程
 */
public class MultiThreadOnAnnotation {

    //設(shè)置10個線程比原,3個線程池插佛,從線程池中取3個線程,用3個線程執(zhí)行10遍測試用例,如果不設(shè)置threadPoolSize量窘,就會默認(rèn)用一個線程執(zhí)行10遍雇寇,所以要設(shè)置一個線程池
    @Test(invocationCount = 10,threadPoolSize = 3)
    public void testMultiThreadOnAnnotation(){
        System.out.println("1");
        System.out.printf("Thread id: %s%n",Thread.currentThread().getId());
    }
}

image.png

在xml中通過配置線程數(shù)來控制線程:
先寫一個測試類,有3個測試方法:

package com.course.LearnTestNgCases.MultiThread;

import org.testng.annotations.Test;

public class MultiThreadOnXml {
    @Test
    public void test1(){
        System.out.printf("Thread Id : %s%n",Thread.currentThread().getId());
    }

    @Test
    public void test2(){
        System.out.printf("Thread Id : %s%n",Thread.currentThread().getId());
    }

    @Test
    public void test3(){
        System.out.printf("Thread Id : %s%n",Thread.currentThread().getId());
    }
}

查看xml文件:
parallel="methods":

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="methods" thread-count="3">
    <!--
    parallel="methods"蚌铜,methods級別锨侯,所有用例都可以在不同的線程中執(zhí)行
     thread-count:代表了最大并發(fā)線程數(shù)
      xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
    -->
    <test name="testthread">
        <classes>
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
        </classes>
    </test>
</suite>

執(zhí)行結(jié)果為:


image.png

parallel="classes":

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="classes" thread-count="3">
    <!--
    parallel="methods"冬殃,methods級別囚痴,所有用例都可以在不同的線程中執(zhí)行
     thread-count:代表了最大并發(fā)線程數(shù)
      xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池
      
    parallel="classes"审葬,classs級別:
           相同的class tag 下的用例在同一個線程中執(zhí)行
           不同的class tag 下的用例可以在不同的線程中執(zhí)行
    -->
    <test name="testthread">
        <classes>
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
        </classes>
    </test>
</suite>

測試結(jié)果:


image.png

創(chuàng)建不同的classs:

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="classes" thread-count="6" >
    <!--
    parallel="methods"深滚,methods級別,所有用例都可以在不同的線程中執(zhí)行
     thread-count:代表了最大并發(fā)線程數(shù)
      xml文件配置這種方式不能指定線程池涣觉,只有方法上才可以指定線程池

    parallel="classes"痴荐,classs級別:
           相同的class tag 下的用例在同一個線程中執(zhí)行
           不同的class tag 下的用例可以在不同的線程中執(zhí)行

    -->
    <test name="testthread">
        <classes name="demo1">
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
        </classes>
        <classes name="demo2">
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
        </classes>

    </test>
</suite>

測試結(jié)果,線程會有些亂:


image.png
parallel="tests",tests級別:
       相同的test tag 下的用例在同一個線程中執(zhí)行
       不同的test tag 下的用例可以在不同的線程中執(zhí)行
<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="tests" thread-count="3" >
    <!--
    parallel="methods"官册,methods級別生兆,所有用例都可以在不同的線程中執(zhí)行
     thread-count:代表了最大并發(fā)線程數(shù)
      xml文件配置這種方式不能指定線程池,只有方法上才可以指定線程池

    parallel="classes"膝宁,classs級別:
           相同的class tag 下的用例在同一個線程中執(zhí)行
           不同的class tag 下的用例可以在不同的線程中執(zhí)行

    parallel="tests"鸦难,tests級別:
           相同的test tag 下的用例在同一個線程中執(zhí)行
           不同的test tag 下的用例可以在不同的線程中執(zhí)行
    -->
    <test name="testthread">
        <classes name="demo1">
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
        </classes>

    </test>
</suite>
image.png

設(shè)置不同的tests:

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="thread" parallel="tests" thread-count="3" >
    <!--
    parallel="methods"根吁,methods級別,所有用例都可以在不同的線程中執(zhí)行
     thread-count:代表了最大并發(fā)線程數(shù)
      xml文件配置這種方式不能指定線程池合蔽,只有方法上才可以指定線程池

    parallel="classes"击敌,classs級別:
           相同的class tag 下的用例在同一個線程中執(zhí)行
           不同的class tag 下的用例可以在不同的線程中執(zhí)行

    parallel="tests",tests級別:
           相同的test tag 下的用例在同一個線程中執(zhí)行
           不同的test tag 下的用例可以在不同的線程中執(zhí)行
    -->
    <test name="testthread">
        <classes name="demo1">
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
        </classes>

    </test>
    <test name="testthread2">
        <classes name="demo2">
            <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXmltwo"/>
        </classes>

    </test>
</suite>

測試結(jié)果:


image.png

總結(jié):正常情況下辈末,用methods級別會比較多愚争,多條用例在多個線程下執(zhí)行,會節(jié)省測試時間挤聘。

14轰枝、超時測試
@Test(timeOut = 3000)//超時等待3000毫秒,就是3秒

package com.course.LearnTestNgCases;

import org.testng.annotations.Test;

/*
超時測試组去,測試中調(diào)接口可能會超時鞍陨,導(dǎo)致調(diào)接口失敗,通過設(shè)置超時時間从隆,使用例執(zhí)行通過诚撵。
 */
public class TimeOutTest {

    @Test(timeOut = 3000)//超時等待3000毫秒,就是3秒
    public void test1() throws InterruptedException {
        //設(shè)置一個線程键闺,讓線程休眠2000毫秒,等待時間大于休眠時間寿烟,所以用例會執(zhí)行成功
        Thread.sleep(2000);
    }

    @Test(timeOut = 3000)
    public void test2() throws InterruptedException {
        //設(shè)置一個線程,讓線程休眠4000毫秒辛燥,等待時間小于休眠時間筛武,所以用例會執(zhí)行失敗
        Thread.sleep(4000);
    }

}

注:更多的超時設(shè)置可以參考
摘抄自:https://blog.csdn.net/u011191463/article/details/78664896

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挎塌,隨后出現(xiàn)的幾起案子徘六,更是在濱河造成了極大的恐慌,老刑警劉巖榴都,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件待锈,死亡現(xiàn)場離奇詭異,居然都是意外死亡嘴高,警方通過查閱死者的電腦和手機(jī)竿音,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拴驮,“玉大人谍失,你說我怎么就攤上這事∮ㄌ溃” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵颠印,是天一觀的道長纲岭。 經(jīng)常有香客問我抹竹,道長,這世上最難降的妖魔是什么止潮? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任窃判,我火速辦了婚禮,結(jié)果婚禮上喇闸,老公的妹妹穿的比我還像新娘袄琳。我一直安慰自己,他們只是感情好燃乍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布唆樊。 她就那樣靜靜地躺著,像睡著了一般刻蟹。 火紅的嫁衣襯著肌膚如雪逗旁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天舆瘪,我揣著相機(jī)與錄音片效,去河邊找鬼。 笑死英古,一個胖子當(dāng)著我的面吹牛淀衣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播召调,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼膨桥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了某残?” 一聲冷哼從身側(cè)響起国撵,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玻墅,沒想到半個月后介牙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澳厢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年环础,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剩拢。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡线得,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出徐伐,到底是詐尸還是另有隱情脐雪,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布顿仇,位于F島的核電站,受9級特大地震影響祸穷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勺三,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一雷滚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吗坚,春花似錦祈远、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至炊汹,卻和暖如春躬充,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讨便。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工充甚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霸褒。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓伴找,卻偏偏與公主長得像,于是被迫代替她去往敵國和親废菱。 傳聞我的和親對象是個殘疾皇子技矮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 感謝原作者的奉獻(xiàn),原作者博客地址:http://blog.csdn.net/zhu_ai_xin_520/arti...
    狼孩閱讀 14,006評論 1 35
  • TestNG默認(rèn)情況下殊轴,會生產(chǎn)兩種類型的測試報告HTML的和XML的衰倦。 測試報告位于 "test-output" ...
    我為峰2014閱讀 7,749評論 0 5
  • 當(dāng)從微博這一八卦發(fā)動機(jī)上獲悉了某個消息之后孽文,就可以預(yù)料到又要有無數(shù)的公眾號和抓熱點(diǎn)的人要趕緊下手驻襟,搶占高地了。 果...
    梅疏雨閱讀 326評論 0 0
  • 朋友最近受腰痛困擾芋哭,多次求醫(yī)無果沉衣,連吃飯都不能久立,突然覺得我們是不是就這樣老了减牺?可是豌习,我們怎么可以老存谎?我們還在年...
    一百年不孤獨(dú)閱讀 178評論 0 0