The technology department suddenly announced that all JAVA developers should know the interface automation test framework

Finishing some aspects of Java architecture, interview information (micro services, clusters, distributed, middleware, etc.), small partners need to be concerned about the public number [programmers], no way to collect their own.

Written in the front

Unit test Junit can completely meet the daily development self-test. Why should I study TestNG? It has affected my development progress!

Recently, the chief of the technology department suddenly announced that: all developers must be proficient in the automatic test framework TestNG, so there were complaints from colleagues on the top. Yes, I was complaining at the beginning, because I didn't know what it was, but I found it was really super practical from the beginning of contact to slowly writing test cases, after application to the project.

Let's see where it's better than Junit?

1, TestNG initial knowledge

TestNG [hereinafter referred to as TG] is a test framework for a large number of tests (such as multi interface data dependency during testing), which can be covered from simple unit test to integration test and even framework level test, so it is a very powerful test framework!

1. Preparation steps

There are three steps in the test case of conventional TG

  1. When writing business test code, insert Comments provided by TG

  2. Arrange the classes and group s to be tested and write them to testng.xml or build.xml

  3. Run a single TG test case [idea, eclipse, ant, cmd, etc. can be run]

  4. Run the test ng test case of the whole project [if it is a multi module project, enter the corresponding module directory to run the command or configuration]

    • xml

      <plugin>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
      	<groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
      	<configuration>
      		<skip>false</skip>
      		<testFailureIgnore>false</testFailureIgnore>
      		<suiteXmlFiles>
      			<file>${project.basedir}/src/test/OrderTest.xml</file>
      		</suiteXmlFiles>
      	</configuration>
      </plugin>
      
    • MVN command

      The command here will take precedence over the configuration in pom.xml, which is better than the java command, because mvn handles a part of packaging, which is convenient for monitoring

      mvn clean package/install -DskipTests
      
      mvn clean package/install -Dmaven.test.skip=true
      #Or directly
      mvn test
      

2. Relevant notes

annotation Effect
@BeforeSuite The annotated method will run before the entire test suite
@AfterSuite The annotated method will run after the entire test suite
@BeforeTest The annotated method will run before all use cases in the test suite are executed
@AfterTest The annotated method will run after all use cases in the test suite are executed
@BeforeGroups The annotated method will run before any use case within the specified group is executed
@AfterGroups The annotated method will run after execution of any use case in the specified group
@BeforeClass The annotated method will run any other method in the corresponding class marked @ Test before execution
@AfterClass The annotated method will run after the execution of any other method marked @ Test in the corresponding class of this method
@BeforeMethod The annotated method will run any other method in the corresponding class marked @ Test before execution
@AfterMethod The annotated method will run after the execution of any other method marked @ Test in the corresponding class of this method
@DataProvider The annotated method forces to return a two-dimensional array Object [] [] as the data factory of another @ Test method
@Factory The annotated method, as an Object factory, forces the return of an Object array Object []
@Listeners Define a listener for a test class
@Parameters Define a set of parameters, pass the values of the parameters to the method during method operation, and the values of the parameters are defined in testng.xml
@Test The marked method is a test method. If the marked method is a class, all public methods in this class are test methods

Note: for the corresponding attribute configuration values, click in the corresponding class to query, not to elaborate one by one

3. Explanation of terms

suite

Represented by xml file, including one or more test cases, wrapped with labels

Generally speaking, an xml < suite > corresponds to a java class. Unless otherwise, you need to specify < suite > in java

Otherwise, all @ Test annotation attributes suite name of the java class corresponding to xml are < suite name ='xxx '> defined in xml by default

test

A class represented by a label that contains one or more TestNG

1. Check in tests registration type test

These test classes need to be run before committing new code to ensure that the basic functions are not destroyed

2.Functional tests

These tests should cover all features of the software and run at least once a day, even if you don't want to run it in some cases

Check in test is a subset of Functional tests

Basic example
public class Test1 {
  @Test(groups = { "functest", "checkintest" })
  public void testMethod1() {
  }
 
  @Test(groups = {"functest", "checkintest"} )
  public void testMethod2() {
  }
 
  @Test(groups = { "functest" })
  public void testMethod3() {
  }
}
<test name="Test1">
  <groups>
    <run>
      <include name="functest"/>
      <!-- <include name="checkintest"/> -->
    </run>
  </groups>
  <classes>
    <class name="example1.Test1"/>
  </classes>
</test>

Run testng.xml file result:

In all @ Test annotations, methods 1, 2 and 3 defined in xml will be run. If you change to check intent, only methods 1 and 2 will be run

Conclusion:

1.xml file defines the running strategy of test cases

2. The so-called test case category is just a conceptual definition

- register the test case of the class, which may not run if it is not involved in the xml orchestration

- functional test class is a test case that we are sure to run in xml orchestration

Extension example
1. Regular matching
@Test
public class Test1 {
  @Test(groups = { "windows.checkintest" })
  public void testWindowsOnly() {
  }
 
  @Test(groups = {"linux.checkintest"} )
  public void testLinuxOnly() {
  }
 
  @Test(groups = { "windows.functest" )
  public void testWindowsToo() {
  }
}
<test name="Test1">
  <groups>
    <run>
      <include name="windows.*"/>
    </run>
  </groups>
 
  <classes>
    <class name="example1.Test1"/>
  </classes>
</test>

Let's play the brain hole, and we should be able to guess the running result [don't be tempted by the name of windows]

2. Regular exclusion

[officially, this style of writing is not recommended]

If you start refactoring Java code (the regular expression used in the tag may no longer match your method)

This is likely to crash your test framework

package org.vk.test.springtest_testng;

import org.testng.annotations.Test;

public class Test1 {

    @Test(groups = {"functest", "checkintest"})
    public void testMethod1() {
        System.out.println(1);
    }

    @Test(groups = {"functest", "checkintest"})
    public void testMethod2() {
        System.out.println(2);
    }

    @Test(groups = {"functest"})
    public void testMethod3() {
        System.out.println(3);
    }
}
<suite name="Suite" parallel="classes" thread-count="1">
    <test name="Test1">
        <groups>
            <run>
                <include name="functest"/>
            </run>
        </groups>
        <classes>
            <class name="org.vk.test.springtest_testng.Test1">
                <methods>
                    <include name="testMethod*"></include>
                    <exclude name="testMethod3"></exclude>
                </methods>
            </class>
        </classes>
    </test>
</suite>

Run result: testMethod3 will not execute

Summary: the submit configuration, from top to bottom, is actually a layer by layer filtering of the arrangement rules

Corresponding to group configuration, obviously all methods will be executed, but when class configuration is finished, configure it again and filter method 3

test class

java class, containing at least one TG annotation, represented by

test method

java method, with @ test annotation. By default, the return value of test method will be ignored unless it is declared to return

<suite allow-return-values="true">
<!--perhaps-->
<test allow-return-values="true">

test group

Test method group. You can not only define which group the method belongs to, but also set which subgroups the group contains. TG will automatically call

It can be defined in < test > or < suite > of testng.xml

If you specify group "a" in and group "b" in, both "a" and "b" will be included

Example 1: Foundation

@Test(groups = {"checkintest", "broken"} )
public void testMethod2() {
}
<test name="Simple example">
  <groups>
    <run>
      <include name="checkintest"/>
      <exclude name="broken"/>
    </run>
  </groups>
  
  <classes>
    <class name="example1.Test1"/>
  </classes>
</test>

Run result: nothing

Summary: xml configuration determines the final result. No matter what anti thinking configuration occurs, the theoretical result is the final result

[official tips]

To achieve the disable effect, you can also disable the Test separately by using the "enabled" attribute on the @ Test and @ Before/After annotation.

Example 2: combination

@Test(groups = { "checkin-test" })
public class All {
 
  @Test(groups = { "func-test" )
  public void method1() { ... }
 
  public void method2() { ... }
}

Results: method1 belongs to the two groups of checkin-test and func-test, and method2 belongs to the checkin-test group only

groups of groups

There are sub groups in the group, which are called MetaGroups. I call them tuples

functest and checkintent are mentioned in the test section of noun interpretation. Here we subdivide their full test into windows and linux groups

New all group, including two large groups

<suite name="Suite" parallel="classes" thread-count="1">    
	<test name="Test1">
      <groups>
        <define name="functest">
          <include name="windows"/>
          <include name="linux"/>
        </define>

        <define name="all">
          <include name="functest"/>
          <include name="checkintest"/>
        </define>

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

      <classes>
        <class name="example1.Test1"/>
      </classes>
    </test>
</suite>

Result: all methods run

Summary: multiple tests can be combined to form a group to represent a large function

testng.xml

In each section of testng.xml, you can find two other ways to call TG in the document corresponding to ant and command line

Personal understanding:

Generally speaking, it's OK to run @ Test class directly in idea and eclipse, but why do we need testng.xml?

Reason: if you need to arrange the test cases of java classes and methods without xml, it is difficult to do a highly flexible business logic test by only providing a way other than xml files!

parameters

Parameters of test cases, input parameter configuration of test methods, and used in method execution

Test case parameters

1.@Parameters
1.1 xml injection
@Parameters({ "first-name" })
@Test
public void testSingleString(String firstName) {
  System.out.println("Invoked testString " + firstName);
  assert "Cedric".equals(firstName);
}
<suite name="My suite">
  <parameter name="first-name"  value="Cedric"/>
  <test name="Simple example">
  <-- ... -->
</suite>
  1. The order in which XML parameters map to Java parameters is the same as in the comments, and TestNG will issue an error if the number does not match.

