maven命令使用


一、Maven 命令基本结构

Maven 命令通常遵循以下格式:

mvn [options] [<goal(s)>] [<phase(s)>]

  • [options]可选参数,用于修改 Maven 命令的行为,例如 -D用于传递系统属性,-U用于强制更新依赖等。
  • [<goal(s)>]插件目标,指定要执行的具体任务,例如 compiler:compile
  • [<phase(s)>]生命周期阶段,Maven 内置的构建阶段,例如 compile, package, install。执行某个阶段会自动执行其之前的所有阶段

🚀 二、核心生命周期命令

Maven 有三个内置的生命周期:clean, default(构建), site(文档)。最常用的是 cleandefault

命令 含义 & 作用
mvn clean 清理项目,删除 target目录及其所有构建输出文件(如编译的类文件、打包的 Jar)。
mvn compile 编译项目的主源代码src/main/java目录下的 .java文件),编译后的 .class文件输出到 target/classes目录。
mvn test 运行项目的单元测试src/test/java目录下的测试类)。它会先自动执行 compiletest-compile,运行测试后通常会生成测试报告。
mvn package 将编译好的代码打包成可分发的格式(例如 JAR、WAR)。它会先执行 compiletest。打包后的文件默认位于 target/目录下。
mvn install 将项目打包并安装到本地 Maven 仓库(通常是 ~/.m2/repository/)。这样,本地其他项目就可以通过 GAV 坐标引用这个 Jar 包了。
mvn deploy 将最终的包部署到远程 Maven 仓库(如公司私服 Nexus 或 Artifactory)。这需要你在 pom.xml中配置正确的分布式仓库信息。

这些命令通常可以组合使用,例如 mvn clean compilemvn clean package,最经典的就是 mvn clean install,表示先清理再重新编译、测试、打包并安装


📦 三、依赖管理命令

这些命令帮助你查看和管理项目的依赖关系。

命令 含义 & 作用
mvn dependency:tree 以树形结构显示项目的所有依赖(直接依赖和传递依赖)。这是排查 Jar 包冲突的利器,能清晰看到每个依赖的来源和版本。
mvn dependency:analyze 分析项目依赖,检查是否存在“已声明但未使用”的依赖,或者“未声明但已使用”的依赖(这种情况分析结果会提示,需要注意)。
mvn dependency:resolve 解析并下载项目所需的所有依赖包到本地仓库。
mvn dependency:purge-local-repository 清除本地仓库中未使用的依赖。使用需谨慎,因为它可能会删除其他项目所需的依赖。

ℹ️ 四、项目信息与帮助命令

这些命令用于获取项目信息、有效配置或创建新项目。

命令 含义 & 作用
mvn -vmvn --version 显示 Maven 和 JDK 的版本信息
mvn help:effective-pom 显示项目合并所有父 POM 和当前配置后的最终有效 POM。当配置层级很多时,这个命令非常有用,可以帮你确认配置是否生效。
mvn help:describe 描述某个插件的信息。例如 mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin
mvn archetype:generate 使用 Maven 原型(模板)快速创建一个新项目。它会交互式地引导你输入 GAV 等信息。

⚙️ 五、常用参数 (Options)

这些参数可以附加在上述命令之后,以改变其行为。

参数 含义 & 作用 示例
-Dproperty=value 传递系统属性或插件属性,用途非常广泛。 mvn test -Dtest=MyTestClass(只运行MyTestClass测试类) mvn install -DskipTests(跳过测试)
-U--update-snapshots 强制检查并更新远程仓库的快照(SNAPSHOT)依赖。确保你用到的是最新的快照包。 mvn compile -U
-X--debug 开启调试模式,输出非常详细的 Maven 执行信息,用于排查问题。 mvn install -X
-q--quiet 安静模式,只输出错误信息,减少控制台输出。 mvn compile -q
-P profile-id 激活指定的 Maven Profile。Profile 用于定义不同环境(如 dev, prod)的构建配置。 mvn package -Pprod
-T threads 指定构建使用的线程数,进行并行构建以加快速度。 mvn install -T 4(用4个线程)

💡 六、实用技巧与场景示例

  1. 跳过测试的几种方式

    • -DskipTests: 跳过测试运行,但会编译测试代码。
    • -Dmaven.test.skip=true: 完全跳过测试,既不编译也不运行测试。
    mvn install -DskipTests      # 只编译不运行测试
    mvn package -Dmaven.test.skip=true # 完全不碰测试
    
  2. 运行单个测试类或方法

    mvn test -Dtest=MyTestClass           # 运行单个测试类
    mvn test -Dtest=MyTestClass#testMethod # 运行单个测试方法
    mvn test -Dtest="*MyTest"             # 使用通配符
    
  3. 组合命令:清理并安装

    mvn clean install # 最常用的组合之一,确保是一次全新的构建安装
    
  4. 查看依赖树并找出冲突

    mvn dependency:tree > tree.txt # 将依赖树输出到文件,方便仔细查看
    # 在 tree.txt 中搜索冲突的 Jar 包名,查看不同版本是如何被引入的
    

testng

