การสร้างเวิร์กโฟลว์การทดสอบการรวมอย่างต่อเนื่องโดยใช้ GitHub Actions

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างรวดเร็ว ↬ ด้วยความช่วยเหลือของบทช่วยสอนนี้ คุณสามารถเรียนรู้วิธีสร้างเวิร์กโฟลว์การรวมอย่างต่อเนื่องสำหรับ Node JS REST API ของคุณโดยใช้ GitHub Actions รวมถึงวิธีรายงานความครอบคลุมการทดสอบด้วย Coveralls

เมื่อมีส่วนร่วมในโครงการบนแพลตฟอร์มการควบคุมเวอร์ชันเช่น GitHub และ Bitbucket หลักการคือมีสาขาหลักที่มีฐานโค้ดที่ใช้งานได้ นอกจากนี้ยังมีสาขาอื่นๆ ที่นักพัฒนาหลายคนสามารถทำงานกับสำเนาของ main เพื่อเพิ่มคุณสมบัติใหม่ แก้ไขข้อผิดพลาด และอื่นๆ เป็นเรื่องที่สมเหตุสมผลเพราะจะง่ายต่อการตรวจสอบผลกระทบที่การเปลี่ยนแปลงที่เข้ามาจะมีต่อโค้ดที่มีอยู่ หากมีข้อผิดพลาดใด ๆ ก็สามารถตรวจสอบและแก้ไขได้อย่างง่ายดายก่อนที่จะรวมการเปลี่ยนแปลงเข้ากับสาขาหลัก อาจใช้เวลานานในการค้นหาโค้ดทุกบรรทัดด้วยตนเองเพื่อค้นหาข้อผิดพลาดหรือจุดบกพร่อง แม้กระทั่งสำหรับโครงการขนาดเล็ก นั่นคือที่มาของการบูรณาการอย่างต่อเนื่อง

การบูรณาการอย่างต่อเนื่อง (CI) คืออะไร?

“การบูรณาการอย่างต่อเนื่อง (CI) เป็นแนวทางปฏิบัติในการรวมการเปลี่ยนแปลงโค้ดโดยอัตโนมัติจากผู้ร่วมให้ข้อมูลหลายรายในโครงการซอฟต์แวร์เดียว”

— Atlassian.com

แนวคิดทั่วไปเบื้องหลังการรวมอย่างต่อเนื่อง (CI) คือการทำให้แน่ใจว่าการเปลี่ยนแปลงที่เกิดขึ้นกับโครงการจะไม่ "ทำลายงานสร้าง" นั่นคือทำลายฐานรหัสที่มีอยู่ การใช้การรวมอย่างต่อเนื่องในโครงการของคุณ ขึ้นอยู่กับว่าคุณตั้งค่าเวิร์กโฟลว์ของคุณอย่างไร จะสร้างบิลด์เมื่อใดก็ตามที่ใครทำการเปลี่ยนแปลงกับที่เก็บ

ดังนั้น Build คืออะไร?

บิลด์ — ในบริบทนี้ — คือการรวบรวมซอร์สโค้ดให้อยู่ในรูปแบบที่ปฏิบัติการได้ หากประสบความสำเร็จ แสดงว่าการเปลี่ยนแปลงที่เข้ามาจะไม่ส่งผลเสียต่อ codebase และพร้อมดำเนินการ อย่างไรก็ตาม หากบิลด์ล้มเหลว การเปลี่ยนแปลงจะต้องได้รับการประเมินใหม่ นั่นคือเหตุผลที่แนะนำให้ทำการเปลี่ยนแปลงโปรเจ็กต์โดยการทำงานกับสำเนาของโปรเจ็กต์ในสาขาอื่นก่อนที่จะรวมเข้ากับโค้ดเบสหลัก ด้วยวิธีนี้ หากบิลด์พัง จะเป็นการง่ายกว่าที่จะหาว่าข้อผิดพลาดมาจากไหน และไม่ส่งผลต่อซอร์สโค้ดหลักของคุณด้วย

“ยิ่งจับจุดบกพร่องได้เร็วเท่าไหร่ ก็ยิ่งซ่อมได้ถูกกว่า”