  2. Parameter scope:

    In testng.xml, they can be declared under or under tags.

    If two parameters have the same name, the parameters defined in have precedence. This is convenient if you need to specify a parameter that applies to all tests and override its value only for some tests.

1.2 @Optional injection
@Parameters("hello")
@Test(groups = {"functest"})
public void testMethod4(@Optional("hello") String hello) {
	System.out.println(hello);
}

[official instructions]

@The Parameters annotation is also applicable to annotations such as @Before/Afterand @Factory! Result: output hello string

Summary: the above methods are suitable for simple parameter configuration and complex object injection

2.@DataProvider

This method is derived to make up for the first method. If the parameter construction is complex, complex objects cannot be annotated in xml or @ Optional

That's how it's built

Example: the same class
//This method will provide data to any test method that declares that its Data Provider
//is named "test1"
@DataProvider(name = "test1",parallel = true)
public Object[][] createData1() {
 return new Object[][] {
   { "Cedric", new Integer(36) },
   { "Anne", new Integer(37)},
 };
}
 
//This test method declares that its data should be supplied by the Data Provider
//named "test1"
@Test(dataProvider = "test1")
public void verifyData1(String n1, Integer n2) {
 System.out.println(n1 + " " + n2);
}

Output results:

Cedric 36
Anne 37

Note: the number of concurrent threads can be adjusted in <suite data-provider-thread-count= "20" of xml, with 10 threads configured by default.

If you want to run specific data providers in different thread pools, you need to run them from different XML files.

Example: cross class
public class StaticProvider {
  @DataProvider(name = "create")
  public static Object[][] createData() {
    return new Object[][] {
      new Object[] { new Integer(42) }
    };
  }
}
 
public class MyTest {
  @Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
  public void test(Integer n) {
    // ...
  }
}
Example: other classes
@DataProvider(name = "test1")
public MyCustomData[] createData() {
  return new MyCustomData[]{ new MyCustomData() };
}
@DataProvider(name = "test1")
public Iterator<MyCustomData> createData() {
  return Arrays.asList(new MyCustomData()).iterator();
}
@DataProvider(name = "test1")
public Iterator<Stream> createData() {
  return Arrays.asList(Stream.of("a", "b", "c")).iterator();
}
Parameter return value description
  • Object[] []

    The size of the first dimension of the array is the number of calls to the test method

    The size of the second dimension of the array contains an array of objects that must be compatible with the parameter type of the test method

  • Iterator<Object[ ]>

    Unlike the first return type, this allows you to lazy initialize the return value

    The only limitation is that in the case of an iterator, its parameter type cannot be explicitly parameterized

    This is particularly useful if there are many parameter sets to pass to the method and you do not want to pre create all parameter sets

The following examples all have one feature: can't be explicitly parameterized

@DataProvider(name = "test1")

Initialization of display is not allowed. People with experience can chat privately! At present, I will directly paste the code on my side, which will report an error.

factory

Factories can also be used with data providers, which can be leveraged by placing @ Factory annotations on regular methods or constructors

You can use @ Factory to create tests dynamically. It is generally used to create multiple instances of a test class. All test cases in each instance will be executed. The @ Factory method to construct an instance must return Object [].

The dependencies in the next section has its application. I won't explain it too much here. The official website example is similar to it.

dependencyes

In some cases, we need to arrange the execution order. TG provides two ways:

  • xml

    <test name="My suite">
      <groups>
        <dependencies>
          <group name="c" depends-on="a  b" />
          <group name="z" depends-on="c" />
        </dependencies>
      </groups>
    </test>
    
  • annotation

    Set the dependent properties on the @ Test annotation: dependsOnMethods or dependsOnGroups

Hard dependence

All dependent methods must be running and successful to run.

If at least one error occurs in the dependency, it will not be invoked in the report and marked as a skip.

alwaysRun=false by default

@Test(groups = { "init" })
public void serverStartedOk() {}
 
@Test(groups = { "init" })
public void initEnvironment() {}
 
@Test(dependsOnGroups = { "init.*" })
public void method1() {}

If a dependent method fails and has a hard dependency on it, the method that depends on it is not marked as failed, but as skipped. It is important that the skipped method will be reported in the final report in the same way (in HTML, the color is neither red nor green), because the skipped method does not necessarily fail.

For advanced usage, please refer to [http://beust.com/weblog/2004/08/19/dependent-test-methods /]

Soft dependence

Even if some methods fail, you will always pursue the methods you depend on.

This is useful when you just want to make sure that your test methods run in a specific order, but their success does not really depend on the success of other methods.

Get soft dependency by adding alwaysRun=true to the @ Test annotation.

group-by-instances

Test cases run in groups according to instances

The usual dependsOnGroups rely on annotations and can only implement the following modes:

a(1)
a(2)
b(2)
b(2)

But there is also a case where we want to implement multiple groups of users and one group of operation behaviors: login and logout

signIn("us")
signOut("us")
signIn("uk")
signOut("uk")

At this time, we need to use @ Factory annotation and group by instance to implement

Test1.java

public class Test1 {

   private String countryName;

    public Test1(String countryName) {
        this.countryName = countryName;
    }

    @Test
    public void signIn() {
        System.out.println(countryName + " signIn");
    }

    @Test(dependsOnMethods = "signIn")
    public void signOut() {
        System.out.println(countryName + " signOut");
    }

}

Test.xml

<suite name="Suite">
    <test name="Test1" >
        <classes>
            <class name="org.vk.test.springtest_testng.Test1"></class>
        </classes>
    </test>
</suite>

TestFactory.java

public class TestFactory {

    @Factory(dataProvider = "init")
    public Object[] test(int nums) {
        Object[] object = new Object[nums];
        List<String> ctrys = Arrays.asList("US", "UK", "HK");
        for (int i = 0; i < nums; i++) {
            Test1 t = new Test1(ctrys.get(i));
            object[i] = t;
        }
        return object;
    }

    @DataProvider//The name can be defaulted, and the method name shall prevail by default
    public Object[][] init() {
        return new Object[][]{new Object[]{3}};
    }
}

TestFactory.xml

<suite name="Suite2" group-by-instances="true">
    <test name="TestFactory" >
        <classes>
            <class name="org.vk.test.springtest_testng.TestFactory"/>
        </classes>
    </test>
</suite>

verbose

verbose = "2" indicates the log level of the record. There are 0-10 levels in total, where 0 represents none and 10 represents the most detailed

preserve-order

The default is true, and the class es under the < test > tag are executed in order

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Preserve order test runs">
  <test name="Regression 1" preserve-order="true">
    <classes>
      <class name="com.pack.preserve.ClassOne"/>
      <class name="com.pack.preserve.ClassTwo"/>
      <class name="com.pack.preserve.ClassThree"/>
    </classes>
  </test>
</suite>

4. Concurrent unit test

This is useful if you run multiple suite files (for example, "java org.testng.testng testng1.xml testng2.xml") and want the suites to run in separate threads. You can use the following command line flags to specify the size of the thread pool:

1.cmd command

java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml

2.xml configuration

<suite name="My suite" parallel="methods" thread-count="5"></suite>
<! -- TestNG will run all test methods in a separate thread. Dependent methods will also run in separate threads, but they will follow the order you specify -- >
<suite name="My suite" parallel="tests" thread-count="5"></suite>
<! -- TestNG will run all methods in the same < test > tag in the same thread, but each < test > tag will be in a separate thread. This allows you to group all non thread safe classes into the same < test > and ensure that they will all run in the same thread, while using TestNG to run tests with as many threads as possible. >
<suite name="My suite" parallel="classes" thread-count="5"></suite>
<! -- TestNG will run all methods in the same class in the same thread, but each class will run in a separate thread. >
<suite name="My suite" parallel="instances" thread-count="5"></suite>
<! -- TestNG will run all methods in the same instance in the same thread, but two methods on two different instances will run in different threads. >

3. Annotation configuration

The function testServer is called ten times from three different threads. A 10 second timeout guarantees that no thread will block the thread forever.

@Test(threadPoolSize = 3, invocationCount = 10, timeOut = 10000)//timeOut is valid whether it is multithreaded or not
public void testServer() {
	...
	...
}

5. Failed tests

How to find

Every time a test fails in the suite, TestNG creates a file called TestNG-failed.xml in the output directory.

This XML file contains the information necessary to rerun only the failed methods, allowing you to quickly regenerate the failure without having to run the entire test.

Therefore, a typical session is as follows:

java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs testng.xml
java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs test-outputs\testng-failed.xml

testng-failed.xml will contain all the necessary dependent methods, so you can ensure that the failed methods run without any skipping failures.

Other methods: you can also get it through the test report target \ surefire reports \ index.html or the third-party test report plug-in

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-uckf794w-1581562336476) (C: \ users \ Dell \ appdata \ roaming \ typora \ typora user images \ 1579156460298. PNG))