官网

注解:@Test 标记是用例

属性:

  • priority=-1 默认测试用例的执行顺序市方法名的ASCII码,值越小优先级越高

  • enable=false 是否执行用例

  • description 此方法描述

  • dataProvider 测试方法的数据提供者名称

  • alwaysRun=true 如果设置为 true,即使之前调用的一些方法失败或被跳过,此配置方法也将运行

  • invocationCount=2 测试用例执行次数2

  • groups={“auth”} 该类/方法所属的组列表

  • dependsOnGroups={“auth”} 该方法的依赖组列表

  • dependsOnMethods={“Testlogin”} 测试用例之间的依赖关系

  • successPercentage 预期此方法的成功率百分比

  • invocationTimeOut 该测试应花费的最大毫秒数,用于所有调用次数的累积时间

@DataProvider 标记一个方法为提供测试方法数据的方法

  • name=“test” 此数据提供者的名称
  • parallel=true 测试将在并行中运行

@Factory 标记一个方法作为工厂,该方法返回将被 TestNG 用作测试类的对象。该方法必须返回 Object[]

xml文件中,
tests级别:不同test标签下,用例可以在不同线程执行;相同test标签下,用例在用一个线程执行
method级别:所有用例在不同线程下执行
class级别:不同class标签下,用例可以在不同线程执行;相同clas标签下,用例在用一个线程执行

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<!--<suite name="TestSuite" parallel="tests" thread-count="3">-->
<suite name="TestSuite" preserve-order="true">
   <parameter name="first-name"  value="Cedric"/>

   <!-- 定义可在测试中使用的参数 -->
   <parameter name="browser" value="chrome"/>
   <parameter name="environment" value="staging"/>
   <test name="注册模块">
       <classes>
           <class name="com.register.TestRegister"/>
       </classes>
   </test>
   <test name="登录模块1">
       <classes>
           <class name="com.login.TestLogin1"/>
               <parameter name="username" value="admin"></parameter>
               <parameter name="password" value="123"></parameter>
       </classes>
   </test>
   <test name="登录模块2">
       <classes>
           <class name="com.login.TestLogin2"/>
       </classes>
   </test>
   <test name="计算模块">
       <classes>
           <class name="com.example.CalculatorTest"/>
       </classes>
   </test>
   <!-- 运行 Smoke 和 Regression 测试 ,排除除integration外-->
   <test name="Run Smoke and Regression Tests">
       <groups>
           <run>
               <include name="smoke"/>
               <include name="regression"/>
               <exclude name="integration"/>
           </run>
       </groups>
       <classes>
           <class name="com.example.CalculatorTest"/>
       </classes>
   </test>


   <!-- 使用元组(MetaGroups)定义复杂的分组逻辑 -->
   <test name="Run Custom Group Combinations">
       <groups>
           <define name="all-core-tests">
               <include name="smoke"/>
               <include name="regression"/>
           </define>
           <run>
               <include name="all-core-tests"/>
           </run>
       </groups>
       <classes>
           <class name="com.example.CalculatorTest"/>
       </classes>
   </test>

</suite>

生成测试报告

1:引入依赖库,见下方pom.xml

testng.xml中编写不同路径下的套件执行内容

2:运行测试,生成测试结果的json文件

mvn test clean test

3:生成HTML报告

mvn io.qameta.allure:allure-maven:serve
allure

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.yourcompany</groupId>
    <artifactId>your-test-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- 统一编码防止乱码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <aspectj.version>1.9.7</aspectj.version> 
        <allure.version>2.17.3</allure.version> <!-- 或 2.13.8 -->
    </properties>

    <dependencies>
        <!-- TestNG -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.4.0</version>
            <scope>test</scope>
        </dependency>
        
        <!-- Allure TestNG 适配器 -->
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-testng</artifactId>
            <version>${allure.version}</version> <!-- 版本由上面的property控制 -->
            <scope>test</scope>
        </dependency>
        
        <!-- AspectJ 用于支持 Allure 的步骤记录 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Maven Surefire 插件 (用于执行 TestNG 测试) -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M7</version>
                <configuration>
                    <!-- 配置argLine以支持Allure监听器和AspectJ编织器 -->
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    </argLine>
                    <systemProperties>
                        <property>
                            <!-- 指定Allure结果目录 -->
                            <name>allure.results.directory</name>
                            <value>${project.build.directory}/allure-results</value>
                        </property>
                    </systemProperties>
                </configuration>
            </plugin>
            
            <!-- Allure Maven 插件 (用于生成和展示报告) -->
            <plugin>
                <groupId>io.qameta.allure</groupId>
                <artifactId>allure-maven</artifactId>
                <version>2.10.0</version>
                <configuration>
                    <reportVersion>${allure.version}</reportVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

extentreports
1:导入依赖extentreports

        <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>

2:重写IReporter 的generateReport方法

3:xml文件引入监听器

<listeners>
    <listener class-name="com.example.ExtentTestNGIReporterListener"></listener>
</listeners>

//package com.welab.automation.framework.listener;

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.text.SimpleDateFormat;
import java.util.*;