— David Farley, การส่งมอบอย่างต่อเนื่อง: การเปิดตัวซอฟต์แวร์ที่เชื่อถือได้ผ่านการสร้าง การทดสอบ และการปรับใช้อัตโนมัติ

มีเครื่องมือหลายอย่างที่ช่วยในการสร้างการผสานรวมอย่างต่อเนื่องสำหรับโครงการของคุณ ซึ่งรวมถึง Jenkins, TravisCI, CircleCI, GitLab CI, GitHub Actions เป็นต้น สำหรับบทช่วยสอนนี้ ฉันจะใช้ GitHub Actions

GitHub Actions เพื่อการบูรณาการอย่างต่อเนื่อง

CI Actions เป็นฟีเจอร์ที่ค่อนข้างใหม่บน GitHub และช่วยให้สามารถสร้างเวิร์กโฟลว์ที่รันการสร้างและทดสอบโปรเจ็กต์ของคุณโดยอัตโนมัติ เวิร์กโฟลว์ประกอบด้วยงานอย่างน้อยหนึ่งงานที่สามารถเปิดใช้งานได้เมื่อมีเหตุการณ์เกิดขึ้น เหตุการณ์นี้อาจเป็นการพุชไปยังสาขาใดก็ได้ใน repo หรือการสร้างคำขอดึง ฉันจะอธิบายข้อกำหนดเหล่านี้โดยละเอียดเมื่อเราดำเนินการต่อไป

มาเริ่มกันเลย!

ข้อกำหนดเบื้องต้น

นี่คือบทช่วยสอนสำหรับผู้เริ่มต้น ดังนั้นฉันจะพูดถึง GitHub Actions CI ในระดับพื้นผิวเป็นส่วนใหญ่ ผู้อ่านควรคุ้นเคยกับการสร้าง Node JS REST API โดยใช้ฐานข้อมูล PostgreSQL, Sequelize ORM และการเขียนการทดสอบด้วย Mocha และ Chai

คุณควรติดตั้งสิ่งต่อไปนี้ในเครื่องของคุณด้วย:

  • โหนดเจเอส,
  • PostgreSQL,
  • นพ.
  • VSCode (หรือโปรแกรมแก้ไขและเทอร์มินัลที่คุณเลือก)

ฉันจะใช้ประโยชน์จาก REST API ที่ฉันสร้างแล้วชื่อ countries-info-api เป็น api ธรรมดาๆ ที่ไม่มีการอนุญาตตามบทบาท (ในขณะที่เขียนบทช่วยสอนนี้) ซึ่งหมายความว่าทุกคนสามารถเพิ่ม ลบ และ/หรืออัปเดตรายละเอียดของประเทศได้ แต่ละประเทศจะมีรหัส (UUID ที่สร้างขึ้นโดยอัตโนมัติ) ชื่อ เมืองหลวง และประชากร เพื่อให้บรรลุสิ่งนี้ ฉันจึงใช้ Node js, express js framework และ Postgresql สำหรับฐานข้อมูล

ฉันจะอธิบายสั้น ๆ วิธีตั้งค่าเซิร์ฟเวอร์ ฐานข้อมูล ก่อนเริ่มเขียนการทดสอบความครอบคลุมของการทดสอบ และไฟล์เวิร์กโฟลว์สำหรับการผสานรวมอย่างต่อเนื่อง

คุณสามารถโคลน repo countries-info-api เพื่อติดตามหรือสร้าง API ของคุณเอง

เทคโนโลยีที่ใช้ : Node Js, NPM (ตัวจัดการแพ็คเกจสำหรับ Javascript), ฐานข้อมูล Postgresql, ภาคต่อ ORM, Babel

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

การตั้งค่าเซิร์ฟเวอร์

ก่อนตั้งค่าเซิร์ฟเวอร์ ฉันได้ติดตั้งการพึ่งพาจาก npm

 npm install express dotenv cors npm install --save-dev @babel/core @babel/cli @babel/preset-env nodemon