How to retry

If there is an error in the test case, the retry steps to enable TG are as follows:

  1. Build a java class to implement the org.testng.IRetryAnalyzer interface
  2. Bind the class built in the first step to the @ Test annotation, for example, @ Test (retryAnalyzer=LocalRetry.class)
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
 
public class MyRetry implements IRetryAnalyzer {
 
  private int retryCount = 0;
  private static final int maxRetryCount = 3;
 
  @Override
  public boolean retry(ITestResult result) {
    if (retryCount < maxRetryCount) {
      retryCount++;
      return true;
    }
    return false;
  }
}
import org.testng.Assert;
import org.testng.annotations.Test;
 
public class TestclassSample {
 
  @Test(retryAnalyzer = MyRetry.class)
  public void test2() {
    Assert.fail();
  }
}

6. Execution mode

1. command line

java org.testng.TestNG testng1.xml [testng2.xml testng3.xml ...
option Parameter type Explain
-configfailurepolicy skip continue
-d A directory Address to generate test report
-dataproviderthreadcount Default number of threads running test cases in parallel During parallel testing, set the default maximum number of threads, provided that the [- parallel] option takes effect
-excludegroups Comma separated group list Exclude the list of groups to run
-groups Comma separated group list List of groups to run (example: "windows, Linux, expression")
-listener java classes found in classpath directory Allow yourself to define test listeners, but you must implement org.testng.ITestListener
-usedefaultlisteners true false
-methods Comma separated full path class method Specify specific method run, com.obj 1.test, com.obj 2.test
-methodselectors Comma separated method priority list Specify method selector, com.Selector1:3, com.Selector2:2
-parallel methods|tests|classes Sets the number of parallel threads for the default test. If not, the default mechanism is single thread testing. This can be overridden in the suite definition. It can be methods, test cases, classes
-reporter Custom report listener Similar to the - listener option, it allows you to set the JavaBeans attribute Example: -reporter com.MyReporter:methodFilter=*insert*,enableFiltering=true once or more times in the report, if necessary
-sourcedir Comma separated directory The directory of the JavaDoc annotated test source. This option is required only when JavaDoc type annotations are used. "Src / test" or "Src / test / org / TestNG / eclipse plugin; Src / test / org / TestNG / TestNG"
-suitename Default suite suite name This configuration is ignored if the relevant name is configured for suit.xml or source code
-testclass Comma separated java class list under classpath directory "org.foo.Test1,org.foo.test2"
-testjar jar package name Specify the jar file that contains the test class. If the testng.xml file is found in the root directory of the jar file, the file will be used, otherwise, all test classes found in the jar file will be treated as test classes.
-testname Default name of test case Specifies the name of the test defined on the command line. If the suite.xml file or source code specifies a different test name, this option is ignored. If you enclose the test name in double quotes "like this", it is possible to create a test name with spaces.
-testnames Comma separated test name The configuration here will only run if the test case matches
-testrunfactory java classes found under comma separated classpath Allow yourself to define the class to run. The class must implement org.testng.itestunnerfactory
-threadcount number Set the maximum number of threads for running test cases concurrently. Only the - parallel option will take effect. If it is defined in the submit, the configuration will be ignored / overwritten.
-xmlpathinjar Path of xml under jar package Contains the path to a valid XML file in the test jar (for example, "resources/testng.XML"). The default value is "testng.xml", which means there is a file named "testng.xml" in the root directory of the jar file. This option is ignored unless "- testjar" is specified.

2.ant

https://testng.org/doc/ant.html

3.eclipse

https://testng.org/doc/eclipse.html

4.idea

https://testng.org/doc/idea.html

  • Package: specify a package to run. All test cases under the package will run
  • Group: specify a TestNG group to run
  • Suite: specify a testng.xml file to run
  • Class: run all test cases in the corresponding class
  • Method: run a test case of a single method

7. Expansion module

Programming test

This example creates a TestNG object and runs the test class Run2.

It also adds a TestListener. You can use the adapter class org.testng.TestListenerAdapter, or you can implement org.testng.ITestListener yourself. This interface contains various callback methods that can be used to track when a test starts, succeeds, fails, and so on.

TestListenerAdapter tla = new TestListenerAdapter();
TestNG testng = new TestNG();
testng.setTestClasses(new Class[] { Run2.class });
testng.addListener(tla);
testng.run();

For example, if you want to implement functions like xml:

<suite name="TmpSuite" >
  <test name="TmpTest" >
    <classes>
      <class name="test.failures.Child"  />
    <classes>
    </test>
</suite>

So you can program it like this:

// 1. choreography
XmlSuite suite = new XmlSuite();
suite.setName("TmpSuite");
 
XmlTest test = new XmlTest(suite);
test.setName("TmpTest");
List<XmlClass> classes = new ArrayList<XmlClass>();
classes.add(new XmlClass("test.failures.Child"));
test.setXmlClasses(classes) ;
// 2. operation
List<XmlSuite> suites = new ArrayList<XmlSuite>();
suites.add(suite);
TestNG tng = new TestNG();
tng.setXmlSuites(suites);
tng.run();

[ https://jitpack.io/com/github/cbeust/testng/master/javadoc/org/testng/package-summary.html ]

BeanShell

If the and tags in testng.xml are not enough for your needs, you can use bean shell expressions to determine whether a test method should be included in the test run.

You can specify this expression under the tag:

<test name="BeanShell test">
   <method-selectors>
     <method-selector>
       <script language="beanshell"><![CDATA[
         groups.containsKey("test1")
       ]]></script>
     </method-selector>
   </method-selectors>
  <!-- ... -->

When a script tag is found in testng.xml, TestNG ignores the subsequent sum of groups and methods in the current tag, and your bean shell expression is the only way to determine whether to include the test method.

There are several other points to note:

It must return a Boolean value. In addition to this constraint, allow any valid bean shell code (for example, you might want to return true between working days and false during weekends, which will allow you to run tests differently depending on the date).

  • To facilitate the writing of bean shell conditions, TG prepares the following parameters:

    java.lang.reflect.Method --- method: the current test method.
    org.testng.ITestNGMethod — testngMethod: the description of the current test method.
    java.util.Map groups---: a map of the groups the current test method belongs to.

  • CDATA declarations, as shown above, surround expressions to avoid lengthy references to reserved XML characters.

Annotation converter

TestNG allows you to modify the contents of all comments at run time. If you want to override specific comments at run time, you need to use the annotation converter

Practical steps:

1. Implement the IAnnotationTransformer interface

public class MyTransformer implements IAnnotationTransformer {
  public void transform(ITest annotation, Class testClass,
      Constructor testConstructor, Method testMethod)
  {
    if ("invoke".equals(testMethod.getName())) {
      annotation.setInvocationCount(5);/// / execute 5 times
    }
  }
}

2. Run the cmd command or run it programmatically.

TestNG tng = new TestNG()

[official original]

IAnnotationTransformer only allows you to modify the @ Test annotation.

If you need to modify another TestNG annotation (@ Factory or @ DataProvider), use the IAnnotationTransformer2 interface

method interceptors

Once TestNG calculates the order in which test methods are called, these methods are divided into two groups:

1. Run [include dependencies] in sequence

2. Do not operate in a specific order

In order to have more control over methods belonging to the second class, TestNG defines the following interfaces:

public interface IMethodInterceptor {
  List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context);
}

Participation:

The list of methods passed in parameters is all methods that can be run in any order.

Return:

You can program the input method list without changing, reducing or expanding methods

Implementation:

java -classpath "testng-jdk15.jar:test/build" org.testng.TestNG -listener test.methodinterceptors.NullMethodInterceptor
   -testclass test.methodinterceptors.FooTest

Example:

Here is a method interceptor that reorders methods so that test methods belonging to the fast group are always run first:

public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
  List<IMethodInstance> result = new ArrayList<IMethodInstance>();
  for (IMethodInstance m : methods) {
    Test test = m.getMethod().getConstructorOrMethod().getAnnotation(Test.class);
    Set<String> groups = new HashSet<String>();
    for (String group : test.groups()) {
      groups.add(group);
    }
    if (groups.contains("fast")) {
      result.add(0, m);
    }
    else {
      result.add(m);
    }
  }
  return result;
}

Monitor

