美文网首页
Moco+HttpClient+TestNg+MyBatis+M

Moco+HttpClient+TestNg+MyBatis+M

作者: 零下的雨 | 来源:发表于2018-12-06 14:28 被阅读0次

    接口框架实现整体步骤:

    resource层:
    底层是application.properties配置路径文件
    databaseconfig.xml配置数据库文件
    mapper文件夹中是sqlmapper.xml的配置文件
    testng.xml配置文件 测试类的配置文件
    还有moco的接口文件,json格式的,需要一个jar包依赖

    utils层:
    configfile类是从application.properties配置文件中获取url和参数信息,string、json、map等类型
    databaseutil类是加载database.xml文件,处理从数据库中获取数据信息

    model层:
    对应的实体类是与数据库的表一致、还可以定义接口名的枚举类和参数url的枚举类

    config层:
    配置测试报告,extenttestngrepoter监听器类

    case层:
    写具体的接口类,用httpclient去实现每个接口的调用和结果验证
    接口与接口之间的数据依赖可以通过testng的数据依赖去传递。

    一、创建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>
    

    二、创建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
    

    三、创建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.加载数据库驱动 -->
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <!-- 2.数据库连接地址 -->
                    <property name="url" value="jdbc:mysql://localhost:3306/dbgirl"/>
                    <!-- 数据库用户... -->
                    <property name="username" value="rootname"/>
                    <!-- 数据库密码... -->
                    <property name="password" value="passname"/>
                </dataSource>
            </environment>
        </environments>
        <!-- 注册映射文件:java对象与数据库之间的xml文件路径! -->
        <mappers>
            <mapper resource="mapper/SQLMapper.xml"/>
        </mappers>
    </configuration>
    

    四、在resource下创建mapper文件夹,创建SQLMapper.xml文件,先不写内容
    testng.xml文件也可以先建立


    image.png

    五、利用springboot中的spring-data-jpa创建实体类,创建数据库表,可以参考在《springboot学习(四)》中的代码。
    也可以手动去创建数据库表。
    六、在java下创建cases、config、model、utils文件夹,分别放不同的内容
    现在model中创建对应数据库表的实体类,代码如下:
    只举其中一个表的例子,字段值要与数据库表的值一致

    package com.course.model;
    
    import lombok.Data;
    
    @Data
    public class User {
        private Integer id;  //id是主键,并且会自增,调接口时不需要传参数id,传其他参数就可以
        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类,补充一下源码:

    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中写一个枚举类,把接口写成枚举类型,保证传进来的接口名称正确

    package com.course.model;
    
    public enum InterfaceName {
        LOGIN,UPDATEUSERINFO,GETUSERLIST,GETUSERINFO,ADDUSER
    }
    
    

    七、在config文件中创建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="用户管理系统测试套件">
        <test name="用户管理系统测试用例">
            <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文件夹下创建的ExtentTestNGIReporterListener类的路径。

    在config文件夹下增加监听类:不用改类中的内容:

    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;
                }
                //统计suite下的成功、失败、跳过的总用例数
                int suiteFailSize=0;
                int suitePassSize=0;
                int suiteSkipSize=0;
                ExtentTest suiteTest=null;
                //存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
                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){
                        //没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
                        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());
                        //统计SuiteResult下的数据
                        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() {
            //文件夹不存在的话进行创建
            File reportDir= new File(OUTPUT_FOLDER);
            if(!reportDir.exists()&& !reportDir .isDirectory()){
                reportDir.mkdir();
            }
            ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
            // 设置静态文件的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) {
            //存在父节点时,获取父节点的标签
            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) {
                //调整用例排序,按时间排序
                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="";
                    //如果有参数,则使用参数的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{
                        //作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
                        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报告,进入idea,按着图中去点击


    image.png

    选择左侧测试方法,点击listener,勾选Use default reporters,然后点击确定


    image.png

    右键执行testng.xml文件,就能看到test-output文件夹,查看生成的报告:


    image.png

    右键点击index.html选择copy path,粘贴路径在浏览器中查看报告信息。

    八、在utils文件中创建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 = "";
            //如果传进来的接口名称正确,就返回相应接口的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中创建DatabaseUtil,从databaseconfig.xml配置文件中取配置信息,创建sqlsession并返回,在case类中就不用再写获取数据库信息了

    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封装起来,从数据库中获取数据
     */
    public class DatabaseUtil {
        public static SqlSession getsqlSession() throws IOException {
            //获取配置的资源文件
            Reader reader = Resources.getResourceAsReader("databaseConfig.xml");
            //得到SqlSessionFactory,使用类加载器加载xml文件
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
            //得到sqlsession对象,这个对象就能执行配置文件中的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注解,在用例执行前先获取接口的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类中调getsqlSession方法,直接获取到sqlsession对象,可以执行sql语句,不需要配置数据库信息
            SqlSession sqlSession = DatabaseUtil.getsqlSession();
            //调selectone方法,通过mybatis中的sql语句查数据库表,并返回结果赋值给logincase对象
            //sqlstatement与SQLMapper.xml中的id一致,33是传的参数id
            loginCase loginCase = sqlSession.selectOne("logincase",33);
            //打印查询的结果,结果就是从数据库中查询到的结果
            System.out.println(loginCase.toString());
            System.out.println(TestConfig.loginuri);
        }
    
        //调数据库表中第二行数据,可以认为是用户名和密码错误,登录失败的接口
        @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的类,给该类创建了多个uri名称的属性,这样uri名称就不用随便再起,直接从这个类中调用就行。

    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

    十一、执行loginTest类时遇到问题:
    报org.apache.ibatis.exceptions.PersistenceException:的错误
    是因为mybatis没有连接上数据库,或者是数据库和实体类的对应关系没有建立起来
    需要修改databaseconfig.xml文件,数据库url为:

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

    之前该url是写在yml中,&符号不需要转义,写到xml中需要对 & 转义成&

    还需要加上
    <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" /> 要写多个,哪个实体类对应的有数据库表都要单独写,要不然也会报错。

    同时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的学习总结:

    以上的框架中缺少moco接口,请参考《Moco接口框架应用实战》。
    下面会在前十一章搭建框架的基础上,利用HttpClient去调接口验证测试。
    12.1 moco的接口:
    登录接口


    image.png

    还有一个调列表接口,不再贴图:

    TestDologin 测试类:

    public class TestDologin {
    
    
        //设置全局变量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);
            //获取所有接口的参数值,doLoginParams接口是post入参json格式的,可以从配置文件中调,getMerchantListuri中已拼接了接口
            TestConfig.doLoginParams = ConfigFile.GetParams(ParamsName.DOLOGINPARAMS);
        }
        
        /*
        从配置文件中获取全部的参数转换成String类型入参
         */
        @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,下一个接口入参时使用cookie
            cookieStore = httpClient.getCookieStore();
    
            //调moco的接口可以用这个
            HttpPost httpPost = new HttpPost(TestConfig.doLoginuri);
            //设置参数
            //使用json入参,入参是中文时不行。
    
            //设置请求头header
            httpPost.setHeader("Content-Type","application/json;charset=gbk");
            //传入参数
    
            StringEntity entity=new StringEntity(TestConfig.doLoginParams.toString(),"gbk");
            httpPost.setEntity(entity);
    
            //获取返回结果
            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();
    
            //参数已经拼接在url中
            HttpGet httpGet = new HttpGet(TestConfig.getMerchantListuri);
    
    
    
            //通过moco接口,可以使用setcookiestore方法从上一个接口获取token值
            httpClient.setCookieStore(TestDologin.cookieStore);
    
            //调取真实的接口只能设置header,传值token
    //        httpGet.setHeader("token",TestDologin.token);
    
            HttpResponse response = httpClient.execute(httpGet);
            /*
            下面要验证几种断言方法:
            1、判断接口返回是否是200
            2、判断一页返回的门店列表有20条
            3、判断从第5位开始评分由大到小
            4、判断与数据库中获取数据是否一致
             */
    
            //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、判断与数据库中获取数据是否一致
            SqlSession sqlSession = DatabaseUtil.getsqlsession();
            int marchantid = sqlSession.selectOne("getmarchantid",MerchantId);
            Assert.assertEquals(marchantid,MerchantId);
    
        }
    
    }
    
    

    总结:入参的处理方法有很多种:
    get请求:
    1、把参数拼接在url中

    getMerchantListuri=getMerchantList?longitude=116.41090393066406&latitude=39.96870040893555
    

    2、部分参数拼接在url中,其他参数和url拼接到一起

            //url要拼接参数
            URIBuilder uriBuidler = new URIBuilder(TestConfig.getCarWashDetailuri);
            //需要从其他类中依赖的参数值
            uriBuidler.setParameter("merchantId", String.valueOf(TestgetMerchantList.MerchantId));
    
            HttpGet httpGet = new HttpGet(uriBuidler.build());
    

    3、定义一个List<NameValuePair>,传入参数

            URIBuilder uriBuilder = new URIBuilder(TestConfig.getCarWashDetailuritwouri);
    
            //第一种写入参数的方法,定义一个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类型的参数
    配置文件中这些写:

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

    写一个工具类,定义一个获取object参数的方法

        //定义一个静态的方法,获取post接口的参数值,返回类型是Object类型
        public static Object GetParams(ParamsName name){
            if (name.equals(ParamsName.DOLOGINPARAMS)){
                Object doLoginParams = resourceBundle.getObject("doLoginParams");
                return doLoginParams;
            }
            return null;
        }
    

    在httpclient中入参直接这样写:

    TestConfig.doLoginParams = ConfigFile.GetParams(ParamsName.DOLOGINPARAMS);
    
            //传入参数
    
            StringEntity entity=new StringEntity(TestConfig.doLoginParams.toString(),"gbk");
            httpPost.setEntity(entity);
    

    注:直接配置文件中取String类型的参数值也行,不用传object的也可以

    2、配置文件中写部分参数

    doLoginParamstwo={"phone": "15677777777"}
    

    工具类中写,定义一个返回map类型参数的方法:

        //定义一个静态的方法,获取post接口的参数值,返回类型为map,这样有些参数可以依赖其他的接口,或者手动填写参数
        public static Map<String,Object> GetParamswithMap(ParamsName name){
            if (name == ParamsName.DOLOGINPARAMSTWOPARAMS){
                String doLoginParams = resourceBundle.getString("doLoginParamstwo");
                return JSON.parseObject(doLoginParams);
            }
            return null;
    
        }
    

    在httpclient中入参直接这样写:

    TestConfig.doLoginParamstwo = ConfigFile.GetParamswithMap(ParamsName.DOLOGINPARAMSTWOPARAMS);
    
            //传入参数
            Map<String,Object> map = TestConfig.doLoginParamstwo;
            map.put("validateCode",TestSendVerificationCode.validate_code);//也可以使用数据依赖传参数
    
    
            StringEntity entity=new StringEntity(map.toString(),"gbk");
            httpPost.setEntity(entity);
    

    十三、TestNg的扩展学习总结:
    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("这是最基本的注解,用来把方法标记为测试的一部分");
        }
    }
    
    

    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("这是最基本的注解,用来把方法标记为测试的一部分");
        }
        @Test
        public void testcasetwo(){
            System.out.println("第二个测试方法");
        }
        @BeforeMethod
        public void beforemethod(){
            System.out.println("测试方法之前运行");
        }
        @AfterMethod
        public void aftermethod(){
            System.out.println("测试方法之后运行");
        }
    }
    
    

    测试结果为:


    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("这是最基本的注解,用来把方法标记为测试的一部分");
        }
        @Test
        public void testcasetwo(){
            System.out.println("第二个测试方法");
        }
        @BeforeMethod
        public void beforemethod(){
            System.out.println("测试方法之前运行");
        }
        @AfterMethod
        public void aftermethod(){
            System.out.println("测试方法之后运行");
        }
        @BeforeClass
        public void beforeclass(){
            System.out.println("在类之前运行");
        }
        @AfterClass
        public void afterclass(){
            System.out.println("在类之后运行");
        }
    }
    
    

    测试结果:


    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("这是最基本的注解,用来把方法标记为测试的一部分");
        }
        @Test
        public void testcasetwo(){
            System.out.println("第二个测试方法");
        }
        @BeforeSuite
        public void beforesuite(){
            System.out.println("beforesuite测试套件,在BeforeClass类之前运行");
        }
        @AfterSuite
        public void aftersuite(){
            System.out.println("aftersuite测试套件,在Afterclass类之后运行");
        }
        @BeforeTest
        public void beforetest(){
            System.out.println("beforetest在测试之前运行");
        }
        @AfterTest
        public void aftertest(){
            System.out.println("aftertest在测试之后运行");
        }
        @BeforeClass
        public void beforeclass(){
            System.out.println("在类之前运行");
        }
        @AfterClass
        public void afterclass(){
            System.out.println("在类之后运行");
        }
        @BeforeMethod
        public void beforemethod(){
            System.out.println("测试方法之前运行");
        }
        @AfterMethod
        public void aftermethod(){
            System.out.println("测试方法之后运行");
        }
    
    }
    
    

    测试结果:


    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("这是最基本的注解,用来把方法标记为测试的一部分");
        }
        @Test
        public void testcasetwo(){
            System.out.println("第二个测试方法");
        }
        @BeforeMethod
        public void beforemethod(){
            System.out.println("测试方法之前运行");
        }
        @AfterMethod
        public void aftermethod(){
            System.out.println("测试方法之后运行");
        }
        @BeforeClass
        public void beforeclass(){
            System.out.println("在类之前运行");
        }
        @AfterClass
        public void afterclass(){
            System.out.println("在类之后运行");
        }
        @BeforeSuite
        public void beforesuite(){
            System.out.println("beforesuite测试套件,在BeforeClass类之前运行");
        }
        @AfterSuite
        public void aftersuite(){
            System.out.println("aftersuite测试套件,在Afterclass类之后运行");
        }
    }
    
    

    测试结果:


    image.png

    总结:
    testng的注解执行顺序如下:
    BeforeSuite > BeforeTest > BeforeClass > BeforeMethod > Test
    测试套件 > 测试组 > 测试类 > 测试方法 >具体的测试方法

    7、套件测试:先创建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("在测试套件前执行");
        }
    
        @AfterSuite
        public void aftersuite(){
            System.out.println("在测试套件后执行");
        }
    }
    
    

    另外两个是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("支付成功");
        }
    }
    
    

    创建suite.xml 名称可以随意起,该文件中写测试类执行的顺序:


    image.png

    与上面讲的注解顺序一致。
    把suite.xml文件写全,如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <suite name="学习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

    右键运行suite.xml文件,查看运行结果:


    image.png

    正常情况下,这些写会比较合理:

    <?xml version="1.0" encoding="UTF-8" ?>
    <suite name="学习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>
    

    运行结果:


    image.png

    8、忽略测试
    @Test(enabled = false) 忽略测试
    @Test(enabled = true) 执行测试

    package com.course.LearnTestNgCases;
    
    import org.testng.annotations.Test;
    
    public class IgnoreTest {
        @Test
        public void ignore1(){
            System.out.println("执行测试");
        }
        @Test(enabled = false)
        public void ignore2(){
            System.out.println("enable=false 忽略测试");
        }
        @Test(enabled = true)
        public void ignore3(){
            System.out.println("enable=true 执行测试");
        }
    }
    
    

    执行结果:


    image.png

    9、testng组测试
    9.1、方法分组
    在方法上加@Test(groups="groupsname")进行分组
    @BeforeGroups(“groupsname”) 、@AfterGroups(“groupsname”)注解,在对应的测试组执行之前执行,在对应的测试组执行之后执行

    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("服务端测试用例");
        }
        @Test(groups = "client")
        public void testclient(){
            System.out.println("客户端测试用例");
        }
    
        @BeforeGroups("server")
        public void beforeservergroups(){
            System.out.println("在服务端测试用例执行之前执行");
        }
        @AfterGroups("server")
        public void afterservergroups(){
            System.out.println("在服务端测试用例执行之后执行");
        }
    
        @BeforeGroups("client")
        public void beforeclientgroups(){
            System.out.println("在客户端测试用例执行之前执行");
        }
        @AfterGroups("client")
        public void afterclientgroups(){
            System.out.println("在客户端测试用例执行之后执行");
        }
    
    }
    
    

    groups组的名字要一致。

    执行结果:


    image.png

    9.2 类分组
    先建3个类,在每个类上加不同的groups,在testng.xml中通过配置groups来分组执行测试用例

    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

    右键执行该xml,查看结果中只执行了groupclass1和groupclass2两个类,groupclass3类没有执行。


    image.png

    加上teacher分组,再次执行就能看到执行了groupclass3类


    image.png image.png

    10、异常测试:
    执行中有异常,期望测试结果就是异常,用@Test(expectedExceptions = RuntimeException.class)注解使测试用例执行结果为正常

    package com.course.LearnTestNgCases;
    
    import org.testng.annotations.Test;
    
    /*
    异常测试,期望测试结果就是异常,用@Test(expectedExceptions = RuntimeException.class)注解使测试用例执行结果为正常
     */
    public class Exceptiontest {
    
        @Test(expectedExceptions = RuntimeException.class)
        public void testExceptiontest(){
            System.out.println("执行时会有异常,预期结果就是希望有异常,所以测试用例会执行通过。本用例要抛出一个异常");
    
    
            //主动抛出一个异常或者制造一个异常
    //        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方法执行通过后再执行下面的方法,执行失败不执行下面的方法");
        }
    
        @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方法执行通过后再执行下面依赖该组的方法,执行失败不执行下面的方法");
        }
    
        @Test(dependsOnGroups = "testthird")
        public void testfourth(){
            System.out.println("testfourth方法依赖testthird方法");
        }
    

    12、参数化测试
    12.1 参数化测试,xml文件参数化
    在xml中写入:


    image.png

    在类中写入@parameter({"name","age"})注解,输入参数


    image.png

    12.2 参数化测试,@DataProvider方法
    先写一个dataprovider方法,定义一个name值,返回类型必须是Object[][]或者Iterator<Object[]>,参数个数可以为一个或者多个

        //数据方法
        @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值一致,参数值传了3遍,执行结果有三条记录:

        //测试用例
        @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方法为:

        //数据方法二,根据不同的方法传不同的值进来
        @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,"秦小姐洗车门店","北京市通州区"}
                };
            }
            return result;
    
        }
    

    根据调Method类的getName方法获取不同方法的名称,判断不同的名称返回不同的值。


    image.png

    扩展@dataprovider的使用:
    12.3 从另一个类中获取数据依赖
    第一个类中专门写数据

    package com.course.LearnTestNgCases.GroupsAndDataprovider;
    
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.Test;
    /*
    专门提供数据的类,在使用数据信息的地方用@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)来引入测试数据

    package com.course.LearnTestNgCases.GroupsAndDataprovider;
    
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.Test;
    
    
    public class class222 {
    
        //测试用例,从class111类中引入测试数据
        @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[]>();
            //创建sqlsession的实例
            SqlSession session= DatabaseUtil.getsqlsession();
            //从数据库中获取数据信息
            List<Object> alldata=session.selectList("getmerchatidAndname");
    
    
           //从数据库中获取的数据类型是Object,该方法返回的类型是Object[],所以需要做类型转换
            //用foreach循环对alldata中的每一条数据做形式转换,并存储到result中
            for (Object u : alldata) {
                //做一个形式转换
                result.add(new Object[] { u });
            }
            //然后result的迭代器类型的数据
            return result.iterator();
    
    
            //下面的代码与上面的代码功能类似就是做一个形式转换,从Object类型转换成Object[]类型
            //生成一个迭代器,对迭代器中的每一个数据做形式转换
            //迭代器的方法有hasNext()是否有值,有值为真,没值为假,it.next()获取具体的值
    //        Iterator it=alldata.iterator();
    //        while(it.hasNext()){
    //            result.add(new Object[] { it.next() });
    //        }
    //        return  result.iterator();
    
        }
    

    然后在model层建一个实体类:

    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>
    

    再在测试类中调dataprovider数据类,入参类型是Cfwshop cfwshop 实体类,通过get方法获取具体值。

        //测试用例,从class111类中引入Iterator类型的测试数据
        //需要创建一个Cfwshop的实体类,这样才能接收到具体的值
        @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 {
    
        //设置10个线程,3个线程池,从线程池中取3个线程,用3个线程执行10遍测试用例,如果不设置threadPoolSize,就会默认用一个线程执行10遍,所以要设置一个线程池
        @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中通过配置线程数来控制线程:
    先写一个测试类,有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级别,所有用例都可以在不同的线程中执行
         thread-count:代表了最大并发线程数
          xml文件配置这种方式不能指定线程池,只有方法上才可以指定线程池
        -->
        <test name="testthread">
            <classes>
                <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
            </classes>
        </test>
    </suite>
    

    执行结果为:


    image.png

    parallel="classes":

    <?xml version="1.0" encoding="UTF-8" ?>
    <suite name="thread" parallel="classes" thread-count="3">
        <!--
        parallel="methods",methods级别,所有用例都可以在不同的线程中执行
         thread-count:代表了最大并发线程数
          xml文件配置这种方式不能指定线程池,只有方法上才可以指定线程池
          
        parallel="classes",classs级别:
               相同的class tag 下的用例在同一个线程中执行
               不同的class tag 下的用例可以在不同的线程中执行
        -->
        <test name="testthread">
            <classes>
                <class name="com.course.LearnTestNgCases.MultiThread.MultiThreadOnXml"/>
            </classes>
        </test>
    </suite>
    

    测试结果:


    image.png

    创建不同的classs:

    <?xml version="1.0" encoding="UTF-8" ?>
    <suite name="thread" parallel="classes" thread-count="6" >
        <!--
        parallel="methods",methods级别,所有用例都可以在不同的线程中执行
         thread-count:代表了最大并发线程数
          xml文件配置这种方式不能指定线程池,只有方法上才可以指定线程池
    
        parallel="classes",classs级别:
               相同的class tag 下的用例在同一个线程中执行
               不同的class tag 下的用例可以在不同的线程中执行
    
        -->
        <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>
    

    测试结果,线程会有些乱:


    image.png
    parallel="tests",tests级别:
           相同的test tag 下的用例在同一个线程中执行
           不同的test tag 下的用例可以在不同的线程中执行
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <suite name="thread" parallel="tests" thread-count="3" >
        <!--
        parallel="methods",methods级别,所有用例都可以在不同的线程中执行
         thread-count:代表了最大并发线程数
          xml文件配置这种方式不能指定线程池,只有方法上才可以指定线程池
    
        parallel="classes",classs级别:
               相同的class tag 下的用例在同一个线程中执行
               不同的class tag 下的用例可以在不同的线程中执行
    
        parallel="tests",tests级别:
               相同的test tag 下的用例在同一个线程中执行
               不同的test tag 下的用例可以在不同的线程中执行
        -->
        <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

    设置不同的tests:

    <?xml version="1.0" encoding="UTF-8" ?>
    <suite name="thread" parallel="tests" thread-count="3" >
        <!--
        parallel="methods",methods级别,所有用例都可以在不同的线程中执行
         thread-count:代表了最大并发线程数
          xml文件配置这种方式不能指定线程池,只有方法上才可以指定线程池
    
        parallel="classes",classs级别:
               相同的class tag 下的用例在同一个线程中执行
               不同的class tag 下的用例可以在不同的线程中执行
    
        parallel="tests",tests级别:
               相同的test tag 下的用例在同一个线程中执行
               不同的test tag 下的用例可以在不同的线程中执行
        -->
        <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>
    

    测试结果:


    image.png

    总结:正常情况下,用methods级别会比较多,多条用例在多个线程下执行,会节省测试时间。

    14、超时测试
    @Test(timeOut = 3000)//超时等待3000毫秒,就是3秒

    package com.course.LearnTestNgCases;
    
    import org.testng.annotations.Test;
    
    /*
    超时测试,测试中调接口可能会超时,导致调接口失败,通过设置超时时间,使用例执行通过。
     */
    public class TimeOutTest {
    
        @Test(timeOut = 3000)//超时等待3000毫秒,就是3秒
        public void test1() throws InterruptedException {
            //设置一个线程,让线程休眠2000毫秒,等待时间大于休眠时间,所以用例会执行成功
            Thread.sleep(2000);
        }
    
        @Test(timeOut = 3000)
        public void test2() throws InterruptedException {
            //设置一个线程,让线程休眠4000毫秒,等待时间小于休眠时间,所以用例会执行失败
            Thread.sleep(4000);
        }
    
    }
    
    

    注:更多的超时设置可以参考
    摘抄自:https://blog.csdn.net/u011191463/article/details/78664896

    相关文章

      网友评论

          本文标题:Moco+HttpClient+TestNg+MyBatis+M

          本文链接:https://www.haomeiwen.com/subject/brolcqtx.html