public class ExtentTestNGIReporterListener implements IReporter {
    static SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    static String dataNow=format.format(new Date());
    //生成的路径以及文件名
    private static final String OUTPUT_FOLDER = "test-output/";
    private static final String FILE_NAME = "测试报告.html";
    private static final String DocumentTitle = "api自动化测试报告";
    private static final String ReportName = "api自动化测试报告";


    private ExtentReports extent;

    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        init();
        boolean createSuiteNode = false;
        if(suites.size()>1){
            createSuiteNode=true;
        }
        for (ISuite suite : suites) {
            Map<String, ISuiteResult> result = suite.getResults();
            //如果suite里面没有任何用例,直接跳过,不在报告里生成
            if(result.size()==0){
                continue;
            }
            //统计suite下的成功、失败、跳过的总用例数
            int suiteFailSize=0;
            int suitePassSize=0;
            int suiteSkipSize=0;
            ExtentTest suiteTest=null;
            //存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
            if(createSuiteNode){
                suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
            }
            boolean createSuiteResultNode = false;
            if(result.size()>1){
                createSuiteResultNode=true;
            }
            for (ISuiteResult r : result.values()) {
                ExtentTest resultNode;
                ITestContext context = r.getTestContext();
                if(createSuiteResultNode){
                    //没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
                    if( null == suiteTest){
                        resultNode = extent.createTest(r.getTestContext().getName());
                    }else{
                        resultNode = suiteTest.createNode(r.getTestContext().getName());
                    }
                }else{
                    resultNode = suiteTest;
                }
                if(resultNode != null){
                    resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName());
                    System.out.println("suite.getName()-->"+suite.getName()+"\tr.getTestContext().getName()-->"+r.getTestContext().getName());
                    if(resultNode.getModel().hasCategory()){
                        resultNode.assignCategory(r.getTestContext().getName());
                    }else{
                        resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
                    }
                    resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
                    resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
                    //统计SuiteResult下的数据
                    int passSize = r.getTestContext().getPassedTests().size();
                    int failSize = r.getTestContext().getFailedTests().size();
                    int skipSize = r.getTestContext().getSkippedTests().size();
                    suitePassSize += passSize;
                    suiteFailSize += failSize;
                    suiteSkipSize += skipSize;
                    if(failSize>0){
                        resultNode.getModel().setStatus(Status.FAIL);
                    }
                    resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
                }
                buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL);
                buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP);
                buildTestNodes(resultNode,context.getPassedTests(), Status.PASS);
            }
            if(suiteTest!= null){
                suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
                if(suiteFailSize>0){
                    suiteTest.getModel().setStatus(Status.FAIL);
                }
            }

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

        extent.flush();
    }

    private void init() {
        //文件夹不存在的话进行创建
        File reportDir= new File(OUTPUT_FOLDER);
        if(!reportDir.exists()&& !reportDir .isDirectory()){
            reportDir.mkdir();
        }
        ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
        // 设置静态文件的DNS
        //怎么样解决cdn.rawgit.com访问不了的情况
        htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);

        htmlReporter.config().setDocumentTitle(DocumentTitle);
        htmlReporter.config().setReportName(ReportName);
        htmlReporter.config().setChartVisibilityOnOpen(true);
        htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
        htmlReporter.config().setTheme(Theme.STANDARD);
        htmlReporter.config().setCSS(".node.level-1  ul{ display:none;} .node.level-1.active ul{display:block;}");
        extent = new ExtentReports();
        extent.attachReporter(htmlReporter);
        extent.setReportUsesManualConfiguration(true);
    }

    private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
        //存在父节点时,获取父节点的标签
        String[] categories=new String[0];
        if(extenttest != null ){
            List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
            categories = new String[categoryList.size()];
            for(int index=0;index<categoryList.size();index++){
                categories[index] = categoryList.get(index).getName();
            }
        }

        ExtentTest test;

        if (tests.size() > 0) {
            //调整用例排序,按时间排序
            Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
                @Override
                public int compare(ITestResult o1, ITestResult o2) {
                    return o1.getStartMillis()<o2.getStartMillis()?-1:1;
                }
            });
            treeSet.addAll(tests.getAllResults());
            for (ITestResult result : treeSet) {
                Object[] parameters = result.getParameters();
                String name="";
                //如果有参数,则使用参数的toString组合代替报告中的name
                for(Object param:parameters){
                    name+=param.toString();
                }
                if(name.length()>0){
                    if(name.length()>50){
                        name= name.substring(0,49)+"...";
                    }
                }else{
                    name = result.getMethod().getMethodName();
                }
                if(extenttest==null){
                    test = extent.createTest(name);
                }else{
                    //作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
                    test = extenttest.createNode(name).assignCategory(categories);
                }
                //test.getModel().setDescription(description.toString());
                //test = extent.createTest(result.getMethod().getMethodName());
                for (String group : result.getMethod().getGroups()) {
                    test.assignCategory(group);
                    test.assignCategory(result.getMethod().getDescription());//描述
                }

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

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

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

文章作者: 读序
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 读序 !
  目录