Detailed example [https://www.jianshu.com/p/2f934240699e]

listening type

There are several interfaces that allow you to modify the behavior of TestNG. These interfaces are widely known as "TestNG listeners"

IAnnotationTransformer (doc, javadoc) transforms comments. It needs to implement this interface and override the transform method
 IAnnotationTransformer2 (doc, javadoc) is also used to transform comments. When the above interface is not satisfied, it is seldom used
 IHookable (doc, javadoc) performs authorization check before executing the test method, and executes the test according to the authorization result
 IInvokedMethodListener (doc, javadoc) enables the listener before and after calling the method, which is commonly used for log collection
 Enable the listener before and after the imethodeinterceptor (DOC, Javadoc) calls the method, which is commonly used for log collection
 IReporter (doc, javadoc) will call this method when running all suites, which can be used to customize test reports later
 Embedding related logic before or after execution of ISuiteListener (doc, javadoc) test suite
 ITestListener (doc, javadoc) is often replaced by TestListenerAdapter

Monitor configuration

1. Command line

2.ant command

3.xml configuration

<suite>
  <listeners>
    <listener class-name="com.example.MyListener" />
    <listener class-name="com.example.MyMethodInterceptor" />
  </listeners>
</suite>

Or

@Listeners({ com.example.MyListener.class, com.example.MyMethodInterceptor.class })
public class MyTest {
  // ...
}

4. Use ServiceLoader

Custom listener

Note that the @ Listeners annotation applies to the entire suite file, just as you specified it in the testng.xml file.

If you want to limit its scope (for example, to run only on the current class), the code in the listener can first examine the test method to be run and then decide

What to do!

1. Customize a new note

@Retention(RetentionPolicy.RUNTIME)
@Target ({ElementType.TYPE})
public @interface DisableListener {}

2. Monitoring inspection

public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) {
  ConstructorOrMethod consOrMethod =iInvokedMethod.getTestMethod().getConstructorOrMethod();
  DisableListener disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableListener.class);
  if (disable != null) {
    return;
  }
  // Return to normal operation
}

3. Comment the test class that does not call the listener

@DisableListener
@Listeners({ com.example.MyListener.class, com.example.MyMethodInterceptor.class })
public class MyTest {
  // ...
}

Dependency injection

Injection type

Native method (executed by TestNG itself)

Extension methods (executed by the dependent injection framework, such as Guice).

  • Any @ Before method or @ Test method can declare parameters of type ITestContext.
  • Any @ AfterMethod method method can declare a parameter of type ITestResult, which will reflect the result of the test method just run.
  • Any @ Before and @ After methods (except @ BeforeSuite and @ AfterSuite) can declare an XmlTest type parameter that contains the current tag.
  • Any @ beforemethod (or @ aftermethod) can declare a java.lang.reflect.Method class parameter. This parameter can accept the test method called after @BeforeMethod runs (or after the method runs @AfterMethod).
  • Any @ BeforeMethod can declare a parameter of type Object []. This parameter contains a list of parameters injected into the next test method by TestNG, such as java.lang.reflect.Method or @ DataProvider.
  • Any @ DataProvider can declare parameters of type ITestContext or java.lang.reflect.Method.
annotation ITestContext XmlTest Method Object[] ITestResult
@BeforeSuite Yes No No No No
@BeforeTest Yes Yes No No No
@BeforeGroups Yes Yes No No No
@BeforeClass Yes Yes No No No
@BeforeMethod Yes Yes Yes Yes Yes
@Test Yes No No No No
@DataProvider Yes No Yes No No
@AfterMethod Yes Yes Yes Yes Yes
@AfterClass Yes Yes No No No
@AfterGroups Yes Yes No No No
@AfterTest Yes Yes No No No
@AfterSuite Yes No No No No
@NoInjection
package org.vk.test.springtest_testng;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.vk.demo.EatCompentConfig;

import java.lang.reflect.Method;

public class Test2 {
    @DataProvider(name = "provider")
    public Object[][] provide() throws Exception {
        return new Object[][] { { EatCompentConfig.class.getMethod("hasNext") } };
    }

    @Test(dataProvider = "provider")
    public void withoutInjection(@NoInjection Method m) {
        Assert.assertEquals(m.getName(), "hasNext");
    }

    @Test(dataProvider = "provider")
    public void withInjection(Method m) {
        Assert.assertEquals(m.getName(), "withInjection");
    }
}

This annotation is to turn off dependency injection. Why?

In the case, the default objects in the input and dependency injection of the withoutInjection method overlap, and by default, the dependency is used

The corresponding object is the Method itself. If @ NoInjection is not added, then it represents the Method itself. But our code means that we really want to pass in an input Method instead of the default Method of dependency injection. Therefore, we need the annotation @ NoInjection to turn off dependency injection, and Assert will succeed!

8. Test report

There are many types of technical options for test reports. I choose a relatively simple and attractive external report plug-in, which is implemented with a custom listener

Note: target \ surefire reports \ index.html, here is the original test report. The report location of other plug-ins can be defined by yourself

pom.xml

<!--testng rely on-->
<dependency>
	<groupId>org.testng</groupId>
	<artifactId>testng</artifactId>
	<version>7.1.0</version>
</dependency>
<!--Dependence of test report-->
<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>

xml configuration

<suite name="test2">
    <listeners>
      <listener class-name="org.vk.test.listeners.report.ExtentTestNGIReporterListener"/>
    </listeners>
    <test name="Test2" >
        <classes>
            <class name="org.vk.test.demos.Test2">
            </class>
        </classes>
    </test>
</suite>

Monitor of user-defined test report [copy it, no need to implement it by yourself]

package org.vk.test.listeners.report;

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.*;

/**
 * TestNg Generate a good-looking test UI Report
 *
 * @author liuleiba@ecej.com
 * @version 1.0
 */
public class ExtentTestNGIReporterListener implements IReporter {
    //Path and file name of the beautified test report
    private static final String OUTPUT_FOLDER = "target/test-report/";
    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();
            //If there is no use case in suite, skip it and do not generate it in the report
            if(result.size()==0){
                continue;
            }
            //Count the total number of success, failure and skip cases under suite
            int suiteFailSize=0;
            int suitePassSize=0;
            int suiteSkipSize=0;
            ExtentTest suiteTest=null;
            //In the case of multiple suites, the test results of the same suite are classified into one class in the report, and a level-1 node is created.
            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){
                    //If suite is not created, SuiteResult will be created as a primary node, otherwise it will be created as a child node of 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());
                    //Statistics of data under 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() {
        //Create if folder does not exist
        File reportDir= new File(OUTPUT_FOLDER);
        if(!reportDir.exists()&& !reportDir .isDirectory()){
            reportDir.mkdir();
        }
        ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
        // Set DNS for static files
        //How to solve the problem that cdn.rawgit.com can't access
        htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);

        htmlReporter.config().setDocumentTitle("PC End automation test report");
        htmlReporter.config().setReportName("PC End automation test report");
        htmlReporter.config().setChartVisibilityOnOpen(true);
        htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
        htmlReporter.config().setTheme(Theme.STANDARD);
        htmlReporter.config().setEncoding("gbk");
        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) {
        //Get the label of the parent node when it exists
        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) {
            //Adjust use case sorting by time
            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="";
                //If there are parameters, the toString combination of the parameters is used instead of the name in the report
                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{
                    //When it is created as a child node, the settings are consistent with the labels of the parent node, which is convenient for report retrieval.
                    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){
                    //Output the log of the use case to the report
                    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();
    }
}

Results output page:

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-okxmk1by-1581562336478) (C: \ users \ Dell \ appdata \ roaming \ typora \ typora user images \ 1579085189960. PNG))

9. Combination of TestNG and Springboot

1. Coding specification

For unit test, we hope you can design, process and code according to the following rules:

1. Nomenclature

Root directory of test package: it must be under src/test/java * * [this directory will be skipped during source code construction, and unit test framework will scan this directory by default]**

Package path of java class in the test package: consistent with the actual class to be tested * * [refer to the screenshot in the writing process]**

Java class name of test package: follow the rules of orderqueryservice.java - > orderqueryservicetest.java

XML path of test package: under the package of the class to be tested, create a new XML package to store testng.xml of each test type

Listener of test package: under the package of the class to be tested, create a listers package according to the scope of the listener,

2. Design principles

  • Fully automated, not interactive. When mvn test or running test cases for the entire class, all tests can be run automatically
  • Test case s should be reliable and reliable, and any core changes at the underlying level should be perceived in time
  • It supports repeated operation and ensures that the coverage [if else] can basically cover all scenarios. [70% for ordinary statements and 100% for core statements]
  • The run result must be asserted. System.out.print and log placeholder are not allowed to output test information
  • For testing of pure database repository layer, it is not allowed to use mock for testing, and data rollback mechanism is required to avoid dirty data
  • At the same time of each iteration and modification of the project version, maintain the test case, and do not allow Ignore to the existing and well run test
  • Complex interface, as far as possible to separate test cases, keep a small granularity to help quickly find and accurately locate problems

2. Preparation process

1. Directory structure and package configuration

Basic directory

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-lbjdw9pa-1581562336480) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782404369. PNG))

Test package configuration

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-8hs2ntom-1581562336481) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782525627. PNG))

2.pom import dependency

<!--testng rely on-->
<dependency>
	<groupId>org.testng</groupId>
	<artifactId>testng</artifactId>
	<version>7.1.0</version>
</dependency>
<!--Dependence of test report-->
<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>

3. Write test class

@DataProvider annotation input parameter
package com.ecej.order.basics.service.impl;

import com.alibaba.fastjson.JSON;
import com.ecej.order.basics.Startup;
import com.ecej.order.basics.api.query.OrderQueryService;
import com.ecej.order.basics.bean.dto.CombinedOrderDTO;
import com.ecej.order.basics.bean.dto.WorkOrderDetailDTO;
import com.ecej.order.basics.bean.request.WorkOrderDetailQueryReqParam;
import com.ecej.order.basics.bean.request.WorkOrderListQueryReqParam;
import com.ecej.order.common.util.DateUtil;
import com.ecej.order.model.baseResult.ResultMessage;
import com.ecej.order.listener.ExtentTestNGIReporterListener;
import com.ecej.order.util.QueryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;