ฉันใช้กรอบงานด่วนและเขียนในรูปแบบ ES6 ดังนั้นฉันต้องใช้ Babeljs เพื่อคอมไพล์โค้ดของฉัน คุณสามารถอ่านเอกสารอย่างเป็นทางการเพื่อทราบข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานและวิธีกำหนดค่าสำหรับโครงการของคุณ Nodemon จะตรวจจับการเปลี่ยนแปลงใดๆ ที่เกิดขึ้นกับโค้ดและรีสตาร์ทเซิร์ฟเวอร์โดยอัตโนมัติ

หมายเหตุ : แพ็คเกจ Npm ที่ติดตั้งโดยใช้ --save-dev จำเป็นในระหว่างขั้นตอนการพัฒนาเท่านั้น และมองเห็นได้ภายใต้ devDependencies ในไฟล์ package.json

ฉันเพิ่มสิ่งต่อไปนี้ในไฟล์ index.js ของฉัน:

 import express from "express"; import bodyParser from "body-parser"; import cors from "cors"; import "dotenv/config"; const app = express(); const port = process.env.PORT; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.get("/", (req, res) => { res.send({message: "Welcome to the homepage!"}) }) app.listen(port, () => { console.log(`Server is running on ${port}...`) })

สิ่งนี้ตั้งค่า API ของเราให้ทำงานบนสิ่งใดก็ตามที่กำหนดให้กับตัวแปร PORT ในไฟล์ . .env นี่คือที่ที่เราจะประกาศตัวแปรที่เราไม่ต้องการให้ผู้อื่นเข้าถึงได้ง่าย dotenv npm โหลดตัวแปรสภาพแวดล้อมของเราจาก . .env

ตอนนี้เมื่อฉันรัน npm run start ในเทอร์มินัล ฉันได้รับสิ่งนี้:

เซิร์ฟเวอร์กำลังทำงาน
เซิร์ฟเวอร์ทำงานบนพอร์ต 3000 (ตัวอย่างขนาดใหญ่)

อย่างที่คุณเห็น เซิร์ฟเวอร์ของเราเริ่มทำงานแล้ว เย้!

ลิงก์นี้ https://127.0.0.1:your_port_number/ ในเว็บเบราว์เซอร์ของคุณควรส่งคืนข้อความต้อนรับ นั่นคือตราบใดที่เซิร์ฟเวอร์ยังทำงานอยู่

เบราว์เซอร์
หน้าแรก. (ตัวอย่างขนาดใหญ่)

ถัดไป ฐานข้อมูลและโมเดล

ฉันสร้างโมเดลประเทศโดยใช้ Sequelize และเชื่อมต่อกับฐานข้อมูล Postgres ของฉัน Sequelize เป็น ORM สำหรับ Nodejs ข้อได้เปรียบที่สำคัญคือช่วยเราประหยัดเวลาในการเขียนแบบสอบถาม SQL ดิบ

เนื่องจากเราใช้ Postgresql ฐานข้อมูลจึงสามารถสร้างได้โดยใช้บรรทัดคำสั่ง psql โดยใช้คำสั่ง CREATE DATABASE database_name สิ่งนี้สามารถทำได้บนเทอร์มินัลของคุณ แต่ฉันชอบ PSQL Shell

ในไฟล์ env เราจะตั้งค่าสตริงการเชื่อมต่อของฐานข้อมูลของเรา ตามรูปแบบด้านล่างนี้

 TEST_DATABASE_URL = postgres://<db_username>:<db_password>@127.0.0.1:5432/<database_name>

สำหรับแบบจำลองของฉัน ฉันทำตามบทช่วยสอนภาคต่อนี้ ง่ายต่อการติดตามและอธิบายทุกอย่างเกี่ยวกับการตั้งค่า Sequelize

ต่อไป ฉันจะเขียนการทดสอบสำหรับโมเดลที่ฉันเพิ่งสร้างและตั้งค่าความครอบคลุมบน Coverall

การเขียนแบบทดสอบและการรายงานผล

