{"id":39,"date":"2026-04-09T10:00:00","date_gmt":"2026-04-09T02:00:00","guid":{"rendered":"http:\/\/localhost:8888\/?p=39"},"modified":"2026-07-09T16:17:45","modified_gmt":"2026-07-09T08:17:45","slug":"jenkins%e6%b5%8b%e8%af%95%e6%b5%81%e6%b0%b4%e7%ba%bf%e6%90%ad%e5%bb%ba","status":"publish","type":"post","link":"https:\/\/blog.runnergo.cn\/?p=39","title":{"rendered":"Jenkins\u6d4b\u8bd5\u6d41\u6c34\u7ebf\u642d\u5efa &#8211; CI\/CD\u4e2d\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u96c6\u6210\u5b9e\u6218"},"content":{"rendered":"<h1>Jenkins\u6d4b\u8bd5\u6d41\u6c34\u7ebf\u642d\u5efa &#8211; CI\/CD\u4e2d\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u96c6\u6210\u5b9e\u6218<\/h1>\n<p>&gt; \u4f5c\u8005\uff1aDevOps\u8001\u5175<br \/>\n&#8212;<\/p>\n<h2>\u524d\u8a00<\/h2>\n<p>\u5927\u5bb6\u597d\uff0c\u6211\u662fDevOps\u8001\u5175\u3002\u505aCI\/CD\u5b9e\u8df510\u5e74\uff0c\u6d4b\u8bd5\u96c6\u6210\u662f\u6700\u5bb9\u6613\u88ab\u5ffd\u89c6\u7684\u73af\u8282\u3002\u4eca\u5929\u5206\u4eabJenkins\u6d4b\u8bd5\u6d41\u6c34\u7ebf\u7684\u642d\u5efa\u7ecf\u9a8c\u3002<\/p>\n<p>&#8212;<\/p>\n<h2>\u4e00\u3001\u6d4b\u8bd5\u5728CI\/CD\u4e2d\u7684\u4f4d\u7f6e<\/h2>\n<h3>\u4f20\u7edfCI\u6d41\u7a0b<\/h3>\n<p><code>`<\/code><br \/>\n\u4ee3\u7801\u63d0\u4ea4 -&gt; \u7f16\u8bd1 -&gt; \u90e8\u7f72 -&gt; \u6d4b\u8bd5\uff08\u624b\u52a8\uff09<br \/>\n<code>`<\/code><\/p>\n<p>\u6d4b\u8bd5\u5f80\u5f80\u5728\u90e8\u7f72\u540e\u624b\u52a8\u6267\u884c\uff0c\u53cd\u9988\u6162\u3001\u6548\u7387\u4f4e\u3002<\/p>\n<h3>\u7406\u60f3CI\/CD\u6d41\u7a0b<\/h3>\n<p><code>`<\/code><br \/>\n\u4ee3\u7801\u63d0\u4ea4 -&gt; \u5355\u5143\u6d4b\u8bd5 -&gt; \u6784\u5efa -&gt; \u96c6\u6210\u6d4b\u8bd5 -&gt; \u90e8\u7f72 -&gt; \u81ea\u52a8\u5316\u6d4b\u8bd5 -&gt; \u76d1\u63a7<br \/>\n<code>`<\/code><\/p>\n<p>\u6d4b\u8bd5\u5e94\u8be5\uff1a<\/p>\n<ul>\n<li><strong>\u5de6\u79fb<\/strong>\uff1a\u5c3d\u65e9\u6267\u884c\uff0c\u5feb\u901f\u53cd\u9988<\/li>\n<li><strong>\u81ea\u52a8\u5316<\/strong>\uff1a\u51cf\u5c11\u4eba\u5de5\u5e72\u9884<\/li>\n<li><strong>\u96c6\u6210<\/strong>\uff1a\u5d4c\u5165\u6d41\u6c34\u7ebf\u5404\u73af\u8282<\/li>\n<\/ul>\n<p>&#8212;<\/p>\n<h2>\u4e8c\u3001Jenkins Pipeline\u57fa\u7840<\/h2>\n<h3>Pipeline\u6982\u5ff5<\/h3>\n<p>Jenkins Pipeline\u662f\u4e00\u7cfb\u5217\u4efb\u52a1\u7684\u7ec4\u5408\uff1a<\/p>\n<ul>\n<li>Stage\uff1a\u9636\u6bb5\u5212\u5206<\/li>\n<li>Step\uff1a\u5177\u4f53\u4efb\u52a1<\/li>\n<li>Parallel\uff1a\u5e76\u884c\u6267\u884c<\/li>\n<\/ul>\n<h3>Pipeline\u8bed\u6cd5<\/h3>\n<p><code>`<\/code>groovy<br \/>\npipeline {<br \/>\n    agent any<\/p>\n<p>    stages {<br \/>\n        stage(&#8216;\u6784\u5efa&#8217;) {<br \/>\n            steps {<br \/>\n                sh &#8216;mvn clean package&#8217;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        stage(&#8216;\u6d4b\u8bd5&#8217;) {<br \/>\n            steps {<br \/>\n                sh &#8216;mvn test&#8217;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        stage(&#8216;\u90e8\u7f72&#8217;) {<br \/>\n            steps {<br \/>\n                sh &#8216;deploy.sh&#8217;<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u4e09\u3001\u6d4b\u8bd5\u6d41\u6c34\u7ebf\u642d\u5efa<\/h2>\n<h3>\u5355\u5143\u6d4b\u8bd5\u96c6\u6210<\/h3>\n<p><code>`<\/code>groovy<br \/>\npipeline {<br \/>\n    agent any<\/p>\n<p>    stages {<br \/>\n        stage(&#8216;\u5355\u5143\u6d4b\u8bd5&#8217;) {<br \/>\n            steps {<br \/>\n                sh &#8221;&#8217;<br \/>\n                    # Python\u5355\u5143\u6d4b\u8bd5<br \/>\n                    pytest tests\/unit -v &#8211;cov=src &#8211;cov-report=xml<br \/>\n                &#8221;&#8217;<br \/>\n            }<br \/>\n            post {<br \/>\n                always {<br \/>\n                    \/\/ \u53d1\u5e03\u6d4b\u8bd5\u62a5\u544a<br \/>\n                    publishHTML([<br \/>\n                        reportDir: &#8216;reports&#8217;,<br \/>\n                        reportFiles: &#8216;coverage.html&#8217;,<br \/>\n                        reportName: &#8216;\u5355\u5143\u6d4b\u8bd5\u8986\u76d6\u7387&#8217;<br \/>\n                    ])<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<h3>\u96c6\u6210\u6d4b\u8bd5\u96c6\u6210<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;\u96c6\u6210\u6d4b\u8bd5&#8217;) {<br \/>\n    steps {<br \/>\n        sh &#8221;&#8217;<br \/>\n            # \u542f\u52a8\u6d4b\u8bd5\u73af\u5883<br \/>\n            docker-compose -f docker-compose.test.yml up -d<\/p>\n<p>            # \u7b49\u5f85\u670d\u52a1\u5c31\u7eea<br \/>\n            sleep 30<\/p>\n<p>            # \u6267\u884c\u96c6\u6210\u6d4b\u8bd5<br \/>\n            pytest tests\/integration -v<\/p>\n<p>            # \u6e05\u7406\u73af\u5883<br \/>\n            docker-compose -f docker-compose.test.yml down<br \/>\n        &#8221;&#8217;<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<h3>API\u6d4b\u8bd5\u96c6\u6210<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;API\u6d4b\u8bd5&#8217;) {<br \/>\n    steps {<br \/>\n        \/\/ \u4f7f\u7528Newman\u8fd0\u884cPostman\u6d4b\u8bd5<br \/>\n        sh &#8221;&#8217;<br \/>\n            npm install -g newman<br \/>\n            newman run tests\/collection.json<br \/>\n                -e tests\/environment.json<br \/>\n                -r cli,html<br \/>\n                &#8211;reporter-html-export reports\/api-report.html<br \/>\n        &#8221;&#8217;<br \/>\n    }<br \/>\n    post {<br \/>\n        always {<br \/>\n            publishHTML([<br \/>\n                reportDir: &#8216;reports&#8217;,<br \/>\n                reportFiles: &#8216;api-report.html&#8217;,<br \/>\n                reportName: &#8216;API\u6d4b\u8bd5\u62a5\u544a&#8217;<br \/>\n            ])<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<h3>RunnerGo\u96c6\u6210<\/h3>\n<p>RunnerGo\u53ef\u4ee5\u901a\u8fc7API\u89e6\u53d1\u6d4b\u8bd5\uff1a<\/p>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;RunnerGo\u81ea\u52a8\u5316\u6d4b\u8bd5&#8217;) {<br \/>\n    steps {<br \/>\n        sh &#8221;&#8217;<br \/>\n            # \u8c03\u7528RunnerGo API\u89e6\u53d1\u6d4b\u8bd5<br \/>\n            curl -X POST<br \/>\n                -H &#8220;Authorization: Bearer ${RUNNERGO_TOKEN}&#8221;<br \/>\n                -H &#8220;Content-Type: application\/json&#8221;<br \/>\n                -d &#8216;{&#8220;plan_id&#8221;: &#8220;${PLAN_ID}&#8221;, &#8220;scene_ids&#8221;: []}&#8217;<br \/>\n                ${RUNNERGO_URL}\/api\/run<br \/>\n        &#8221;&#8217;<\/p>\n<p>        \/\/ \u7b49\u5f85\u6d4b\u8bd5\u5b8c\u6210\u5e76\u83b7\u53d6\u62a5\u544a<br \/>\n        script {<br \/>\n            def reportId = &#8221;<br \/>\n            def status = &#8216;running&#8217;<\/p>\n<p>            while (status == &#8216;running&#8217;) {<br \/>\n                sleep(time: 10, unit: &#8216;SECONDS&#8217;)<\/p>\n<p>                def response = sh(<br \/>\n                    script: &#8216;curl -s ${RUNNERGO_URL}\/api\/status&#8217;,<br \/>\n                    returnStdout: true<br \/>\n                ).trim()<\/p>\n<p>                def json = new groovy.json.JsonSlurper().parseText(response)<br \/>\n                status = json.status<br \/>\n                reportId = json.report_id<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u56db\u3001\u6d4b\u8bd5\u73af\u5883\u7ba1\u7406<\/h2>\n<h3>Docker\u73af\u5883<\/h3>\n<p>\u4f7f\u7528Docker\u521b\u5efa\u9694\u79bb\u6d4b\u8bd5\u73af\u5883\uff1a<\/p>\n<p><code>`<\/code>yaml<\/p>\n<h1>docker-compose.test.yml<\/h1>\n<p>version: &#8216;3&#8217;<br \/>\nservices:<br \/>\n  app:<br \/>\n    build: .<br \/>\n    ports:<br \/>\n      &#8211; &#8220;8080:8080&#8221;<br \/>\n    environment:<br \/>\n      &#8211; DB_HOST=test-db<br \/>\n      &#8211; DB_PORT=3306<\/p>\n<p>  test-db:<br \/>\n    image: mysql:8<br \/>\n    environment:<br \/>\n      &#8211; MYSQL_ROOT_PASSWORD=test<br \/>\n      &#8211; MYSQL_DATABASE=test_db<\/p>\n<p>  redis:<br \/>\n    image: redis:alpine<br \/>\n<code>`<\/code><\/p>\n<h3>\u73af\u5883\u53d8\u91cf\u7ba1\u7406<\/h3>\n<p><code>`<\/code>groovy<br \/>\npipeline {<br \/>\n    agent any<\/p>\n<p>    environment {<br \/>\n        TEST_ENV = &#8216;staging&#8217;<br \/>\n        DB_HOST = &#8216;test-db&#8217;<br \/>\n        API_URL = &#8216;http:\/\/test-api.example.com&#8217;<br \/>\n    }<\/p>\n<p>    stages {<br \/>\n        stage(&#8216;\u6d4b\u8bd5&#8217;) {<br \/>\n            steps {<br \/>\n                sh &#8216;pytest tests&#8217;<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u4e94\u3001\u6d4b\u8bd5\u62a5\u544a\u96c6\u6210<\/h2>\n<h3>JUnit\u62a5\u544a<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;\u6d4b\u8bd5&#8217;) {<br \/>\n    steps {<br \/>\n        sh &#8216;pytest &#8211;junitxml=reports\/junit.xml&#8217;<br \/>\n    }<br \/>\n    post {<br \/>\n        always {<br \/>\n            junit &#8216;reports\/*.xml&#8217;<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<h3>Allure\u62a5\u544a<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;\u6d4b\u8bd5&#8217;) {<br \/>\n    steps {<br \/>\n        sh &#8216;pytest &#8211;alluredir=allure-results&#8217;<br \/>\n    }<br \/>\n    post {<br \/>\n        always {<br \/>\n            allure([<br \/>\n                includeProperties: false,<br \/>\n                jdk: &#8221;,<br \/>\n                results: [[path: &#8216;allure-results&#8217;]],<br \/>\n                reportBuildPolicy: &#8216;ALWAYS&#8217;,<br \/>\n                report: &#8216;allure-report&#8217;<br \/>\n            ])<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u516d\u3001\u8d28\u91cf\u95e8\u7981<\/h2>\n<h3>\u6d4b\u8bd5\u5931\u8d25\u963b\u65ad<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;\u6d4b\u8bd5&#8217;) {<br \/>\n    steps {<br \/>\n        script {<br \/>\n            def result = sh(script: &#8216;pytest&#8217;, returnStatus: true)<\/p>\n<p>            if (result != 0) {<br \/>\n                error(&#8216;\u6d4b\u8bd5\u5931\u8d25\uff0c\u963b\u65ad\u90e8\u7f72&#8217;)<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<h3>\u8986\u76d6\u7387\u95e8\u7981<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;\u8986\u76d6\u7387\u68c0\u67e5&#8217;) {<br \/>\n    steps {<br \/>\n        script {<br \/>\n            def coverage = sh(<br \/>\n                script: &#8216;python get_coverage.py&#8217;,<br \/>\n                returnStdout: true<br \/>\n            ).trim().toDouble()<\/p>\n<p>            if (coverage &lt; 80) {<br \/>\n                error(&quot;\u8986\u76d6\u7387 ${coverage}% \u4e0d\u8fbe\u6807\uff08\u8981\u6c4280%\u4ee5\u4e0a\uff09&quot;)<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u4e03\u3001\u5e76\u884c\u6d4b\u8bd5<\/h2>\n<h3>\u5e76\u884c\u6267\u884cStage<\/h3>\n<p><code>`<\/code>groovy<br \/>\npipeline {<br \/>\n    agent any<\/p>\n<p>    stages {<br \/>\n        stage(&#8216;\u5e76\u884c\u6d4b\u8bd5&#8217;) {<br \/>\n            parallel {<br \/>\n                stage(&#8216;\u5355\u5143\u6d4b\u8bd5&#8217;) {<br \/>\n                    steps {<br \/>\n                        sh &#8216;pytest tests\/unit&#8217;<br \/>\n                    }<br \/>\n                }<br \/>\n                stage(&#8216;API\u6d4b\u8bd5&#8217;) {<br \/>\n                    steps {<br \/>\n                        sh &#8216;newman run api-tests.json&#8217;<br \/>\n                    }<br \/>\n                }<br \/>\n                stage(&#8216;UI\u6d4b\u8bd5&#8217;) {<br \/>\n                    steps {<br \/>\n                        sh &#8216;pytest tests\/ui&#8217;<br \/>\n                    }<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u516b\u3001\u5b9a\u65f6\u6d4b\u8bd5\u4efb\u52a1<\/h2>\n<h3>\u5b9a\u65f6\u89e6\u53d1<\/h3>\n<p><code>`<\/code>groovy<br \/>\npipeline {<br \/>\n    agent any<\/p>\n<p>    triggers {<br \/>\n        \/\/ \u6bcf\u5929\u65e9\u4e0a9\u70b9\u6267\u884c<br \/>\n        cron(&#8216;0 9 * * *&#8217;)<br \/>\n    }<\/p>\n<p>    stages {<br \/>\n        stage(&#8216;\u5b9a\u65f6\u6d4b\u8bd5&#8217;) {<br \/>\n            steps {<br \/>\n                sh &#8216;pytest tests\/regression&#8217;<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u603b\u7ed3<\/h2>\n<p>Jenkins\u6d4b\u8bd5\u6d41\u6c34\u7ebf\u8981\u70b9\uff1a<\/p>\n<p>| \u73af\u8282 | \u914d\u7f6e |<br \/>\n|&#8212;&#8212;|&#8212;&#8212;|<br \/>\n| \u5355\u5143\u6d4b\u8bd5 | pytest + \u8986\u76d6\u7387\u62a5\u544a |<br \/>\n| \u96c6\u6210\u6d4b\u8bd5 | Docker\u73af\u5883\u9694\u79bb |<br \/>\n| API\u6d4b\u8bd5 | Newman\/RunnerGo\u96c6\u6210 |<br \/>\n| \u62a5\u544a | JUnit\/Allure\u53d1\u5e03 |<br \/>\n| \u95e8\u7981 | \u8986\u76d6\u7387\u3001\u5931\u8d25\u963b\u65ad |<br \/>\n| \u5e76\u884c | parallel stage |<\/p>\n<p>&#8212;<\/p>\n<p><strong>\u4e0b\u671f\u9884\u544a\uff1aAppium 2.0\u65b0\u67b6\u6784\u89e3\u6790 &#8211; \u79fb\u52a8\u7aef\u81ea\u52a8\u5316\u6d4b\u8bd5\u8fdb\u9636\u6307\u5357<\/strong><\/p>\n<p>&gt; \u4f5c\u8005\uff1aDevOps\u8001\u5175\uff0c10\u5e74CI\/CD\u5b9e\u8df5\u7ecf\u9a8c<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Jenkins\u6d4b\u8bd5\u6d41\u6c34\u7ebf\u642d\u5efa &#8211; CI\/CD\u4e2d\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u96c6\u6210\u5b9e\u6218 &gt; \u4f5c\u8005\uff1aDevOps\u8001\u5175 &#8212; \u524d\u8a00 \u5927\u5bb6\u597d\uff0c\u6211\u662fDevOps\u8001\u5175\u3002\u505aCI\/CD\u5b9e\u8df510\u5e74\uff0c\u6d4b\u8bd5\u96c6\u6210\u662f&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-39","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/posts\/39","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=39"}],"version-history":[{"count":0,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/posts\/39\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=39"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=39"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=39"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}