/**
 * @ClassName: &#x8ba2;&#x5355;&#x67e5;&#x8be2;&#x6d4b;&#x8bd5;
 * @Author: Administrator
 * @Description: zlr
 * @Date: 2020/1/16 17:54
 * @Version: 1.0
 */
@SpringBootTest(classes = Startup.class)
@Listeners(ExtentTestNGIReporterListener.class)
public class OrderQueryServiceImplTest extends AbstractTestNGSpringContextTests {
    private static final Logger logger = LoggerFactory.getLogger(OrderQueryServiceImplTest.class);

    @Autowired
    private OrderQueryService orderQueryService;

    @Test(dataProvider = "createOrderListQueryData", suiteName = "Order list unit test", groups = "queryWorkOrderList", timeOut = 10000)
    public void queryWorkOrderListPageTest(int paramType, ITestContext testContext, WorkOrderListQueryReqParam param) {
        logger.info("Parameter name={};Test section[{}]Second start={}",paramType,testContext.getPassedTests().size()+1);
        ResultMessage<QueryResult<CombinedOrderDTO>> queryWorkOrderListPage = orderQueryService.queryWorkOrderListPage(param);
        logger.info(JSON.toJSONString(queryWorkOrderListPage));
        switch (paramType) {
            case 1:
                Assert.assertEquals(queryWorkOrderListPage.getCode(), 1000);
                break;
            case 2:
                Assert.assertEquals(queryWorkOrderListPage.getCode(), 1000);
                break;
            case 3:
                Assert.assertEquals(queryWorkOrderListPage.getCode(), 1000);
                break;
            case 4:
                Assert.assertEquals(queryWorkOrderListPage.getCode(), 200);
                break;
            default:
                Assert.assertEquals(queryWorkOrderListPage.getCode(), 200);
        }
    }

    @Test(dataProvider = "createOrderDetailData", suiteName = "Order details unit test", groups = "orderDetailAnnotations", timeOut = 10000)
    public void queryWorkOrderDetailTest(int paramType, WorkOrderDetailQueryReqParam param) {
        ResultMessage<WorkOrderDetailDTO> resultMessage = orderQueryService.queryWorkOrderDetail(param);
        switch (paramType) {
            case 1:
                Assert.assertEquals(resultMessage.getCode(), 1000);
                break;
            case 2:
                Assert.assertEquals(resultMessage.getCode(), 1000);
                break;
            case 3:
                Assert.assertEquals(resultMessage.getCode(), 200);
                break;
            default:
                Assert.assertEquals(resultMessage.getCode(), 200);
        }
        logger.info(JSON.toJSONString(resultMessage));
    }

    /**
     * Create order list query parameters (here you can also use query database as parameter object)
     * Build multi scenario test case parameters (unit test asserts according to scenario 1, 2, 3, 4)
     */
    @DataProvider(name = "createOrderListQueryData")
    public Object[][] createOrderListQueryData() {
        //1. Building empty objects
        WorkOrderListQueryReqParam checkParam = new WorkOrderListQueryReqParam();
        //2. Build incomplete parameter (requestSource)
        WorkOrderListQueryReqParam paramRequestSource = new WorkOrderListQueryReqParam();
        paramRequestSource.setCityId(2237);
        paramRequestSource.setCityIdList(Arrays.asList(2237, 2057, 2367));
        paramRequestSource.setBookStartTimeBegin(DateUtil.getDate(new Date(), -4));
        paramRequestSource.setWorkOrderStatusList(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 150));
        paramRequestSource.setBookStartTimeEnd(DateUtil.getDate(new Date(), 4));
        paramRequestSource.setStationIdList(Collections.singletonList(35200342));
        paramRequestSource.setPageNum(1);
        paramRequestSource.setPageSize(10);
        paramRequestSource.setOrderDispatchingModeList(Arrays.asList(1, 2, 3, 4, 5));
        //3. Build order source is 99 required parameter verification (lack of appointment time query)
        WorkOrderListQueryReqParam paramBookStartTime = new WorkOrderListQueryReqParam();
        paramBookStartTime.setRequestSource(99);
        paramBookStartTime.setCityId(2237);
        paramBookStartTime.setCityIdList(Arrays.asList(2237, 2057, 2367));
        paramBookStartTime.setWorkOrderStatusList(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 150));
        paramBookStartTime.setStationIdList(Collections.singletonList(35200342));
        paramBookStartTime.setPageNum(1);
        paramBookStartTime.setPageSize(10);
        paramBookStartTime.setOrderDispatchingModeList(Arrays.asList(1, 2, 3, 4, 5));
        //4. Full parameters
        WorkOrderListQueryReqParam param = new WorkOrderListQueryReqParam();
        param.setRequestSource(99);
        param.setCityId(2237);
        param.setCityIdList(Arrays.asList(2237, 2057, 2367));
        param.setBookStartTimeBegin(DateUtil.getDate(new Date(), -4));
        param.setWorkOrderStatusList(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 150));
        param.setBookStartTimeEnd(DateUtil.getDate(new Date(), 4));
        param.setStationIdList(Collections.singletonList(35200342));
        param.setPageNum(1);
        param.setPageSize(10);
        param.setOrderDispatchingModeList(Arrays.asList(1, 2, 3, 4, 5));
        // param.setWorkOrderNo("4542");
        return new Object[][]{
                { 1, checkParam },
                { 2, paramRequestSource },
                { 3, paramBookStartTime },
                { 4, param },
        };
    }

    /**
     * Build multiple test parameters: create order interface
     */
    @DataProvider(name = "createOrderDetailData")
    public Object[][] createOrderDetailData() {
        //1. Building empty objects
        WorkOrderDetailQueryReqParam checkParam = new WorkOrderDetailQueryReqParam();
        //2. Build incomplete parameter (workorder no)
        WorkOrderDetailQueryReqParam checkWorkOrderNoParam = new WorkOrderDetailQueryReqParam();
        checkWorkOrderNoParam.setRequestSource(99);
        //4. Full parameters
        WorkOrderDetailQueryReqParam param = new WorkOrderDetailQueryReqParam();
        param.setRequestSource(99);
        param.setWorkOrderNo("A201801191022356151");
        return new Object[][]{
                { 1, checkParam },
                { 2, checkWorkOrderNoParam },
                { 3, param },
        };
    }


@Parameters input
package com.ecej.order.basics.service.impl;

import com.alibaba.fastjson.JSON;
import com.ecej.order.basics.Startup;
import com.ecej.order.basics.api.query.OrderQueryService;
import com.ecej.order.basics.bean.dto.WorkOrderDetailDTO;
import com.ecej.order.basics.bean.request.WorkOrderDetailQueryReqParam;
import com.ecej.order.model.baseResult.ResultMessage;
import com.ecej.order.listener.ExtentTestNGIReporterListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

/**
 * @ClassName: OrderTGXml
 * @Author: Administrator
 * @Description: zlr
 * @Date: 2020/1/16 13:51
 * @Version: 1.0
 */
@SpringBootTest(classes = { Startup.class })
@Listeners(ExtentTestNGIReporterListener.class)
public class OrderQueryServiceImplXmlTest extends AbstractTestNGSpringContextTests {
    private static final Logger logger = LoggerFactory.getLogger(OrderQueryServiceImplXmlTest.class);
    @Autowired
    private OrderQueryService orderQueryService;

    @Test(groups = "queryWorkOrderDetail")
    @Parameters({"requestSource","workOrderNo"})
    public void queryWorkOrderDetailTest(Integer requestSource,String workOrderNo){
        WorkOrderDetailQueryReqParam param = new WorkOrderDetailQueryReqParam();
        param.setRequestSource(requestSource);
        param.setWorkOrderNo(workOrderNo);
        ResultMessage<WorkOrderDetailDTO> resultMessage = orderQueryService.queryWorkOrderDetail(param);
        Assert.assertEquals(resultMessage.getCode(), 200);
        logger.info(JSON.toJSONString(resultMessage));
    }
}

Basically, there's no big difference between normal code and normal code. You only need to maintain TG's xml and some of its own annotations [the architecture case has been satisfied]

4. Arrange test class xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Order basic service unit test report" parallel="classes" thread-count="1">
    <listeners><listener class-name="com.ecej.order.listener.ExtentTestNGIReporterListener"/></listeners>
    <test verbose="1" preserve-order="true" name="Order inquiry">
        <parameter name="requestSource" value="99" />
        <parameter name="workOrderNo" value="A201801191022356151"/>

        <groups>
            <define name="queryWorkOrderListPageTest">
                <!--It can be multiple or written separately-->
                <include name="queryWorkOrderList"/>
                <!--<include name="queryWorkOrderDetail"/>-->
            </define>
            <define name="queryWorkOrderDetailTest">
                <include name="queryWorkOrderDetail"/>
            </define>
            <run>
                <include name="queryWorkOrderListPageTest"/>
                <include name="queryWorkOrderDetailTest"/>
            </run>
        </groups>

        <classes>
            <!-- Test classes can be multiple -->
            <class name="com.ecej.order.basics.service.impl.OrderQueryServiceImplTest" />
            <class name="com.ecej.order.basics.service.impl.OrderQueryServiceImplXmlTest" />
        </classes>
    </test>