ทำไมต้องเขียนแบบทดสอบ? โดยส่วนตัวแล้ว ฉันเชื่อว่าการทดสอบการเขียนจะช่วยให้คุณในฐานะนักพัฒนาเข้าใจได้ดีขึ้นว่าซอฟต์แวร์ของคุณคาดว่าจะทำงานอย่างไรในมือของผู้ใช้ เนื่องจากเป็นกระบวนการระดมความคิด นอกจากนี้ยังช่วยให้คุณค้นพบข้อบกพร่องตรงเวลา

การทดสอบ:

มีวิธีการทดสอบซอฟต์แวร์ที่แตกต่างกัน อย่างไรก็ตาม สำหรับบทช่วยสอนนี้ ฉันใช้การทดสอบแบบหน่วยและแบบ end-to-end

ฉันเขียนการทดสอบโดยใช้กรอบการทดสอบ Mocha และไลบรารีการยืนยันของ Chai ฉันยังติดตั้ง sequelize-test-helpers เพื่อช่วยทดสอบโมเดลที่ฉันสร้างขึ้นโดยใช้ sequelize.define

ครอบคลุมการทดสอบ:

ขอแนะนำให้ตรวจสอบความครอบคลุมในการทดสอบของคุณ เนื่องจากผลลัพธ์จะแสดงให้เห็นว่ากรณีทดสอบของเราครอบคลุมโค้ดจริงหรือไม่ และมีการใช้โค้ดมากน้อยเพียงใดเมื่อเราเรียกใช้กรณีทดสอบของเรา

ฉันใช้อิสตันบูล (เครื่องมือครอบคลุมการทดสอบ), nyc (ไคลเอนต์ CLI ของ Instabul) และ Coveralls

ตามเอกสาร อิสตันบูลใช้เครื่องมือโค้ด JavaScript ES5 และ ES2015+ พร้อมตัวนับบรรทัด เพื่อให้คุณสามารถติดตามว่าการทดสอบหน่วยของคุณใช้โค้ดเบสของคุณได้ดีเพียงใด

ในไฟล์ package.json ของฉัน สคริปต์ทดสอบจะรันการทดสอบและสร้างรายงาน

 { "scripts": { "test": "nyc --reporter=lcov --reporter=text mocha -r @babel/register ./src/test/index.js" } }

ในกระบวนการนี้ จะสร้างโฟลเดอร์ .nyc_output ที่มีข้อมูลความครอบคลุมดิบและโฟลเดอร์ coverage ที่มีไฟล์รายงานความครอบคลุม ไฟล์ทั้งสองไม่จำเป็นใน repo ของฉัน ดังนั้นฉันจึงวางไว้ในไฟล์ .gitignore

ตอนนี้เราได้สร้างรายงานแล้ว เราต้องส่งไปยัง Coveralls สิ่งหนึ่งที่ยอดเยี่ยมเกี่ยวกับ Coveralls (และเครื่องมือความครอบคลุมอื่น ๆ ฉันถือว่า) เป็นวิธีที่รายงานความครอบคลุมการทดสอบของคุณ ความครอบคลุมจะแจกแจงเป็นไฟล์ตามแต่ละไฟล์ และคุณสามารถดูความครอบคลุมที่เกี่ยวข้อง บรรทัดที่ครอบคลุม และบรรทัดที่ไม่ได้รับ และสิ่งที่เปลี่ยนแปลงไปในการครอบคลุมของรุ่น

ในการเริ่มต้น ให้ติดตั้งแพ็คเกจ coveralls npm คุณต้องลงชื่อเข้าใช้ coveralls และเพิ่ม repo เข้าไปด้วย

ชุดคลุม repo
Repo เชื่อมต่อกับ Coveralls (ตัวอย่างขนาดใหญ่)

จากนั้นตั้งค่า coveralls สำหรับโปรเจ็กต์ javascript ของคุณโดยสร้างไฟล์ coveralls.yml ในไดเร็กทอรีรากของคุณ ไฟล์นี้จะเก็บ repo-token ของคุณที่ได้รับจากส่วนการตั้งค่าสำหรับ repo ของคุณบนชุดคลุม

