ปัจจุบันผลิตภัณฑ์ซอฟต์แวร์ส่วนใหญ่ได้รับการพัฒนาเป็นทีม เงื่อนไขสำหรับการพัฒนาทีมที่ประสบความสำเร็จสามารถแสดงได้ในรูปแบบไดอะแกรมง่ายๆ
เมื่อคุณเขียนโค้ดแล้ว คุณต้องแน่ใจว่า:
- Работает
- มันไม่ได้ทำลายอะไรเลย รวมถึงโค้ดที่เพื่อนร่วมงานของคุณเขียนด้วย
หากตรงตามเงื่อนไขทั้งสองข้อ แสดงว่าคุณอยู่บนเส้นทางสู่ความสำเร็จ เพื่อให้ตรวจสอบเงื่อนไขเหล่านี้ได้อย่างง่ายดายและไม่เบี่ยงเบนไปจากเส้นทางที่ทำกำไร เราจึงได้คิดค้นการผสานรวมอย่างต่อเนื่อง
CI คือขั้นตอนการทำงานที่คุณรวมโค้ดของคุณเข้ากับโค้ดผลิตภัณฑ์โดยรวมให้บ่อยที่สุดเท่าที่จะเป็นไปได้ และคุณไม่เพียงแค่บูรณาการ แต่ยังตรวจสอบอย่างต่อเนื่องว่าทุกอย่างทำงาน เนื่องจากคุณจำเป็นต้องตรวจสอบบ่อยครั้ง จึงคุ้มค่าที่จะคิดถึงระบบอัตโนมัติ คุณสามารถตรวจสอบทุกอย่างได้ด้วยตนเอง แต่คุณไม่ควรตรวจสอบ และนี่คือเหตุผล
- เรียนทุกท่าน. ชั่วโมงการทำงานของโปรแกรมเมอร์นั้นแพงกว่าการทำงานของเซิร์ฟเวอร์หนึ่งชั่วโมง
- ผู้คนทำผิดพลาด. ดังนั้น สถานการณ์อาจเกิดขึ้นเมื่อรันการทดสอบในสาขาที่ไม่ถูกต้อง หรือมีการรวบรวมการคอมมิตที่ไม่ถูกต้องสำหรับผู้ทดสอบ
- คนก็ขี้เกียจ. บางครั้งเมื่อฉันทำงานเสร็จก็เกิดความคิด: “มีอะไรให้ตรวจสอบ? ฉันเขียนสองบรรทัด - ทุกอย่างใช้งานได้! ฉันคิดว่าบางท่านก็มีความคิดเช่นนั้นเช่นกัน แต่คุณควรตรวจสอบอยู่เสมอ
วิธีการบูรณาการอย่างต่อเนื่องถูกนำไปใช้และพัฒนาในทีมพัฒนามือถือ Avito วิธีที่พวกเขาเพิ่มจาก 0 ถึง 450 บิลด์ต่อวัน และเครื่องจักรที่สร้างนั้นประกอบได้ 200 ชั่วโมงต่อวัน Nikolai Nesterov กล่าว (
เรื่องราวอิงจากตัวอย่างคำสั่ง Android แต่แนวทางส่วนใหญ่ก็ใช้ได้กับ iOS เช่นกัน
กาลครั้งหนึ่ง มีคนคนหนึ่งทำงานในทีม Avito Android ตามคำจำกัดความ เขาไม่ต้องการอะไรจากการบูรณาการอย่างต่อเนื่อง: ไม่มีใครที่จะบูรณาการด้วย
แต่แอปพลิเคชั่นก็เพิ่มขึ้น มีงานใหม่ ๆ ปรากฏขึ้นมากขึ้นเรื่อย ๆ และทีมก็เติบโตขึ้นตามไปด้วย เมื่อถึงจุดหนึ่ง ถึงเวลาที่จะต้องสร้างกระบวนการรวมโค้ดอย่างเป็นทางการมากขึ้น มีการตัดสินใจที่จะใช้ Git flow
แนวคิดของ Git flow เป็นที่รู้จักกันดี: โปรเจ็กต์มีสาขาการพัฒนาร่วมกันเพียงสาขาเดียว และสำหรับฟีเจอร์ใหม่แต่ละรายการ นักพัฒนาจะตัดสาขาแยกกัน คอมมิตกับมัน พุช และเมื่อพวกเขาต้องการรวมโค้ดของตนเข้ากับสาขาพัฒนา ให้เปิด ดึงคำขอ เพื่อแบ่งปันความรู้และหารือเกี่ยวกับแนวทางต่างๆ เราได้แนะนำการตรวจสอบโค้ด กล่าวคือ เพื่อนร่วมงานจะต้องตรวจสอบและยืนยันโค้ดของกันและกัน
การตรวจสอบ
การเห็นรหัสด้วยตาของคุณนั้นเจ๋ง แต่ก็ไม่เพียงพอ ดังนั้นจึงมีการนำการตรวจสอบอัตโนมัติมาใช้
- ก่อนอื่นเราตรวจสอบ การประกอบ ARK.
- เป็นจำนวนมาก การทดสอบ Junit.
- เราพิจารณาความครอบคลุมของโค้ดเนื่องจากเรากำลังดำเนินการทดสอบ
เพื่อทำความเข้าใจว่าควรดำเนินการตรวจสอบเหล่านี้อย่างไร มาดูกระบวนการพัฒนาใน Avito กัน
สามารถแสดงเป็นแผนผังได้ดังนี้:
- นักพัฒนาเขียนโค้ดบนแล็ปท็อปของเขา คุณสามารถเรียกใช้การตรวจสอบการรวมได้ที่นี่ ไม่ว่าจะด้วย Commit Hook หรือเพียงแค่เรียกใช้การตรวจสอบในเบื้องหลัง
- หลังจากที่นักพัฒนาได้พุชโค้ดแล้ว เขาจะเปิดคำขอดึง เพื่อให้โค้ดรวมอยู่ในสาขาที่พัฒนาแล้ว จำเป็นต้องผ่านการตรวจสอบโค้ดและรวบรวมการยืนยันตามจำนวนที่ต้องการ คุณสามารถเปิดใช้งานการตรวจสอบและบิลด์ได้ที่นี่: จนกว่าการสร้างทั้งหมดจะสำเร็จ คำขอดึงข้อมูลจะไม่สามารถรวมเข้าด้วยกันได้
- หลังจากรวมคำขอดึงข้อมูลและโค้ดรวมอยู่ในการพัฒนาแล้ว คุณสามารถเลือกเวลาที่สะดวกได้ เช่น ในเวลากลางคืน ซึ่งเป็นเวลาที่เซิร์ฟเวอร์ทั้งหมดว่าง และดำเนินการตรวจสอบได้มากเท่าที่คุณต้องการ
ไม่มีใครชอบการสแกนบนแล็ปท็อปของตน เมื่อนักพัฒนาเสร็จสิ้นคุณสมบัติ เขาต้องการที่จะพุชมันอย่างรวดเร็วและเปิดคำขอดึง หากในขณะนี้มีการเปิดตัวการตรวจสอบที่ยาวนาน สิ่งนี้ไม่เพียงแต่ไม่น่าพอใจเท่านั้น แต่ยังทำให้การพัฒนาช้าลงด้วย: ในขณะที่แล็ปท็อปกำลังตรวจสอบบางสิ่งบางอย่าง มันเป็นไปไม่ได้ที่จะทำงานได้ตามปกติ
เราชอบดำเนินการตรวจสอบในเวลากลางคืนมาก เนื่องจากมีเวลาและเซิร์ฟเวอร์จำนวนมาก คุณสามารถท่องไปรอบๆ ได้ แต่น่าเสียดายที่เมื่อโค้ดคุณลักษณะได้รับการพัฒนา นักพัฒนาก็มีแรงจูงใจน้อยลงมากในการแก้ไขข้อผิดพลาดที่ CI พบ ฉันคิดอยู่เป็นระยะเมื่อดูข้อผิดพลาดทั้งหมดที่พบในรายงานตอนเช้าว่าฉันจะแก้ไขมันในสักวันหนึ่งในภายหลัง เพราะตอนนี้มีงานใหม่ที่ยอดเยี่ยมในจิราที่ฉันแค่อยากจะเริ่มทำ
หากเช็คขัดขวางคำขอดึง แสดงว่ามีแรงจูงใจเพียงพอ เนื่องจากโค้ดจะไม่เข้าสู่การพัฒนาจนกว่าบิลด์จะเปลี่ยนเป็นสีเขียว ซึ่งหมายความว่างานจะไม่เสร็จสมบูรณ์
ด้วยเหตุนี้ เราจึงเลือกกลยุทธ์ต่อไปนี้: เราดำเนินการตรวจสอบชุดที่เป็นไปได้สูงสุดในเวลากลางคืน และเปิดตัวชุดการตรวจสอบที่สำคัญที่สุด และที่สำคัญที่สุดคือชุดการตรวจสอบที่เร็วที่สุดสำหรับคำขอดึง แต่เราไม่ได้หยุดอยู่แค่นั้น ในทางกลับกัน เราจะปรับความเร็วของการตรวจสอบให้เหมาะสม เพื่อถ่ายโอนการตรวจสอบจากโหมดกลางคืนเพื่อดึงการตรวจสอบคำขอ
ในขณะนั้น การสร้างทั้งหมดของเราเสร็จสมบูรณ์อย่างรวดเร็ว ดังนั้นเราจึงรวม ARK บิลด์ การทดสอบ Junit และการคำนวณการครอบคลุมโค้ดไว้เป็นตัวบล็อกสำหรับคำขอดึง เราเปิดใช้งาน คิดเกี่ยวกับมัน และละทิ้งการครอบคลุมโค้ดเพราะเราคิดว่าเราไม่ต้องการมัน
เราใช้เวลาสองวันในการตั้งค่า CI พื้นฐานให้เสร็จสมบูรณ์ (ต่อไปนี้จะเป็นเวลาโดยประมาณ ซึ่งจำเป็นสำหรับการปรับขนาด)
หลังจากนั้นเราก็เริ่มคิดเพิ่มเติม - เราตรวจสอบถูกต้องแล้วหรือยัง? เรากำลังรัน builds ตามคำขอ pull อย่างถูกต้องหรือไม่?
เราเริ่มต้นการสร้างการคอมมิตครั้งสุดท้ายของสาขาที่เปิดการร้องขอการดึง แต่การทดสอบการคอมมิตนี้สามารถแสดงได้ว่าโค้ดที่นักพัฒนาเขียนนั้นใช้งานได้เท่านั้น แต่พวกเขาไม่ได้พิสูจน์ว่าเขาไม่ได้ทำลายอะไรเลย ที่จริงแล้ว คุณจำเป็นต้องตรวจสอบสถานะของสาขาที่กำลังพัฒนาหลังจากรวมฟีเจอร์เข้ากับฟีเจอร์แล้ว
เพื่อทำเช่นนี้ เราได้เขียนสคริปต์ทุบตีแบบง่ายๆ premerge.sh:
#!/usr/bin/env bash
set -e
git fetch origin develop
git merge origin/develop
ที่นี่การเปลี่ยนแปลงล่าสุดทั้งหมดจากการพัฒนาจะถูกดึงขึ้นมาและรวมเข้ากับสาขาปัจจุบัน เราได้เพิ่มสคริปต์ premerge.sh เป็นขั้นตอนแรกในบิวด์ทั้งหมด และเริ่มตรวจสอบสิ่งที่เราต้องการอย่างแน่นอน บูรณาการ.
ใช้เวลาสามวันในการแปลปัญหา ค้นหาวิธีแก้ไข และเขียนสคริปต์นี้
แอปพลิเคชันได้รับการพัฒนา มีงานปรากฏขึ้นมากขึ้นเรื่อยๆ ทีมก็เติบโตขึ้น และบางครั้ง premerge.sh ก็เริ่มทำให้เราผิดหวัง การพัฒนามีการเปลี่ยนแปลงที่ขัดแย้งกันซึ่งทำให้โครงสร้างพัง
ตัวอย่างเหตุการณ์นี้เกิดขึ้น:
นักพัฒนาสองคนเริ่มทำงานกับคุณสมบัติ A และ B พร้อมกัน ผู้พัฒนาคุณสมบัติ A ค้นพบคุณสมบัติที่ไม่ได้ใช้ในโครงการ answer()
และก็เอามันออกไปเหมือนลูกเสือที่ดี ในเวลาเดียวกัน ผู้พัฒนาฟีเจอร์ B เพิ่มการเรียกใหม่ให้กับฟังก์ชันนี้ในสาขาของเขา
นักพัฒนาทำงานให้เสร็จและเปิดคำขอดึงในเวลาเดียวกัน เปิดตัว builds แล้ว premerge.sh จะตรวจสอบคำขอดึงทั้งสองคำขอที่เกี่ยวข้องกับสถานะการพัฒนาล่าสุด - การตรวจสอบทั้งหมดจะเป็นสีเขียว หลังจากนั้น คำขอการดึงของฟีเจอร์ A จะถูกรวมเข้าด้วยกัน คำขอการดึงของฟีเจอร์ B จะถูกรวมเข้าด้วยกัน... บูม! ตัวแบ่งการพัฒนาเนื่องจากโค้ดการพัฒนามีการเรียกไปยังฟังก์ชันที่ไม่มีอยู่จริง
เมื่อมันไม่พัฒนาก็เป็น ภัยพิบัติในท้องถิ่น. ทั้งทีมไม่สามารถรวบรวมสิ่งใดและส่งไปทดสอบได้
มันบังเอิญว่าฉันทำงานเกี่ยวกับโครงสร้างพื้นฐานบ่อยที่สุด: การวิเคราะห์ เครือข่าย ฐานข้อมูล นั่นคือฉันเองที่เขียนฟังก์ชันและคลาสเหล่านั้นที่นักพัฒนารายอื่นใช้ ด้วยเหตุนี้ฉันจึงพบว่าตัวเองตกอยู่ในสถานการณ์ที่คล้ายกันบ่อยมาก ฉันยังมีภาพนี้แขวนอยู่สักพักหนึ่ง
เนื่องจากสิ่งนี้ไม่เหมาะกับเรา เราจึงเริ่มสำรวจทางเลือกต่างๆ เกี่ยวกับวิธีการป้องกัน
วิธีที่จะไม่พังพัฒนา
ตัวเลือกแรก: สร้างคำขอดึงทั้งหมดใหม่เมื่ออัปเดตการพัฒนา ในตัวอย่างของเรา หากคำขอการดึงที่มีคุณลักษณะ A เป็นคำขอแรกที่จะรวมในการพัฒนา คำขอการดึงของคุณลักษณะ B จะถูกสร้างขึ้นใหม่ และด้วยเหตุนี้ การตรวจสอบจะล้มเหลวเนื่องจากข้อผิดพลาดในการคอมไพล์
เพื่อให้เข้าใจว่าจะใช้เวลานานแค่ไหน ลองพิจารณาตัวอย่างที่มี PR สองตัว เราเปิด PR สองรายการ: สองบิลด์, การตรวจสอบสองครั้ง หลังจากที่ PR แรกถูกรวมเข้ากับการพัฒนาแล้ว PR ที่สองจะต้องถูกสร้างใหม่ โดยรวมแล้ว PR สองตัวต้องมีการตรวจสอบสามครั้ง: 2 + 1 = 3
โดยหลักการแล้วก็โอเค แต่เราดูที่สถิติ และสถานการณ์ทั่วไปในทีมของเราคือ 10 PR ที่เปิดอยู่ จากนั้นจำนวนการตรวจสอบคือผลรวมของความก้าวหน้า: 10 + 9 +... + 1 = 55 นั่นคือยอมรับ 10 ประชาสัมพันธ์ คุณต้องสร้างใหม่ 55 ครั้ง และนี่คือสถานการณ์ในอุดมคติ เมื่อการตรวจสอบทั้งหมดผ่านในครั้งแรก เมื่อไม่มีใครเปิดคำขอดึงเพิ่มเติมในขณะที่โหลเหล่านี้กำลังประมวลผล
ลองนึกภาพตัวเองเป็นนักพัฒนาที่ต้องเป็นคนแรกที่คลิกที่ปุ่ม "รวม" เพราะหากเพื่อนบ้านทำเช่นนี้ คุณจะต้องรอจนกว่างานสร้างทั้งหมดจะดำเนินไปอีกครั้ง... ไม่ นั่นจะไม่ทำงาน จะทำให้การพัฒนาช้าลงอย่างมาก
วิธีที่สองที่เป็นไปได้: รวบรวมคำขอดึงหลังจากตรวจสอบโค้ด นั่นคือ คุณเปิดคำขอดึงข้อมูล รวบรวมการอนุมัติตามจำนวนที่ต้องการจากเพื่อนร่วมงาน แก้ไขสิ่งที่จำเป็น จากนั้นเปิดใช้งานรุ่น หากสำเร็จ คำขอดึงจะถูกรวมเข้ากับการพัฒนา ในกรณีนี้ จะไม่มีการรีสตาร์ทเพิ่มเติม แต่การตอบสนองจะช้าลงอย่างมาก ในฐานะนักพัฒนา เมื่อฉันเปิดคำขอดึงข้อมูล ฉันต้องการดูว่ามันจะใช้งานได้ทันทีหรือไม่ ตัวอย่างเช่น หากการทดสอบล้มเหลว คุณจะต้องแก้ไขอย่างรวดเร็ว ในกรณีของการสร้างล่าช้า ผลตอบรับจะช้าลง และทำให้การพัฒนาทั้งหมดลดลง สิ่งนี้ก็ไม่เหมาะกับเราเช่นกัน
เป็นผลให้เหลือเพียงตัวเลือกที่สามเท่านั้น - จักรยาน. รหัสทั้งหมดของเรา แหล่งที่มาทั้งหมดของเราถูกจัดเก็บไว้ในพื้นที่เก็บข้อมูลบนเซิร์ฟเวอร์ Bitbucket ดังนั้นเราจึงต้องพัฒนาปลั๊กอินสำหรับ Bitbucket
ปลั๊กอินนี้จะแทนที่กลไกการรวมคำขอดึง จุดเริ่มต้นคือมาตรฐาน: PR เปิดขึ้น แอสเซมบลีทั้งหมดถูกเปิดใช้งาน การตรวจสอบโค้ดเสร็จสมบูรณ์ แต่หลังจากการตรวจสอบโค้ดเสร็จสิ้นและนักพัฒนาตัดสินใจที่จะคลิกที่ "ผสาน" ปลั๊กอินจะตรวจสอบสถานะการพัฒนาที่ดำเนินการตรวจสอบ หากการพัฒนาได้รับการอัปเดตหลังจากสร้างแล้ว ปลั๊กอินจะไม่อนุญาตให้รวมคำขอดึงดังกล่าวเข้ากับสาขาหลัก มันจะรีสตาร์ทบิวด์ของการพัฒนาที่ค่อนข้างล่าสุดอีกครั้ง
ในตัวอย่างของเราที่มีการเปลี่ยนแปลงที่ขัดแย้งกัน บิลด์ดังกล่าวจะล้มเหลวเนื่องจากข้อผิดพลาดในการคอมไพล์ ดังนั้น ผู้พัฒนาฟีเจอร์ B จะต้องแก้ไขโค้ด เริ่มการตรวจสอบใหม่ จากนั้นปลั๊กอินจะใช้คำขอดึงโดยอัตโนมัติ
ก่อนที่จะใช้ปลั๊กอินนี้ เราใช้เวลาตรวจสอบเฉลี่ย 2,7 ครั้งต่อคำขอดึงข้อมูล ด้วยปลั๊กอินมีการเปิดตัว 3,6 สิ่งนี้เหมาะกับเรา
เป็นที่น่าสังเกตว่าปลั๊กอินนี้มีข้อเสียเปรียบ: รีสตาร์ทบิลด์เพียงครั้งเดียวเท่านั้น นั่นคือยังคงมีหน้าต่างเล็ก ๆ ที่สามารถพัฒนาการเปลี่ยนแปลงที่ขัดแย้งกันได้ แต่โอกาสที่จะเกิดสิ่งนี้มีน้อย และเราได้ทำการแลกเปลี่ยนระหว่างจำนวนการออกสตาร์ทและความน่าจะเป็นที่จะล้มเหลว ในสองปีมันถูกยิงเพียงครั้งเดียว ดังนั้นมันคงไม่สูญเปล่า
เราใช้เวลาสองสัปดาห์ในการเขียนปลั๊กอิน Bitbucket เวอร์ชันแรก
เช็คใหม่
ในขณะเดียวกันทีมของเราก็ยังคงเติบโตอย่างต่อเนื่อง มีการเพิ่มเช็คใหม่แล้ว
เราคิดว่า: ทำไมต้องทำผิดพลาดหากสามารถป้องกันได้? และนั่นคือเหตุผลที่พวกเขาดำเนินการ การวิเคราะห์โค้ดแบบคงที่. เราเริ่มต้นด้วย Lint ซึ่งรวมอยู่ใน Android SDK แต่ในเวลานั้นเขาไม่รู้วิธีทำงานกับโค้ด Kotlin เลย และเรามีแอปพลิเคชันที่เขียนด้วย Kotlin ไปแล้ว 75% ดังนั้นจึงมีการเพิ่มสิ่งในตัวเข้ากับผ้าสำลี การตรวจสอบ Android Studio
เพื่อทำเช่นนี้ เราต้องทำการกระทำในทางที่ผิดมากมาย: นำ Android Studio มาบรรจุใน Docker และรันบน CI ด้วยจอภาพเสมือน เพื่อให้คิดว่ามันทำงานบนแล็ปท็อปจริง แต่มันก็ได้ผล
ในช่วงเวลานี้เองที่เราเริ่มเขียนกันมากมาย การทดสอบเครื่องมือวัด และนำไปปฏิบัติ การทดสอบภาพหน้าจอ. นี่คือเมื่อมีการสร้างภาพหน้าจออ้างอิงสำหรับมุมมองขนาดเล็กแยกต่างหาก และการทดสอบประกอบด้วยการถ่ายภาพหน้าจอจากมุมมองและเปรียบเทียบกับมาตรฐานโดยตรงแบบพิกเซลต่อพิกเซล หากมีความคลาดเคลื่อน แสดงว่าเลย์เอาต์มีข้อผิดพลาดไปที่ไหนสักแห่งหรือมีบางอย่างผิดปกติในรูปแบบ
แต่การทดสอบเครื่องมือและการทดสอบภาพหน้าจอจำเป็นต้องดำเนินการบนอุปกรณ์: บนเครื่องจำลองหรือบนอุปกรณ์จริง เมื่อพิจารณาว่ามีการทดสอบจำนวนมากและมีการดำเนินการบ่อยครั้ง จึงจำเป็นต้องมีทั้งฟาร์ม การเริ่มต้นฟาร์มของคุณเองนั้นใช้แรงงานคนมากเกินไป ดังนั้นเราจึงพบตัวเลือกสำเร็จรูป - Firebase Test Lab
ห้องปฏิบัติการทดสอบฐานไฟ
ถูกเลือกเนื่องจาก Firebase เป็นผลิตภัณฑ์ของ Google ซึ่งหมายความว่าควรเชื่อถือได้และไม่น่าจะตายไป ราคาสมเหตุสมผล: $5 ต่อชั่วโมงในการใช้งานอุปกรณ์จริง และ 1 $ ต่อชั่วโมงสำหรับการทำงานของโปรแกรมจำลอง
ใช้เวลาประมาณสามสัปดาห์ในการปรับใช้ Firebase Test Lab ใน CI ของเรา
แต่ทีมยังคงเติบโตอย่างต่อเนื่อง และ Firebase ก็เริ่มทำให้เราผิดหวัง ในเวลานั้นเขาไม่มี SLA ใด ๆ บางครั้ง Firebase ทำให้เรารอจนกว่าอุปกรณ์ตามจำนวนที่กำหนดจะว่างสำหรับการทดสอบ และไม่ได้เริ่มดำเนินการทันทีตามที่เราต้องการ การรอคิวใช้เวลานานถึงครึ่งชั่วโมงซึ่งถือว่านานมาก การทดสอบการใช้เครื่องมือดำเนินการกับ PR ทุกรายการ ความล่าช้าทำให้การพัฒนาช้าลงอย่างมาก จากนั้นการเรียกเก็บเงินรายเดือนก็มาพร้อมกับผลรวมแบบกลม โดยทั่วไปแล้ว มีการตัดสินใจที่จะละทิ้ง Firebase และทำงานภายในองค์กร เนื่องจากทีมเติบโตขึ้นมามากพอแล้ว
นักเทียบท่า + Python + ทุบตี
เรานำ Docker ยัดอีมูเลเตอร์ลงไป เขียนโปรแกรมง่ายๆ ใน Python ซึ่งในเวลาที่เหมาะสมจะแสดงจำนวนอีมูเลเตอร์ที่ต้องการในเวอร์ชันที่ต้องการและหยุดเมื่อจำเป็น และแน่นอนว่ามีสคริปต์ทุบตีสองสามตัว - เราจะอยู่ที่ไหนถ้าไม่มีพวกมัน?
ใช้เวลาห้าสัปดาห์ในการสร้างสภาพแวดล้อมการทดสอบของเราเอง
ด้วยเหตุนี้ สำหรับทุกคำขอดึงจึงมีรายการตรวจสอบที่บล็อกการรวมจำนวนมาก:
- การประกอบ ARK;
- การทดสอบ Junit;
- ผ้าสำลี;
- การตรวจสอบ Android Studio;
- การทดสอบเครื่องมือวัด
- การทดสอบภาพหน้าจอ
สิ่งนี้ช่วยป้องกันการพังที่อาจเกิดขึ้นได้มากมาย ในทางเทคนิคแล้วทุกอย่างทำงานได้ แต่นักพัฒนาบ่นว่าการรอผลลัพธ์นานเกินไป
นานแค่ไหนก็นานเกินไป? เราอัปโหลดข้อมูลจาก Bitbucket และ TeamCity ไปยังระบบการวิเคราะห์และตระหนักว่า เวลารอโดยเฉลี่ย 45 นาที. กล่าวคือ นักพัฒนาเมื่อเปิดคำขอดึง จะรอประมาณ 45 นาทีโดยเฉลี่ยเพื่อให้ได้ผลลัพธ์ของบิลด์ ในความคิดของฉัน มันเยอะมาก และคุณไม่สามารถทำงานแบบนั้นได้
แน่นอนว่าเราตัดสินใจเร่งการสร้างทั้งหมดของเรา
มาเร่งความเร็วกันเถอะ
เมื่อเห็นว่าบิลด์มักจะยืนต่อคิว สิ่งแรกที่เราทำคือ ซื้อฮาร์ดแวร์เพิ่มเติม — การพัฒนาอย่างกว้างขวางนั้นง่ายที่สุด บิลด์หยุดการรอคิว แต่เวลาในการรอลดลงเพียงเล็กน้อย เนื่องจากการตรวจสอบบางอย่างใช้เวลานานมาก
การถอดเช็คที่ใช้เวลานานเกินไป
การบูรณาการอย่างต่อเนื่องของเราสามารถตรวจจับข้อผิดพลาดและปัญหาประเภทนี้ได้
- จะไม่ไป. CI สามารถตรวจจับข้อผิดพลาดในการคอมไพล์เมื่อมีบางสิ่งไม่ถูกสร้างขึ้นเนื่องจากการเปลี่ยนแปลงที่ขัดแย้งกัน อย่างที่ผมบอกไปแล้วว่าไม่มีใครสามารถรวบรวมอะไรได้เลย การพัฒนาหยุดลง และทุกคนก็วิตกกังวล
- ข้อผิดพลาดในพฤติกรรม. ตัวอย่างเช่น เมื่อแอปพลิเคชันถูกสร้างขึ้น แต่หยุดทำงานเมื่อคุณกดปุ่ม หรือไม่ได้กดปุ่มเลย สิ่งนี้ไม่ดีเพราะข้อผิดพลาดดังกล่าวสามารถเข้าถึงผู้ใช้ได้
- ข้อผิดพลาดในเค้าโครง. ตัวอย่างเช่น มีการคลิกปุ่ม แต่ได้ย้ายไปทางซ้าย 10 พิกเซล
- หนี้ทางเทคนิคเพิ่มขึ้น.
หลังจากดูรายการนี้แล้ว เราก็พบว่ามีเพียงสองประเด็นแรกเท่านั้นที่มีความสำคัญ เราต้องการจับปัญหาดังกล่าวก่อน จุดบกพร่องในเลย์เอาต์จะถูกค้นพบในขั้นตอนการตรวจสอบการออกแบบ และสามารถแก้ไขได้อย่างง่ายดาย การจัดการกับหนี้ทางเทคนิคต้องมีกระบวนการและการวางแผนที่แยกจากกัน ดังนั้นเราจึงตัดสินใจที่จะไม่ทดสอบตามคำขอดึง
จากการจำแนกประเภทนี้ เราได้เขย่ารายการเช็คทั้งหมด ขีดฆ่าผ้าสำลี และเลื่อนการเปิดตัวข้ามคืนเพียงเพื่อจะได้รายงานจำนวนปัญหาในโครงการ เราตกลงที่จะทำงานแยกกันกับหนี้ทางเทคนิคและ การตรวจสอบ Android Studio ถูกยกเลิกโดยสิ้นเชิง. Android Studio ใน Docker สำหรับการตรวจสอบการทำงานฟังดูน่าสนใจ แต่ทำให้เกิดปัญหามากมายในการสนับสนุน การอัปเดตเวอร์ชัน Android Studio หมายถึงการต่อสู้กับข้อบกพร่องที่เข้าใจยาก นอกจากนี้ยังเป็นเรื่องยากที่จะสนับสนุนการทดสอบภาพหน้าจอ เนื่องจากไลบรารีไม่เสถียรมากนักและมีผลบวกลวง การทดสอบภาพหน้าจอถูกลบออกจากรายการตรวจสอบแล้ว.
เป็นผลให้เราเหลือ:
- การประกอบ ARK;
- การทดสอบ Junit;
- การทดสอบเครื่องมือวัด
แคชระยะไกล Gradle
หากไม่มีการตรวจสอบอย่างหนักทุกอย่างก็ดีขึ้น แต่ความสมบูรณ์แบบไม่มีขีดจำกัด!
แอปพลิเคชันของเราแบ่งออกเป็นโมดูลการไล่ระดับประมาณ 150 โมดูลแล้ว โดยปกติแล้วแคชระยะไกลของ Gradle จะทำงานได้ดีในกรณีนี้ ดังนั้นเราจึงตัดสินใจลองใช้
Gradle Remote Cache เป็นบริการที่สามารถแคชการสร้างสิ่งประดิษฐ์สำหรับงานแต่ละงานในแต่ละโมดูล Gradle แทนที่จะรวบรวมโค้ดจริงๆ ให้ใช้ HTTP เพื่อเคาะแคชระยะไกลและถามว่ามีใครทำงานนี้แล้วหรือยัง ถ้าใช่ มันก็เพียงดาวน์โหลดผลลัพธ์
การเรียกใช้แคชระยะไกลของ Gradle เป็นเรื่องง่ายเนื่องจาก Gradle มีอิมเมจ Docker เราสามารถทำได้ภายในสามชั่วโมง
สิ่งที่คุณต้องทำคือเปิด Docker และเขียนหนึ่งบรรทัดในโปรเจ็กต์ แม้ว่าจะสามารถเปิดตัวได้อย่างรวดเร็ว แต่ก็ต้องใช้เวลาค่อนข้างนานเพื่อให้ทุกอย่างทำงานได้ดี
ด้านล่างนี้คือกราฟแคชที่หายไป
ในช่วงเริ่มต้น เปอร์เซ็นต์ของแคชที่หายไปคือประมาณ 65 หลังจากผ่านไปสามสัปดาห์ เราก็สามารถเพิ่มค่านี้เป็น 20% ได้ ปรากฎว่างานที่แอปพลิเคชัน Android รวบรวมมีการพึ่งพาสกรรมกริยาแปลก ๆ เนื่องจาก Gradle พลาดแคช
ด้วยการเชื่อมต่อแคช เราจึงเร่งความเร็วในการสร้างได้อย่างมาก แต่นอกจากการประกอบแล้ว ยังมีการทดสอบเครื่องมือวัดด้วยซึ่งใช้เวลานาน บางทีอาจไม่จำเป็นต้องรันการทดสอบทั้งหมดสำหรับคำขอดึงทุกครั้ง เพื่อค้นหาคำตอบ เราใช้การวิเคราะห์ผลกระทบ
การวิเคราะห์ผลกระทบ
ตามคำขอดึง เราจะรวบรวม git diff และค้นหาโมดูล Gradle ที่ถูกแก้ไข
ควรทำการทดสอบเครื่องมือวัดเพื่อตรวจสอบโมดูลที่เปลี่ยนแปลงและโมดูลทั้งหมดที่ขึ้นอยู่กับโมดูลเหล่านั้นเท่านั้น ไม่มีประโยชน์ในการรันการทดสอบสำหรับโมดูลข้างเคียง: โค้ดไม่มีการเปลี่ยนแปลงและไม่มีอะไรเสียหาย
การทดสอบเครื่องมือวัดนั้นไม่ง่ายนัก เนื่องจากจะต้องอยู่ในโมดูลแอปพลิเคชันระดับบนสุด เราใช้การวิเคราะห์พฤติกรรมร่วมกับการวิเคราะห์ bytecode เพื่อทำความเข้าใจว่าการทดสอบแต่ละครั้งเป็นของโมดูลใด
การอัปเกรดการดำเนินการของการทดสอบเครื่องมือวัดเพื่อให้ทดสอบเฉพาะโมดูลที่เกี่ยวข้องนั้นใช้เวลาประมาณแปดสัปดาห์
มาตรการเร่งรัดการตรวจสอบได้ผลสำเร็จ จาก 45 นาที เราเพิ่มเป็นประมาณ 15 นาที เป็นเรื่องปกติอยู่แล้วที่จะต้องรอประมาณหนึ่งในสี่ของชั่วโมงเพื่อสร้างงาน
แต่ตอนนี้นักพัฒนาเริ่มบ่นว่าพวกเขาไม่เข้าใจว่ามีการเปิดตัวบิลด์ใด จะดูบันทึกได้ที่ไหน เหตุใดบิลด์จึงเป็นสีแดง การทดสอบใดล้มเหลว ฯลฯ
ปัญหาเกี่ยวกับข้อเสนอแนะทำให้การพัฒนาช้าลง ดังนั้นเราจึงพยายามให้ข้อมูลที่ชัดเจนและละเอียดเกี่ยวกับ PR แต่ละรายการและสร้างให้มากที่สุดเท่าที่จะเป็นไปได้ เราเริ่มต้นด้วยความคิดเห็นใน Bitbucket ต่อ PR โดยระบุว่าบิลด์ใดล้มเหลวและเพราะเหตุใด และเขียนข้อความที่กำหนดเป้าหมายใน Slack ในท้ายที่สุด เราได้สร้างแดชบอร์ด PR สำหรับเพจที่มีรายการบิวด์ทั้งหมดที่กำลังทำงานอยู่และสถานะ: เข้าคิว กำลังทำงาน ขัดข้อง หรือเสร็จสมบูรณ์ คุณสามารถคลิกที่บิลด์และไปที่บันทึกของมัน
ใช้เวลาหกสัปดาห์ในการรับฟังความคิดเห็นโดยละเอียด
แผน
เรามาดูประวัติศาสตร์ล่าสุดกันดีกว่า หลังจากแก้ไขปัญหาข้อเสนอแนะแล้ว เราก็ก้าวไปสู่ระดับใหม่ - เราตัดสินใจสร้างฟาร์มจำลองของเราเอง เมื่อมีการทดสอบและอีมูเลเตอร์จำนวนมาก การจัดการก็จะทำได้ยาก ด้วยเหตุนี้ โปรแกรมจำลองทั้งหมดของเราจึงย้ายไปยังคลัสเตอร์ k8s พร้อมการจัดการทรัพยากรที่ยืดหยุ่น
นอกจากนี้ยังมีแผนอื่นๆ
- กลับผ้าสำลี (และการวิเคราะห์ทางสถิตอื่นๆ) เรากำลังดำเนินการไปในทิศทางนี้แล้ว
- ดำเนินการทุกอย่างบนตัวบล็อก PR การทดสอบแบบ end-to-end ใน SDK ทุกเวอร์ชัน
ดังนั้นเราจึงได้ติดตามประวัติความเป็นมาของการพัฒนาการบูรณาการอย่างต่อเนื่องใน Avito ตอนนี้ฉันต้องการให้คำแนะนำจากมุมมองที่มีประสบการณ์
Советы
หากฉันสามารถให้คำแนะนำได้เพียงข้อเดียวก็จะเป็นดังนี้:
โปรดระวังเชลล์สคริปต์!
Bash เป็นเครื่องมือที่ยืดหยุ่นและทรงพลังมาก สะดวกและรวดเร็วในการเขียนสคริปต์ แต่คุณสามารถติดกับดักของมันได้ และน่าเสียดายที่เราตกหลุมพรางนั้น
ทุกอย่างเริ่มต้นด้วยสคริปต์ง่ายๆ ที่ทำงานบนเครื่องสร้างของเรา:
#!/usr/bin/env bash
./gradlew assembleDebug
แต่อย่างที่คุณทราบทุกอย่างพัฒนาและซับซ้อนมากขึ้นเมื่อเวลาผ่านไป - มาเรียกใช้สคริปต์หนึ่งจากอีกสคริปต์หนึ่งส่งพารามิเตอร์บางตัวไปที่นั่น - ในที่สุดเราต้องเขียนฟังก์ชันที่กำหนดระดับของการซ้อน bash ที่เราอยู่ตอนนี้ตามลำดับ เพื่อแทรกเครื่องหมายคำพูดที่จำเป็นเพื่อเริ่มต้นทั้งหมด
คุณสามารถจินตนาการถึงต้นทุนแรงงานในการพัฒนาสคริปต์ดังกล่าวได้ ฉันแนะนำให้คุณอย่าตกหลุมพรางนี้
มีอะไรมาทดแทนได้บ้าง?
- ภาษาสคริปต์ใด ๆ เขียนถึง สคริปต์ Python หรือ Kotlin สะดวกกว่าเพราะเป็นการเขียนโปรแกรมไม่ใช่สคริปต์
- หรืออธิบายตรรกะการสร้างทั้งหมดในแบบฟอร์ม งานไล่ระดับที่กำหนดเอง สำหรับโครงการของคุณ
เราตัดสินใจเลือกตัวเลือกที่สอง และตอนนี้เรากำลังลบสคริปต์ทุบตีทั้งหมดอย่างเป็นระบบและเขียนงาน gradle แบบกำหนดเองจำนวนมาก
เคล็ดลับ #2: จัดเก็บโครงสร้างพื้นฐานไว้ในโค้ด
สะดวกเมื่อการตั้งค่าการรวมแบบต่อเนื่องไม่ได้เก็บไว้ในอินเทอร์เฟซ UI ของ Jenkins หรือ TeamCity ฯลฯ แต่อยู่ในรูปแบบของไฟล์ข้อความโดยตรงในพื้นที่เก็บข้อมูลโปรเจ็กต์ สิ่งนี้ทำให้มีความเป็นเวอร์ชันได้ การย้อนกลับหรือสร้างโค้ดในสาขาอื่นไม่ใช่เรื่องยาก
สคริปต์สามารถจัดเก็บไว้ในโครงการได้ จะทำอย่างไรกับสิ่งแวดล้อม?
เคล็ดลับ #3: นักเทียบท่าสามารถช่วยรักษาสิ่งแวดล้อมได้
มันจะช่วยนักพัฒนา Android ได้อย่างแน่นอน น่าเสียดายที่ iOS ยังไม่มี
นี่คือตัวอย่างของไฟล์นักเทียบท่าธรรมดาที่มี jdk และ android-sdk:
FROM openjdk:8
ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip"
ANDROID_HOME="/usr/local/android-sdk"
ANDROID_VERSION=26
ANDROID_BUILD_TOOLS_VERSION=26.0.2
# Download Android SDK
RUN mkdir "$ANDROID_HOME" .android
&& cd "$ANDROID_HOME"
&& curl -o sdk.zip $SDK_URL
&& unzip sdk.zip
&& rm sdk.zip
&& yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses
# Install Android Build Tool and Libraries
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}"
"platforms;android-${ANDROID_VERSION}"
"platform-tools"
RUN mkdir /application
WORKDIR /application
เมื่อเขียนไฟล์ Docker นี้ (ฉันจะบอกความลับแก่คุณคุณไม่จำเป็นต้องเขียน แต่เพียงดึงมันสำเร็จรูปจาก GitHub) และประกอบอิมเมจคุณจะได้เครื่องเสมือนที่คุณสามารถสร้างแอปพลิเคชันได้ และรันการทดสอบ Junit
เหตุผลหลักสองประการที่ทำให้สิ่งนี้สมเหตุสมผลคือความสามารถในการขยายขนาดและการทำซ้ำได้ ด้วยการใช้นักเทียบท่า คุณสามารถเพิ่ม build agent จำนวนมากที่จะมีสภาพแวดล้อมเหมือนกับรุ่นก่อนหน้าได้อย่างรวดเร็ว ทำให้ชีวิตของวิศวกร CI ง่ายขึ้นมาก มันค่อนข้างง่ายที่จะผลักดัน android-sdk ลงในนักเทียบท่า แต่ด้วยโปรแกรมจำลองมันยากขึ้นอีกเล็กน้อย: คุณจะต้องทำงานหนักขึ้นอีกเล็กน้อย (หรือดาวน์โหลดอันที่เสร็จแล้วจาก GitHub อีกครั้ง)
เคล็ดลับที่ 4: อย่าลืมว่าการตรวจสอบไม่ได้ทำเพื่อการตรวจสอบ แต่เพื่อประชาชน
ความคิดเห็นที่รวดเร็วและที่สำคัญที่สุดคือข้อเสนอแนะที่เข้าใจได้นั้นสำคัญมากสำหรับนักพัฒนา: สิ่งใดพัง สิ่งใดที่การทดสอบล้มเหลว ฉันจะดูบันทึกการสร้างได้ที่ไหน
เคล็ดลับ #5: จริงจังเมื่อพัฒนาการรวมระบบอย่างต่อเนื่อง
เข้าใจอย่างชัดเจนว่าคุณต้องการป้องกันข้อผิดพลาดประเภทใด จำนวนทรัพยากร เวลา และเวลาคอมพิวเตอร์ที่คุณยินดีจ่าย เช็คที่ใช้เวลานานเกินไปสามารถถูกเลื่อนออกไปข้ามคืนได้ และข้อผิดพลาดที่ไม่สำคัญมากก็ควรละทิ้งไปโดยสิ้นเชิง
เคล็ดลับ #6: ใช้เครื่องมือสำเร็จรูป
ขณะนี้มีหลายบริษัทที่ให้บริการ cloud CI
นี่เป็นทางออกที่ดีสำหรับทีมขนาดเล็ก คุณไม่จำเป็นต้องสนับสนุนอะไรเลย เพียงแค่จ่ายเงินเพียงเล็กน้อย สร้างแอปพลิเคชันของคุณ และแม้แต่เรียกใช้การทดสอบเครื่องมือวัด
เคล็ดลับ #7: ในทีมขนาดใหญ่ โซลูชันภายในองค์กรจะให้ผลกำไรมากกว่า
แต่ไม่ช้าก็เร็ว เมื่อทีมเติบโตขึ้น โซลูชันภายในองค์กรจะทำกำไรได้มากขึ้น มีปัญหาประการหนึ่งกับการตัดสินใจเหล่านี้ มีกฎว่าด้วยผลตอบแทนทางเศรษฐกิจที่ลดลง ในโครงการใดๆ การปรับปรุงแต่ละครั้งจะยากขึ้นเรื่อยๆ และต้องใช้เงินลงทุนมากขึ้นเรื่อยๆ
เศรษฐศาสตร์อธิบายถึงชีวิตทั้งชีวิตของเรา รวมถึงการบูรณาการอย่างต่อเนื่อง ฉันสร้างตารางค่าแรงสำหรับการพัฒนาแต่ละขั้นตอนของการบูรณาการอย่างต่อเนื่องของเรา
เป็นที่ชัดเจนว่าการปรับปรุงใดๆ ก็ตามนั้นยากขึ้นเรื่อยๆ เมื่อดูกราฟนี้ คุณจะเข้าใจได้ว่าการบูรณาการอย่างต่อเนื่องจำเป็นต้องได้รับการพัฒนาให้สอดคล้องกับการเติบโตของขนาดทีม สำหรับทีมที่มีสมาชิกสองคน การใช้เวลา 50 วันในการพัฒนาฟาร์มจำลองภายในถือเป็นแนวคิดที่ธรรมดามาก แต่ในขณะเดียวกัน สำหรับทีมใหญ่ การไม่ทำ Continuous Integration เลยก็เป็นความคิดที่ไม่ดีเช่นกัน เพราะมีปัญหาในการบูรณาการ แก้ไขปัญหาการสื่อสาร เป็นต้น มันจะต้องใช้เวลามากขึ้น
เราเริ่มต้นด้วยแนวคิดที่ว่าระบบอัตโนมัติเป็นสิ่งจำเป็นเนื่องจากผู้คนมีราคาแพง พวกเขาทำผิดพลาด และเกียจคร้าน แต่ผู้คนก็ทำโดยอัตโนมัติเช่นกัน ดังนั้นปัญหาเดียวกันทั้งหมดจึงมีผลกับระบบอัตโนมัติ
- ระบบอัตโนมัติมีราคาแพง จำตารางแรงงาน
- เมื่อพูดถึงระบบอัตโนมัติ ผู้คนมักทำผิดพลาด
- บางครั้งมันก็ขี้เกียจมากที่จะทำให้เป็นอัตโนมัติ เพราะทุกอย่างเป็นแบบนั้น เหตุใดจึงต้องปรับปรุงสิ่งอื่นใด เหตุใดจึงต้องบูรณาการอย่างต่อเนื่องทั้งหมดนี้
แต่ฉันมีสถิติ: ข้อผิดพลาดเกิดขึ้นได้ใน 20% ของแอสเซมบลี และนี่ไม่ใช่เพราะนักพัฒนาของเราเขียนโค้ดได้ไม่ดี เนื่องจากนักพัฒนามั่นใจว่าหากพวกเขาทำผิดพลาด จะไม่จบลงด้วยการพัฒนา แต่จะถูกตรวจสอบโดยอัตโนมัติ ด้วยเหตุนี้ นักพัฒนาจึงสามารถใช้เวลาในการเขียนโค้ดและสิ่งที่น่าสนใจได้มากขึ้น แทนที่จะใช้งานและทดสอบบางอย่างในเครื่อง
ฝึกบูรณาการอย่างต่อเนื่อง แต่ในปริมาณที่พอเหมาะ
อย่างไรก็ตาม Nikolai Nesterov ไม่เพียงแต่ให้รายงานที่ยอดเยี่ยมเท่านั้น แต่ยังเป็นสมาชิกของคณะกรรมการโครงการอีกด้วย
AppsConf และช่วยผู้อื่นเตรียมสุนทรพจน์ที่มีความหมายสำหรับคุณ ความสมบูรณ์และประโยชน์ของโปรแกรมการประชุมครั้งต่อไปสามารถประเมินได้ตามหัวข้อในกำหนดการ . และดูรายละเอียดเพิ่มเติมได้ที่ Infospace วันที่ 22-23 เมษายน
ที่มา: will.com