</suite>

xml is a secondary arrangement based on the test class, and the final test effect is based on xml

3. Running test cases

1. IDEA runs a single test

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-k2hhy4e4-1581562336483) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782613491. PNG))

[the external link image transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-fzjfe51-1581562336483) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782642269. PNG))

2. IDEA runs the test of the whole package

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-5thiiuiq-1581562336485) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782705068. PNG))

3. Eclipse runs a single test

[the external link image transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-k6c9v2b2-1581562336485) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782761162. PNG))

4. Eclipse runs the test of the whole package

[the external link image transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-h5quh17b-1581562336485) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782774727. PNG))

5. View debug breakpoints

Like normal programs, debug mode can be used to start test cases, make breakpoints for some of the input parameters and returns, and view the parameters and returns

[the external link image transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-cr7dgk8c-1581562336486) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580782877064. PNG))

6. MVN runs the whole project test

Run mvn test in this case

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-bqjpjpjpq-1581562336487) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580783218995. PNG))

If there is a result error, you need to fix the error test in Failed tests until Build Success

4. View test case report

In some cases, the console can see the test results, but for the project release, we'd better view the statistics from the test results report

We can get the running status of the test cases and the success and failure from two types of reports to repair them.

Which kind of report do you want to see? See your personal habits.

Report ng original report

[the external link image transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-zzzprqhq-1581562336487) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580783311156. PNG))

External report beautification Report

[failed to save the image in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-oqjqipaa-1581562336487) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580783343860. PNG))

5. Data processing of test cases [1]

Scenario analysis

For some methods, the dependence on data integrity is high, and the manual construction of data is complex. When the test scenario needs to be comprehensive, we need to simulate the integrity through the test case: Warehousing query modify delete scenario, then we need to insert some data to support other test runs before running some query test cases. After running the test, we need to insert some data to support other test runs We need to erase the used data

At this point, we need to have corresponding means and case s to cover these scenarios

In writing the test case, we need to consider the dirty data generated by the curd operation of the service and dao layers from two perspectives:

1. Local service data erasure

For the rollback of service and dao data in this project, the following three aspects can be considered:

1. Automatic rollback [recommended]

There are two classes to explain:

​ 1.AbstractTestNGSpringContextTests

Only if the test class inherits the class can it have the ability to inject the instance Bean, otherwise the injection reports an error

Summary: [test cases suitable for query processing]

​ 2.AbstractTransactionalTestNGSpringContextTests

After inheriting this class, the test class has the ability to inject instances and control things

Summary: [applicable to any scenario, recommended]

Therefore, to process the service and dao in the local project, for the data generated by the database, we only need to inherit the test class from AbstractTransactionalTestNGSpringContextTests. All the operations on the database in the test case will only stay in the test stage. Once the test case is completed, TestNG will automatically help us roll back the data without any code intrusion.

Example:

/**
 * Demonstrate the Test Curd operation [remote service transaction rollback: programming rollback]
 *
 * @author liulei, liuleiba@ecej.com
 * @version 1.0
 */
@SpringBootTest(classes = Startup.class)
@Listeners(ExtentTestNGIReporterListener.class)
    public class TestNgCurdServiceImplTest extends AbstractTestNGSpringContextTests {

    private static final Logger logger = LoggerFactory.getLogger(TestNgCurdServiceImplTest.class);

    @Autowired
    TestNgCurdService testNgCurdService;

    @DataProvider
    private Object[][] saveOrUpdateParam() {
        SysMenuParam po = new SysMenuParam();
        po.setLevels(1);
        po.setMenuSort(1);
        po.setMenuName("Test menu");
        po.setMenuUrl("menu—url");
        po.setPmenuId("1");
        return new Object[][]{{po}};
    }

    /**
     * 1.Test preservation
     */
    @Rollback
    @Test(groups = "saveOrUpdate", dataProvider = "saveOrUpdateParam")
    public void testSaveOrUpdate(SysMenuParam po) {
        logger.info("Test save start:{}", po);
        ResultMessage<Boolean> result = testNgCurdService.saveOrUpdate(po);
        Assert.assertEquals(result.getCode(), 200);
        logger.info("End of test save", result);
    }
    //...   
}        
2. Selective rollback

The test class inherits the AbstractTestNGSpringContextTests class and uses @ Rollback annotation to roll back some test methods, so as to avoid that the test data in the process of test case finally becomes dirty data. After running the test case, TestNG will automatically help us roll back the data generated by @ Rollback annotation. Other methods without annotation will be true It should be used cautiously in the database level.

Example:

public class TestNgCurdServiceImplTest extends AbstractTestNGSpringContextTests {
//....
//The writing method remains unchanged without any code changes. Just add the annotation @ Rollback to the method to be rolled back
//....
}
3. Program rollback [caution]

Manually program to erase all the data generated before and after the test case runs by manually calling the relevant delete interface one by one [suitable for remote service]

The following chapter [remote service data rollback] has relevant cases and instructions!

2. Remote service data erasure

If a test case is running in a non Mock mode, and a remote service is called to generate dirty data, the data can only be rolled back programmatically at this time, that is, before and after the test case is executed, the relevant delete method can be called to clear it. If the context of the test case is more complex, the data recycling analysis becomes more important, and the services are linked The call is too long. Once the test case has errors, there will be some unpredictable problems, which need to be used carefully.

In this case, we also have case coverage:

Core process:

Testing for remote service additions and deletions, it is inevitable that we need to insert the preparation data before we can test the query interface. We can use the execution mechanism provided by TestNg to invoke the relevant delete method after running the test cases to clear the interim test data during operation, otherwise it will pollute the database.

Interested partners can test by themselves according to the following code

1.service interface and its implementation class
/**
 * TestNg Curd Test case [no practical use]
 * @author liulei, lei.liu@htouhui.com
 * @version 1.0
 */
public interface TestNgCurdService {

    ResultMessage<Boolean> saveOrUpdate(SysMenuParam sysMenuParam);

    ResultMessage<Boolean> delete(SysMenuParam sysMenuParam);

    ResultMessage<List<SysMenuDTO>> queryList(SysMenuReqParam sysMenuReqParam);

}
2. test class
package com.ecej.order.basics.service.impl;

import com.ecej.order.basics.Startup;
import com.ecej.order.basics.api.query.TestNgCurdService;
import com.ecej.order.listener.ExtentTestNGIReporterListener;
import com.ecej.order.model.baseResult.ResultMessage;
import com.ecej.order.strategy.bean.dto.SysMenuDTO;
import com.ecej.order.strategy.bean.request.SysMenuParam;
import com.ecej.order.strategy.bean.request.SysMenuReqParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.*;

import java.util.List;

/**
 * Demonstrate the Test Curd operation [remote service transaction rollback: programming rollback]
 *
 * @author liulei, liuleiba@ecej.com
 * @version 1.0
 */
@SpringBootTest(classes = Startup.class)
@Listeners(ExtentTestNGIReporterListener.class)
public class TestNgCurdServiceImplTest extends AbstractTestNGSpringContextTests {

    private static final Logger logger = LoggerFactory.getLogger(TestNgCurdServiceImplTest.class);

    @Autowired
    TestNgCurdService testNgCurdService;

    @DataProvider
    private Object[][] saveOrUpdateParam() {
        SysMenuParam po = new SysMenuParam();
        po.setLevels(1);
        po.setMenuSort(1);
        po.setMenuName("Test menu");
        po.setMenuUrl("menu—url");
        po.setPmenuId("1");
        return new Object[][]{{po}};
    }

    /**
     * 1.Test preservation
     */
    @Test(groups = "saveOrUpdate", dataProvider = "saveOrUpdateParam")
    public void testSaveOrUpdate(SysMenuParam po) {
        logger.info("Test save start:{}", po);
        ResultMessage<Boolean> result = testNgCurdService.saveOrUpdate(po);
        Assert.assertEquals(result.getCode(), 200);
        logger.info("End of test save:{}", result);
    }

    @DataProvider
    private Object[][] queryListParam() {
        SysMenuReqParam po = new SysMenuReqParam();
        po.setMenuName("Test menu");
        return new Object[][]{{po}};
    }

    /**
     * 2.Test query [don't forget to use the allow return values = "true" annotation in the xml file to force the return of test case results, so that the data can be cleared manually]
     */
    @Test(groups = "queryList", dataProvider = "queryListParam", dependsOnGroups = "saveOrUpdate")
    public List<SysMenuDTO> testQueryList(SysMenuReqParam po) {
        logger.info("Test query start:{}", po);
        ResultMessage<List<SysMenuDTO>> result = testNgCurdService.queryList(po);
        Assert.assertEquals(result.getCode(), 200);
        logger.info("End of test query:{}", result);
        return result.getData();
    }