สคริปต์อื่นที่จำเป็นในไฟล์ package.json คือสคริปต์ครอบคลุม สคริปต์นี้จะมีประโยชน์เมื่อเราสร้างบิลด์ผ่านการดำเนินการ

 { "scripts": { "coverage": "nyc npm run test && nyc report --reporter=text-lcov --reporter=lcov | node ./node_modules/coveralls/bin/coveralls.js --verbose" } }

โดยพื้นฐานแล้ว จะทำการทดสอบ รับรายงาน และส่งไปยังชุดคลุมเพื่อการวิเคราะห์

มาถึงประเด็นหลักของบทช่วยสอนนี้

สร้างไฟล์เวิร์กโฟลว์ Node JS

ณ จุดนี้ เราได้ตั้งค่างานที่จำเป็นที่เราจะทำงานใน GitHub Action (สงสัยว่า “งาน” หมายถึงอะไร อ่านต่อไป)

GitHub ทำให้ง่ายต่อการสร้างไฟล์เวิร์กโฟลว์โดยจัดเตรียมเทมเพลตเริ่มต้น ตามที่เห็นในหน้าการดำเนินการ มีเทมเพลตเวิร์กโฟลว์หลายแบบที่ให้บริการตามวัตถุประสงค์ที่แตกต่างกัน สำหรับบทช่วยสอนนี้ เราจะใช้เวิร์กโฟลว์ Node.js (ซึ่งแนะนำ GitHub แล้ว)

หน้าการดำเนินการ
หน้าการดำเนินการ GitHub (ตัวอย่างขนาดใหญ่)

คุณสามารถแก้ไขไฟล์ได้โดยตรงบน GitHub แต่ฉันจะสร้างไฟล์ใน repo ในพื้นที่ของฉันเอง โฟลเดอร์ .github/workflows ที่มีไฟล์ node.js.yml จะอยู่ในไดเร็กทอรีราก

ไฟล์นี้มีคำสั่งพื้นฐานอยู่แล้ว และความคิดเห็นแรกจะอธิบายสิ่งที่คำสั่งเหล่านั้นทำ

 # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node

ฉันจะทำการเปลี่ยนแปลงบางอย่างเพื่อที่นอกเหนือจากความคิดเห็นข้างต้น มันยังครอบคลุมอยู่

ไฟล์ .node.js.yml ของฉัน:

 name: NodeJS CI on: ["push"] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run coverage - name: Coveralls uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_GIT_BRANCH: ${{ github.ref }} with: github-token: ${{ secrets.GITHUB_TOKEN }}

สิ่งนี้หมายความว่า?

มาทำลายมันกันเถอะ

  • name
    นี่จะเป็นชื่อของเวิร์กโฟลว์ของคุณ (NodeJS CI) หรืองาน (บิลด์) และ GitHub จะแสดงมันบนหน้าการดำเนินการของที่เก็บของคุณ
  • on
    นี่คือเหตุการณ์ที่ทริกเกอร์เวิร์กโฟลว์ บรรทัดนั้นในไฟล์ของฉันโดยพื้นฐานแล้วบอกให้ GitHub ทริกเกอร์เวิร์กโฟลว์ทุกครั้งที่มีการพุชไปยัง repo ของฉัน
  • jobs
    เวิร์กโฟลว์สามารถมีงานได้อย่างน้อยหนึ่งงาน และแต่ละงานรันในสภาพแวดล้อมที่ระบุโดย runs-on ในตัวอย่างไฟล์ด้านบน มีเพียงงานเดียวที่รันบิลด์และรันความครอบคลุม และทำงานในสภาพแวดล้อมของ windows ฉันสามารถแยกมันเป็นสองงานที่แตกต่างกันเช่นนี้:

ไฟล์ Node.yml ที่อัปเดต

 name: NodeJS CI on: [push] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run test coverage: name: Coveralls runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: github-token: ${{ secrets.GITHUB_TOKEN }}
  • env
    ซึ่งประกอบด้วยตัวแปรสภาพแวดล้อมที่พร้อมใช้งานทั้งหมดหรือเฉพาะงานและขั้นตอนในเวิร์กโฟลว์ ในงานความครอบคลุม คุณจะเห็นว่าตัวแปรสภาพแวดล้อมถูก "ซ่อน" สามารถพบได้ในหน้าความลับของ repo ของคุณภายใต้การตั้งค่า
  • steps
    โดยพื้นฐานแล้วนี่คือรายการขั้นตอนที่ต้องทำเมื่อเรียกใช้งานนั้น
  • งาน build ทำหลายอย่าง:
    • มันใช้การดำเนินการเช็คเอาต์ (v2 หมายถึงเวอร์ชัน) ที่จะตรวจสอบที่เก็บของคุณอย่างแท้จริงเพื่อให้เวิร์กโฟลว์ของคุณสามารถเข้าถึงได้
    • ใช้แอ็คชันเซ็ตอัพโหนดที่ตั้งค่าสภาพแวดล้อมของโหนดที่จะใช้
    • มันรันการติดตั้ง สร้างและทดสอบสคริปต์ที่พบในไฟล์ package.json ของเรา
  • coverage
    สิ่งนี้ใช้การกระทำของ coverallsapp ที่โพสต์ข้อมูลความครอบคลุม LCOV ของชุดทดสอบของคุณไปที่ coveralls.io สำหรับการวิเคราะห์
งานที่ประสบความสำเร็จ
งานทั้งหมดดำเนินการสำเร็จ (ตัวอย่างขนาดใหญ่)

ขั้นแรกฉันกดไปที่สาขา feat-add-controllers-and-route และลืมเพิ่ม .coveralls.yml จาก Coveralls ไปยังไฟล์ .coveralls.yml ของฉัน ดังนั้นฉันจึงได้รับข้อผิดพลาดที่คุณเห็นในบรรทัดที่ 132

บิลด์ล้มเหลว
งานล้มเหลวเนื่องจากข้อผิดพลาดในไฟล์ปรับแต่งของ coveralls (ตัวอย่างขนาดใหญ่)
 Bad response: 422 {"message":"Couldn't find a repository matching this job.","error":true}

เมื่อฉันเพิ่ม repo_token ด์ของฉันสามารถทำงานสำเร็จ หากไม่มีโทเค็นนี้ ชุดคลุมจะไม่สามารถรายงานการวิเคราะห์ความครอบคลุมการทดสอบของฉันได้อย่างถูกต้อง สิ่งที่ดี GitHub Actions CI ของเราชี้ให้เห็นข้อผิดพลาดก่อนที่จะถูกส่งไปยังสาขาหลัก

สร้างสำเร็จ
แก้ไขข้อผิดพลาด งานสำเร็จ เย้! (ตัวอย่างขนาดใหญ่)

หมายเหตุ: สิ่งเหล่านี้ถูกถ่ายก่อนที่ฉันจะแยกงานออกเป็นสองงาน นอกจากนี้ ฉันยังสามารถดูสรุปความครอบคลุมและข้อความแสดงข้อผิดพลาดบนเทอร์มินัลของฉันได้เพราะฉันเพิ่ม --verbose flag ที่ส่วนท้ายของสคริปต์ความครอบคลุมของฉัน

บทสรุป

เราสามารถดูวิธีตั้งค่าการผสานรวมอย่างต่อเนื่องสำหรับโครงการของเรา และยังรวมความครอบคลุมการทดสอบโดยใช้การดำเนินการที่ GitHub จัดเตรียมให้ มีวิธีอื่นๆ มากมายที่สามารถปรับให้เข้ากับความต้องการของโครงการของคุณได้ แม้ว่า repo ตัวอย่างที่ใช้ในบทช่วยสอนนี้จะเป็นโปรเจ็กต์เล็กๆ น้อยๆ จริงๆ ก็ตาม คุณสามารถดูได้ว่าการผสานรวมอย่างต่อเนื่องมีความสำคัญเพียงใดในโครงการที่ใหญ่กว่า ตอนนี้งานของฉันประสบความสำเร็จ ฉันมั่นใจที่จะรวมสาขากับสาขาหลักของฉัน ฉันยังคงแนะนำให้คุณอ่านผลลัพธ์ของขั้นตอนต่างๆ หลังจากการรันทุกครั้งเพื่อดูว่าประสบความสำเร็จอย่างสมบูรณ์