    /**
     * 3.Test delete method according to menuId [depends on insert method]
     */
    public void testDelete(SysMenuParam po) {
        logger.info("Test delete start:{}", po);
        ResultMessage<Boolean> result = testNgCurdService.delete(po);
        //It is expected that the deletion succeeds, but the current remote interface will return to the menu primary key after insertion and query, so it will fail here [note]
        Assert.assertEquals(result.getCode(), 303);
        logger.info("End of test deletion:{}", result);
    }

    /**
     * Because it is a remote service, it is impossible to review the test data through the rollback mechanism of test ng
     * Therefore, for the curd of the remote service test case, the test data must be cleared manually to ensure the purity of the data
     */
    @AfterSuite
    public void clearData() {
        //Because the insertion method does not return the primary key, we need to query the newly inserted primary key and delete it
        SysMenuReqParam po = new SysMenuReqParam();
        po.setMenuName("Test menu");
        SysMenuParam sysMenuParam = new SysMenuParam();
        List<SysMenuDTO> sysMenuDTO = testQueryList(po);
        for (SysMenuDTO dto : sysMenuDTO) {
            SysMenuParam param = new SysMenuParam();
            //Since the remote interface does not return the primary key ID, an error will be reported when you delete it. However, the remote interface cannot be changed at present, so please note that
            BeanUtils.copyProperties(dto, param);
            testDelete(sysMenuParam);
        }
    }
}
3.xml file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="TestNg test Curd Kit" allow-return-values="true" parallel="classes" thread-count="1">
    <listeners><listener class-name="com.ecej.order.listener.ExtentTestNGIReporterListener"/></listeners>
    <test verbose="1" preserve-order="true" name="Order inquiry">
        <parameter name="requestSource" value="99" />
        <parameter name="workOrderNo" value="A201801191022356151"/>

        <groups>
            <define name="saveOrUpdate"/>
            <define name="queryList"/>
            <dependencies>
                <group name="queryList" depends-on="saveOrUpdate"/>
            </dependencies>
        </groups>

        <classes>
            <!-- Test classes can be multiple -->
            <class name="com.ecej.order.basics.service.impl.TestNgCurdServiceImplTest" />
        </classes>
    </test>
</suite>

So far, the rollback of things has been completed, but there are also some problems. If there is a problem in any link of the whole link, dirty data will be generated. For example, after adding, the delete method reports an error, then the data inserted into the database will be retained, and the library needs to be cleared manually. If the call link is relatively long and involves a wide range, there is uncertainty !

6. Data processing of test cases [2]

Chapter [1] is a way to process test case data, which is based on the dimension of test method

Another way is to use TestNG's listener to do data burying points, and initialize the data to be used in the database in advance. We do burying points in packages. If the scope is too large, it is not recommended. Once some programs have problems, it is easy to cause data confusion and difficult to handle.

In this scenario, we still have relevant case coverage:

Core process

Before the test suite suite is executed, the data is buried. After the whole suite test case is executed, the data is destroyed

In suite and sql script as media

Interested partners can test it according to the following code:

Test suite listener
package com.ecej.order.listener;

import com.ecej.order.util.TestDataHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.ISuite;
import org.testng.ISuiteListener;

/**
 * Embedded point processing of test data [for test classes that need to be embedded, use this listener directly]
 *
 * @author liulei, liuleiba@ecej.com
 * @version 1.0
 */
public class TestCaseDataPrepareListener implements ISuiteListener {

    private static final Logger logger = LoggerFactory.getLogger(TestCaseDataPrepareListener.class);

    /**
     * Embedded data sql initialization script
     */
    private static String INIT_FILE = "order_init.sql";
    /**
     * sql destroy script of embedded data
     */
    private static String DESTROY_FILE = "order_destroy.sql";

    private static TestDataHandler TestDataHandler = new TestDataHandler();

    /**
     * Before test suite execution
     *
     * @param suite Kit
     */
    @Override
    public void onStart(ISuite suite) {
        logger.info("Test suite starts to initialize test data");
        TestDataHandler.testDataOperate(INIT_FILE, false);
        logger.info("Test suite finishes initializing test data");
    }

    /**
     * After test suite execution
     *
     * @param suite Kit
     */
    @Override
    public void onFinish(ISuite suite) {
        logger.info("Test suite begins to initialize destroy data");
        TestDataHandler.testDataOperate(DESTROY_FILE, true);
        logger.info("Test suite complete initialization destroy data");
    }

}

Embedded data processor
package com.ecej.order.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.*;
import java.sql.*;
import java.util.ArrayList;

/**
 * TODO Optimize the storage to ThreadLocalMap, initialize the data source once, and then reuse the data source later
 *
 * @author liulei, lei.liu@htouhui.com
 * @version 1.0
 */
public class TestDataHandler {

    private static final Logger logger = LoggerFactory.getLogger(TestDataHandler.class);

    private static String DB_DRIVER = "com.mysql.jdbc.Driver";
    private static String DB_URL = "jdbc:mysql://10.4.98.14:3306/ecejservice?useunicode=true&amp;characterencoding=utf-8&amp;zeroDateTimeBehavior=convertToNull";
    private static String DB_USER = "dev_user";
    private static String DB_PWD = "123qweasd";
    /**
     * Running environment
     * TODO To be changed into dynamic
     */
    private static String PROFILE = "dev";

    private static Connection connection;

    static {
        try {
            //Loading mysql driver class
            Class.forName(DB_DRIVER);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Constructor, including operations such as connecting to database
     */
    public TestDataHandler() {
        try {
            //Loading mysql driver class
            Class.forName(DB_DRIVER);
            //Get database connection
            connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
        } catch (Exception e) {
            e.printStackTrace();
            connection = null;
        }
    }

    /**
     * Custom database connection
     *
     * @param dbUrl    Database connection
     * @param User     user
     * @param Password Password
     */
    public TestDataHandler(String dbUrl, String User, String Password) {
        try {
            //Get database connection
            connection = DriverManager.getConnection(dbUrl, User, Password);
        } catch (Exception e) {
            e.printStackTrace();
            connection = null;
        }
    }

    /**
     * Get connection
     *
     * @return Connect conn
     */
    public Connection getConnection() {
        return connection;
    }

    /**
     * Release database connection
     */
    public static void ReleaseConnect() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * Execute SQL statements in batch
     *
     * @param sql     ArrayList collection containing SQL statements to be executed
     * @param ifClose Close database connection or not
     * @return int Function of influence
     */
    public int executeSqlFile(ArrayList<String> sql, boolean ifClose) {
        try {
            Statement st = connection.createStatement();
            for (String subsql : sql) {
                st.addBatch(subsql);
            }
            st.executeBatch();
            return 1;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            if (ifClose) {
                ReleaseConnect();
            }
        }
    }

    /**
     * Read the file in line units, and format each line of the file into ArrayList. It is often used to read line oriented format files
     *
     * @param filePath File path
     */
    private static ArrayList<String> readFileByLines(String filePath) throws Exception {
        ArrayList<String> listStr = new ArrayList<>();
        StringBuffer sb = new StringBuffer();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
            String tempString;
            int flag = 0;
            // Read in one line at a time until null is the end of the file
            while ((tempString = reader.readLine()) != null) {
                // Display line number and filter blank lines
                if (tempString.trim().equals(""))
                    continue;
                if (tempString.substring(tempString.length() - 1).equals(";")) {
                    if (flag == 1) {
                        sb.append(tempString);
                        listStr.add(sb.toString());
                        sb.delete(0, sb.length());
                        flag = 0;
                    } else
                        listStr.add(tempString);
                } else {
                    flag = 1;
                    sb.append(tempString);
                }
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e1) {
                }
            }
        }

        return listStr;
    }

    /**
     * Read the contents of the file to execute in SQL
     *
     * @param file    SQL Path to file
     * @param ifClose Close database connection or not
     */
    public void testDataOperate(String file, boolean ifClose) {
        try {
            Resource resource = new ClassPathResource("sql" + File.separator + PROFILE + File.separator + file);
            ArrayList<String> sqlStr = readFileByLines(resource.getFile().getAbsolutePath());
            if (sqlStr.size() > 0) {
                int num = executeSqlFile(sqlStr, ifClose);
                if (num > 0)
                    logger.info("sql[{}]Successful implementation", sqlStr);
                else
                    logger.error("There are unexecuted SQL Sentence", sqlStr);
            } else {
                logger.info("sql end of execution");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Embedded sql batch script

We set different sql scripts according to different running environments to avoid data confusion. The core attribute Profile is development and test environment

[failed to transfer the pictures in the external link. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly (img-goszc2ct-1581562336489) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580965050323. PNG))

Test class
package com.ecej.order.basics.service.impl;

import com.alibaba.fastjson.JSON;
import com.ecej.order.basics.Startup;
import com.ecej.order.basics.api.query.OrderQueryService;
import com.ecej.order.basics.bean.dto.WorkOrderDetailDTO;
import com.ecej.order.basics.bean.request.WorkOrderDetailQueryReqParam;
import com.ecej.order.listener.ExtentTestNGIReporterListener;
import com.ecej.order.listener.TestCaseDataPrepareListener;
import com.ecej.order.model.baseResult.ResultMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.Listeners;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

/**
 * Order embedded point test, insert data in advance, and automatically delete the embedded point data after running the test case
 * <p>
 * TestCaseDataPrepareListener
 *
 * @author liulei, liuleiba@ecej.com
 * @version 1.0
 */
@SpringBootTest(classes = Startup.class)
@Listeners({ExtentTestNGIReporterListener.class, TestCaseDataPrepareListener.class})
public class TestNgDataPrepareTest extends AbstractTestNGSpringContextTests {

    private static final Logger logger = LoggerFactory.getLogger(OrderQueryServiceImplXmlTest.class);

    @Autowired
    private OrderQueryService orderQueryService;

    @Parameters({"requestSource", "workOrderNo"})
    @Test(groups = "queryWorkOrderDetailTest")
    public void queryWorkOrderDetailTest(@Optional("99") Integer requestSource,
                                         @Optional("A201801191022356151") String workOrderNo) {
        WorkOrderDetailQueryReqParam param = new WorkOrderDetailQueryReqParam();
        param.setRequestSource(requestSource);
        param.setWorkOrderNo(workOrderNo);
        ResultMessage<WorkOrderDetailDTO> resultMessage = orderQueryService.queryWorkOrderDetail(param);
        Assert.assertEquals(resultMessage.getCode(), 200);
        //Assert that the value of embedded data matches sql
        Assert.assertEquals(resultMessage.getData().getOrderServiceInfo().getWorkOrderId().intValue(), 2000000);
        Assert.assertEquals(resultMessage.getData().getOrderServiceInfo().getWorkOrderNo(), "A201801191022356151");
        logger.info("Order details:{}", JSON.toJSONString(resultMessage));
    }

}
xml test
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Order basic service unit test report 2" parallel="classes" thread-count="1">
    <listeners>
        <listener class-name="com.ecej.order.listener.ExtentTestNGIReporterListener"/>
        <listener class-name="com.ecej.order.listener.TestCaseDataPrepareListener"/>
    </listeners>
    <test verbose="1" preserve-order="true" name="Order inquiry">
        <groups>
            <run>
                <include name="queryWorkOrderDetailTest"/>
            </run>
        </groups>

        <classes>
            <!-- Test classes can be multiple -->
            <class name="com.ecej.order.basics.service.impl.TestNgDataPrepareTest" />
        </classes>
    </test>
</suite>
Test case run results

[failed to transfer the pictures in the external chain. The source station may have anti-theft chain mechanism. It is recommended to save the pictures and upload them directly (img-dnqfpeqi-1581562336489) (C: \ users \ 86151 \ appdata \ roaming \ typora \ typora user images \ 1580965229125. PNG))

7. Summary of test case data problems

1. For local services, it is obvious that [automatic rollback] is the best way, with no code intrusion, security, clean and easy to implement.

2. For the remote service, it is obvious that the way of mock is better to deal with the test cases. Mock test itself is a kind of assumption, and will not have a real impact on the data of the database. The designer only needs to care about the core business of the test cases, and does not need to worry about the additional problems caused by the environment and data.

10. Cases of combination of mockito and TestNg

Why Mock

  1. The traditional test case is to start the whole Spring container at the cost of testing. Otherwise, some beans with deep DI cannot be tested, which takes a long time. However, using mockito can filter unnecessary beans, which takes less time to start.

  2. For the implementation details of the internal bean of the test case, mockito can control the details, which can be detailed to the control of some call parameters, execution times and return values inside the test method. It is more inclined to test the control of internal details and business arrangement. Traditional test cases can't do it.

  3. The most powerful thing about Mockito is that it can mock the dependent bean s of the test class.

    In some cases, for some deeply dependent beans or remote service beans, and these beans or services can basically ensure that there is no problem, but the local environment is limited, and the actual call cannot be generated, you can use mockito to mock these beans, so that the actual call will not be generated, but the call function can be fully simulated in the test case Energy time. Using the traditional test case, any error will lead to the failure of running results.

  4. And TestNg seamless combination, that is, you can use mock to make predictions, you can also use the functional features of TestNg.

Mock test process

There is no difference between mockito and testng test case in Chapter 8, but there is difference in writing java test class

1. Annotate Mockito listener

2. Introduce the implementation bean of the test class and the bean it directly depends on

3. Mock directly dependent bean s, that is, @ MockBean

4. Write a test case, predict the internal implementation of the test method at the mock level and assert the results

package com.ecej.order.basics.service.impl;

import com.alibaba.fastjson.JSON;
import com.ecej.model.po.SvcOrderDailyStatisticsPo;
import com.ecej.order.base.dao.order.OrderDailyStatisticsDao;
import com.ecej.order.basics.api.query.OrderQueryService;
import com.ecej.order.basics.bean.dto.SvcOrderDailyStatisticsDTO;
import com.ecej.order.basics.bean.request.OrderDailyStatisticsReqParam;
import com.ecej.order.basics.manager.OrderQueryManager;
import com.ecej.order.common.enums.MessageEnum;
import com.ecej.order.common.util.DateUtil;
import com.ecej.order.model.baseResult.ResultMessage;
import com.ecej.order.test.listener.ExtentTestNGIReporterListener;
import com.ecej.order.test.testng.OrderStatisticsMockitoTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.*;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static org.mockito.Mockito.*;

/**
 * Order query: TestNg + Mock simple test
 *
 * @author liuleiba@ecej.com
 * @date 2020 5:37:09 PM, January 16, 2016
 */
@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
@Listeners(ExtentTestNGIReporterListener.class)
@ContextConfiguration(classes = {OrderQueryServiceImpl.class, OrderDailyStatisticsDao.class, OrderQueryManager.class})
public class OrderQueryServiceImplMockitoTest extends AbstractTestNGSpringContextTests {

    private static final Logger logger = LoggerFactory.getLogger(OrderStatisticsMockitoTest.class);

    @Autowired
    private OrderQueryService orderQueryService;

    @MockBean
    private OrderDailyStatisticsDao orderDailyStatisticsDao;

    @MockBean
    private OrderQueryManager orderQueryManager;

    /**
     * Build multiple test parameters to cover all possible scenarios
     */
    @DataProvider
    private Object[][] mockParam() {
        //1. Build empty objects
        OrderDailyStatisticsReqParam param1 = new OrderDailyStatisticsReqParam();
        //2. Build incomplete parameter 1
        OrderDailyStatisticsReqParam param2 = new OrderDailyStatisticsReqParam();
        param2.setQueryTime(DateUtil.getDate(new Date(), -2));
        //3. Build incomplete parameter2
        OrderDailyStatisticsReqParam param3 = new OrderDailyStatisticsReqParam();
        param3.setStationId(35200372);
        //4. Build complete parameters
        OrderDailyStatisticsReqParam param4 = new OrderDailyStatisticsReqParam();
        param4.setQueryTime(DateUtil.getDate(new Date(), -2));
        param4.setStationId(35200372);
        return new Object[][]{{1, param1}, {2, param2}, {3, param3}, {4, param4}};
    }

    /**
     * Order query test case
     *
     * @param index Parameter index value
     * @param param Input of actual test
     */
    @Test(groups = "orderSearchManager", dataProvider = "mockParam", alwaysRun = true)
    public void orderSearchManageServiceMockTest(int index, OrderDailyStatisticsReqParam param) {
        logger.info("Test section[{}]Next start: order daily report statistics query input parameter:{}", index, JSON.toJSONString(param));

        //1. Actually call the corresponding test method
        ResultMessage<SvcOrderDailyStatisticsDTO> result = orderQueryService.queryOrderDailyStatistics(param);
        logger.info("Test section[{}]End of time: order daily report statistics query results:{}", index, result.getMessage());
        if (index < 4) {
            //Assert the results of wrong tests of multiple test instances
            Assert.assertEquals(result.getCode(), 1000);
            return;
        }

        //2. During the running of the test method, mock the possible dao calls and predict the return value
        ResultMessage<List<SvcOrderDailyStatisticsPo>> daoResult =
                new ResultMessage(MessageEnum.SUCCESS.getValue(), MessageEnum.SUCCESS.getDesc(), Arrays.asList());
        when(orderDailyStatisticsDao.queryOrderDailyStatistics(any())).thenReturn(daoResult);

        //3. Assert the number of dao calls generated by the test service method
        verify(orderDailyStatisticsDao, times(1)).queryOrderDailyStatistics(any());

        //4. Assert the returned result
        Assert.assertEquals(result.getCode(), 200);
    }
}

11. References

[official website] https://testng.org/doc/documentation-main.html

[tutorial] https://www.bbsmax.com/A/kvJ3Ypq7dg/

[actual combat] https://www.jianshu.com/p/880f5eeba016

So much for today. If this article can help you a little, I hope I can get a little favor from you

Your approval is the power of my writing!

Finishing some aspects of Java architecture, interview information (micro services, clusters, distributed, middleware, etc.), small partners need to be concerned about the public number [programmers], no way to collect their own.

40 original articles published, 90 praised, 130000 visitors+
Private letter follow

Tags: xml Java SQL Database

Posted on Wed, 12 Feb 2020 23:48:00 -0500 by Adthegreat