ฉันจะทดสอบรหัสเธรดได้อย่างไร


704

ฉันได้หลีกเลี่ยงฝันร้ายที่กำลังทดสอบโค้ดหลายเธรดเพราะมันดูเหมือนว่าเป็นเขตที่วางทุ่นระเบิดมากเกินไป ฉันต้องการถามว่าผู้คนทดสอบโค้ดที่อาศัยเธรดเพื่อการดำเนินการที่ประสบความสำเร็จได้อย่างไรหรือว่าผู้คนทดสอบเกี่ยวกับปัญหาประเภทนั้น ๆ ที่ปรากฏขึ้นเมื่อสองเธรดตอบสนองในลักษณะที่กำหนดเท่านั้น

ดูเหมือนว่าปัญหาสำคัญสำหรับโปรแกรมเมอร์วันนี้มันจะเป็นประโยชน์ในการรวมความรู้ของเรากับสิ่งนี้


2
ฉันคิดว่าจะโพสต์คำถามเกี่ยวกับปัญหาเดียวกันนี้ แม้ว่า Will จะทำคะแนนดี ๆ ด้านล่างหลายอย่าง แต่ฉันคิดว่าเราทำได้ดีกว่า ฉันยอมรับว่าไม่มี "วิธีการ" เดียวในการจัดการกับสิ่งนี้อย่างหมดจด อย่างไรก็ตาม "การทดสอบที่ดีที่สุดเท่าที่จะทำได้" ก็คือการตั้งค่าแถบที่ต่ำมาก ฉันจะกลับมาพร้อมกับการค้นพบของฉัน
Zach Burlingame

ใน Java: แพ็คเกจ java.util.concurrent มีคลาสที่ไม่รู้จักบางอย่างซึ่งอาจช่วยในการเขียนการทดสอบ JUnit ที่กำหนดขึ้น ดูได้ที่ - CountDownLatch - สัญญาณ - แลกเปลี่ยน
ซินอกซ์

คุณสามารถให้ลิงค์ไปยังคำถามที่เกี่ยวข้องกับการทดสอบหน่วยก่อนหน้าของคุณได้ไหม
Andrew Grimm


7
ฉันคิดว่าเป็นเรื่องสำคัญที่จะต้องทราบว่าคำถามนี้มีอายุ 8 ปีและแอปพลิเคชั่นห้องสมุดมีมานานมากในระหว่างนี้ ใน "ยุคสมัยใหม่" (2016) การพัฒนาแบบมัลติเธรดเกิดขึ้นส่วนใหญ่ในระบบฝังตัว แต่ถ้าคุณกำลังทำงานบนแอพเดสก์ท็อปหรือโทรศัพท์ให้สำรวจทางเลือกอื่นก่อน สภาพแวดล้อมของแอปพลิเคชั่นอย่างเช่น. NET ในปัจจุบันมีเครื่องมือในการจัดการหรือลดความซับซ้อนของ 90% ของสถานการณ์หลายเธรดทั่วไป (asnync / await, PLinq, IObservable, TPL ... ) รหัสแบบมัลติเธรดนั้นยาก หากคุณไม่ได้คิดค้นล้อใหม่คุณไม่จำเป็นต้องทดสอบซ้ำ
พอลวิลเลียมส์

คำตอบ:


245

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

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

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

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

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

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


1
การวิเคราะห์รหัสนั้นยอดเยี่ยมหากคุณจัดการกับภาษา / กรอบงานที่อนุญาต EG: Findbugs จะพบปัญหาการเกิดพร้อมกันที่ง่ายและใช้งานง่ายพร้อมตัวแปรแบบคงที่ สิ่งที่ไม่สามารถหาได้คือรูปแบบการออกแบบเดี่ยวมันจะสามารถสร้างวัตถุทั้งหมดได้หลายครั้ง ปลั๊กอินนี้ไม่เพียงพอสำหรับกรอบการทำงานเช่นฤดูใบไม้ผลิ
ซอมบี้

3
มีการรักษาจริง: วัตถุที่ใช้งานอยู่ drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/…
Dill

6
ในขณะที่นี่เป็นคำแนะนำที่ดีฉันยังคงถามว่า "ฉันจะทดสอบพื้นที่ขั้นต่ำสุดที่ต้องใช้หลายเธรดได้อย่างไร"
Bryan Rayner

5
"ถ้ามันซับซ้อนเกินกว่าที่จะทดสอบคุณกำลังทำผิด" - เราทุกคนต้องดำดิ่งลงไปในรหัสดั้งเดิมที่เราไม่ได้เขียน การสังเกตนี้ช่วยคนได้อย่างไร
Ronna

2
การวิเคราะห์แบบคงที่น่าจะเป็นความคิดที่ดี แต่ไม่ได้ทำการทดสอบ โพสต์นี้ไม่ตอบคำถามซึ่งเกี่ยวกับวิธีทดสอบ
Warren Dew

96

ไม่นานมานี้เมื่อมีการโพสต์คำถามนี้แล้วแต่ยังไม่ได้รับคำตอบ ...

คำตอบของkleolb02เป็นคำตอบที่ดี ฉันจะลองเข้าไปดูรายละเอียดเพิ่มเติม

มีวิธีหนึ่งซึ่งฉันฝึกใช้โค้ด C # สำหรับการทดสอบหน่วยคุณควรจะสามารถโปรแกรมการทดสอบที่ทำซ้ำได้ซึ่งเป็นความท้าทายที่ยิ่งใหญ่ที่สุดในรหัสแบบมัลติเธรด ดังนั้นคำตอบของฉันจึงมุ่งไปที่การบังคับใช้โค้ดอะซิงโครนัสในชุดทดสอบซึ่งทำงานพร้อมกัน

มันเป็นแนวคิดจากหนังสือของ Gerard Meszardos " xUnit Test Patterns " และเรียกว่า "Humble Object" (หน้า 695): คุณต้องแยกรหัสตรรกะหลักและสิ่งที่มีกลิ่นเหมือนโค้ดอะซิงโครนัสจากกันและกัน สิ่งนี้จะส่งผลให้คลาสสำหรับตรรกะหลักซึ่งทำงานพร้อมกัน

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

ตรรกะหลักนี้ต้องล้อมรอบด้วยคลาสอื่นซึ่งรับผิดชอบการรับสายไปยังตรรกะหลักแบบอะซิงโครนัสและมอบหมายการเรียกเหล่านี้ไปยังตรรกะหลัก รหัสการผลิตจะเข้าถึงตรรกะหลักผ่านคลาสนั้นเท่านั้น เนื่องจากคลาสนี้ควรมอบหมายการโทรเท่านั้นจึงเป็นคลาส "โง่" ที่ไม่มีตรรกะมาก ดังนั้นคุณสามารถทำการทดสอบหน่วยของคุณสำหรับคลาสทำงาน asychronous นี้เป็นอย่างน้อย

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


1
แต่บางครั้งถ้าเธรดที่ทำงานร่วมกันได้ดีก็เป็นสิ่งที่ควรทดสอบด้วยใช่ไหม แน่นอนฉันจะแยกตรรกะหลักจากส่วน async หลังจากอ่านคำตอบของคุณ แต่ฉันยังคงต้องทดสอบตรรกะผ่านทางอินเทอร์เฟซแบบ async ด้วยการโทรกลับแบบ
CopperCash

ระบบมัลติโปรเซสเซอร์มีอะไรบ้าง?
Technophile

65

แน่นอนยาก! ในการทดสอบหน่วย (C ++) ของฉันฉันได้แบ่งสิ่งนี้ออกเป็นหลายประเภทตามบรรทัดของรูปแบบการทำงานพร้อมกันที่ใช้:

  1. การทดสอบหน่วยสำหรับคลาสที่ทำงานในเธรดเดี่ยวและไม่ทราบถึงเธรด - ง่ายทดสอบตามปกติ

  2. หน่วยทดสอบสำหรับวัตถุตรวจสอบ (ผู้ที่ดำเนินการวิธีการซิงโครไนซ์ในเธรดการควบคุมของผู้เรียก) ที่เปิดเผย API สาธารณะที่ซิงโครไนซ์ - ยกตัวอย่างหลายเธรดจำลองที่ใช้ API สร้างสถานการณ์สมมติที่ใช้เงื่อนไขภายในของวัตถุแฝง รวมการทดสอบที่ยาวขึ้นหนึ่งรายการซึ่งโดยทั่วไปแล้วจะชนะ heck จากการทดสอบหลายเธรดเป็นระยะเวลานาน นี่คือหลักวิทยาศาสตร์ที่ฉันรู้ แต่มันสร้างความมั่นใจ

  3. การทดสอบหน่วยสำหรับวัตถุที่ใช้งาน (สิ่งที่ห่อหุ้มเธรดหรือชุดควบคุมของตัวเอง) - คล้ายกับ # 2 ด้านบนที่มีการแปรผันตามการออกแบบคลาส API สาธารณะอาจปิดกั้นหรือไม่ปิดกั้นผู้โทรอาจได้รับฟิวเจอร์สข้อมูลอาจมาถึงคิวหรือต้องถูกตัดออก มีหลายชุดที่เป็นไปได้ที่นี่; กล่องสีขาวออกไป ยังคงต้องมีหลายหัวข้อจำลองเพื่อโทรไปยังวัตถุภายใต้การทดสอบ

ในฐานะที่เป็นกัน:

ในการฝึกอบรมนักพัฒนาภายในที่ฉันทำฉันสอนPillars of Concurrencyและรูปแบบทั้งสองนี้เป็นกรอบการทำงานเบื้องต้นสำหรับการคิดและการแยกย่อยปัญหาการเกิดพร้อมกัน เห็นได้ชัดว่ามีแนวคิดขั้นสูงมากขึ้น แต่ฉันพบว่าชุดของพื้นฐานนี้ช่วยให้วิศวกรไม่ต้องทำซุป นอกจากนี้ยังนำไปสู่รหัสที่ทดสอบได้มากกว่าหน่วยตามที่อธิบายไว้ข้างต้น


51

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

การเขียนโค้ดแบบมัลติเธรดที่ทดสอบได้

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

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

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

การเขียนการทดสอบหน่วยสำหรับรหัสแบบมัลติเธรด

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

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

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

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


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

4
สรุปที่น่าทึ่งของหนึ่งในวิธีที่เข้าใจน้อยที่สุด คำตอบของคุณคือการแบ่งที่แท้จริงที่ ppl มองข้ามโดยทั่วไป
prash

1
โหลวินาทีนั้นค่อนข้างนานแม้ว่าคุณจะมีการทดสอบความยาวเพียงไม่กี่ร้อยเท่านั้น ...
Toby Speight

1
@TobySpeight การทดสอบมีความยาวเมื่อเทียบกับการทดสอบตามปกติ ฉันพบว่าการทดสอบครึ่งโหลนั้นมากเกินพอหากโค้ดเธรดได้รับการออกแบบอย่างถูกต้องให้ง่ายที่สุดเท่าที่จะเป็นไปได้ แต่ต้องมีการทดสอบแบบมัลติเธรดสองสามร้อยครั้งซึ่งจะบ่งบอกถึงการจัดเธรดที่ซับซ้อนเกินไป
Warren Dew

2
นั่นเป็นเหตุผลที่ดีที่ทำให้ตรรกะเธรดของคุณแยกออกจากการทำงานเท่าที่จะทำได้ (ฉันรู้ว่าพูดง่ายกว่าทำเสร็จ) และถ้าเป็นไปได้แบ่งชุดทดสอบออกเป็นชุด "เปลี่ยนทุกครั้ง" และ "กำหนดล่วงหน้า" (ดังนั้นการทดสอบนาทีต่อนาทีของคุณจะไม่ได้รับผลกระทบมากเกินไป)
Toby Speight

22

ฉันยังมีปัญหาร้ายแรงในการทดสอบรหัสแบบมัลติเธรด จากนั้นฉันก็พบทางออกที่ยอดเยี่ยมใน "รูปแบบการทดสอบ xUnit" โดย Gerard Meszaros รูปแบบที่เขาอธิบายที่เรียกว่าวัตถุฮัมเบิล

โดยทั่วไปจะอธิบายถึงวิธีที่คุณสามารถแยกลอจิกออกเป็นส่วนแยกที่ง่ายต่อการทดสอบที่แยกออกจากสภาพแวดล้อม หลังจากที่คุณทดสอบตรรกะนี้คุณสามารถทดสอบพฤติกรรมที่ซับซ้อน (การประมวลผลแบบหลายเธรดแบบอะซิงโครนัส ฯลฯ ... )


20

มีเครื่องมือไม่กี่รอบที่ค่อนข้างดี นี่คือข้อมูลสรุปของ Java บางส่วน

บางดีเครื่องมือในการวิเคราะห์แบบคงที่ ได้แก่FindBugs (ให้คำแนะนำที่มีประโยชน์บางอย่าง) JLint , Java Pathfinder (JPF & JPF2) และโบกอร์

MultithreadedTCเป็นเครื่องมือการวิเคราะห์แบบไดนามิกที่ดีมาก (รวมอยู่ใน JUnit) ที่คุณต้องตั้งค่ากรณีทดสอบของคุณเอง

การทดสอบจาก IBM Research เป็นเรื่องที่น่าสนใจ เป็นเครื่องมือในการรหัสของคุณโดยการแทรกพฤติกรรมการแก้ไขเธรดทุกชนิด (เช่นการนอนหลับ & ผลผลิต) เพื่อพยายามที่จะเปิดเผยข้อบกพร่องแบบสุ่ม

SPINเป็นเครื่องมือที่ยอดเยี่ยมสำหรับการสร้างแบบจำลองส่วนประกอบ Java (และอื่น ๆ ) แต่คุณต้องมีกรอบการทำงานที่มีประโยชน์ มันยากที่จะใช้ตามที่เป็นอยู่ แต่มีประสิทธิภาพมากถ้าคุณรู้วิธีใช้ มีเครื่องมือเล็กน้อยที่ใช้ SPIN ใต้ฝากระโปรง

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


16

การรอคอยอาจมีประโยชน์ในการช่วยคุณเขียนการทดสอบหน่วยกำหนด จะช่วยให้คุณรอจนกว่าสถานะบางอย่างในระบบของคุณมีการปรับปรุง ตัวอย่างเช่น:

await().untilCall( to(myService).myMethod(), greaterThan(3) );

หรือ

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

นอกจากนี้ยังมีการสนับสนุน Scala และ Groovy

await until { something() > 4 } // Scala example

1
การรอคอยนั้นยอดเยี่ยม - สิ่งที่ฉันกำลังมองหา!
Forge_7

14

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

อ้างถึง:

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

...

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

...

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


13

ฉันทำสิ่งนี้มามากมายและใช่แล้วมันแย่มาก

เคล็ดลับบางอย่าง:

  • GroboUtilsสำหรับการรันหลายเธรดการทดสอบ
  • alphaWorks ConTestกับคลาสของเครื่องมือเพื่อทำให้เกิดการแทรกสอดระหว่างการทำซ้ำ
  • สร้างthrowableเขตข้อมูลและตรวจสอบในtearDown(ดูรายการ 1) หากคุณพบข้อยกเว้นที่ไม่ดีในเธรดอื่นเพียงกำหนดให้เป็นแบบโยนได้
  • ฉันสร้างคลาส utils ในรายการ 2 และพบว่าล้ำค่าโดยเฉพาะ waitForVerify และ waitForCondition ซึ่งจะเพิ่มประสิทธิภาพการทดสอบของคุณอย่างมาก
  • ใช้ประโยชน์จากAtomicBooleanการทดสอบของคุณ เป็นเธรดที่ปลอดภัยและคุณมักจะต้องมีประเภทอ้างอิงสุดท้ายเพื่อเก็บค่าจากคลาสการเรียกกลับและสิ่งที่คล้ายกัน ดูตัวอย่างในรายการ 3
  • ตรวจสอบให้แน่ใจว่าได้ให้การทดสอบหมดเวลาเสมอ (เช่น@Test(timeout=60*1000)) เนื่องจากการทดสอบการทำงานพร้อมกันบางครั้งอาจแขวนตลอดไปเมื่อถูกทดสอบ

รายชื่อ 1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

รายชื่อ 2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

รายชื่อ 3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}

2
การหมดเวลาเป็นความคิดที่ดี แต่ถ้าการทดสอบหมดเวลาผลลัพธ์ใด ๆ ที่ตามมาในการดำเนินการนั้นจะเป็นที่น่าสงสัย การทดสอบหมดเวลาอาจยังมีเธรดบางตัวทำงานอยู่ซึ่งอาจทำให้คุณยุ่งเหยิง
Don Kirkby

12

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

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

โดยวิธีการที่ฉันคิดว่าด้านการทดสอบรหัส MT นี้ไม่ได้กล่าวถึงที่นี่: ระบุค่าคงที่ของรหัสที่คุณสามารถตรวจสอบแบบสุ่ม น่าเสียดายที่การหาค่าคงที่เหล่านั้นก็เป็นปัญหาที่ยากเช่นกัน นอกจากนี้พวกเขาอาจไม่ถือเวลาตลอดระหว่างการดำเนินการดังนั้นคุณต้องค้นหา / บังคับใช้คะแนนการประหารชีวิตที่คุณสามารถคาดหวังให้พวกเขาเป็นจริง การนำรหัสไปใช้ในสภาวะดังกล่าวก็เป็นปัญหาที่ยากด้วยเช่นกัน (และอาจทำให้เกิดปัญหาการทำงานพร้อมกันด้วยตัวเองมันยากมาก!

ลิงค์ที่น่าสนใจที่จะอ่าน:

  • deterministic interleaving : เฟรมเวิร์กที่อนุญาตให้ทำการสอดแทรกเธรดบางอย่างจากนั้นตรวจสอบค่าคงที่
  • jMock Blitzer : การซิงโครไนซ์การทดสอบความเครียด
  • assertConcurrent : เวอร์ชัน JUnit ของการทดสอบความเค้นที่มีความเครียด
  • การทดสอบพร้อมกันรหัส : ภาพรวมสั้น ๆ ของทั้งสองวิธีหลักของกำลังดุร้าย (ทดสอบความเครียด) หรือกำหนด (ไปสำหรับ invariants)

ผู้เขียนอ้างถึงการสุ่มในการทดสอบ อาจเป็นQuickCheckซึ่งมีการย้ายไปหลายภาษา คุณสามารถรับชมการพูดคุยเกี่ยวกับการทดสอบระบบพร้อมกันได้ที่นี่
Max

6

Pete Goodliffeมีชุดทดสอบหน่วยของรหัสเกลียว

มันเป็นเรื่องยาก. ฉันใช้วิธีที่ง่ายขึ้นและพยายามเก็บรหัสเธรดที่เป็นนามธรรมจากการทดสอบจริง Pete พูดถึงว่าวิธีที่ฉันทำผิด แต่ฉันแยกทางขวาหรือฉันเพิ่งโชคดี


6
ฉันอ่านบทความทั้งสองที่ตีพิมพ์ไปแล้วและฉันไม่พบว่ามีประโยชน์มาก เขาแค่พูดถึงความยากลำบากโดยไม่ให้คำแนะนำที่ชัดเจน บางทีบทความในอนาคตอาจจะดีขึ้น
Don Kirkby

6

สำหรับ Java, ตรวจสอบบทที่ 12 ของJcip มีตัวอย่างที่เป็นรูปธรรมบางประการเกี่ยวกับการเขียนการทดสอบหน่วยที่กำหนดขึ้นแบบมัลติเธรดเพื่อทดสอบความถูกต้องและค่าคงที่ของรหัสที่เกิดขึ้นพร้อมกัน

"การพิสูจน์" ความปลอดภัยของเธรดพร้อมการทดสอบหน่วยเป็นเรื่องที่มากกว่า ความเชื่อของฉันคือการให้บริการที่ดีขึ้นโดยการทดสอบการรวมอัตโนมัติในแพลตฟอร์ม / การกำหนดค่าที่หลากหลาย


6

ฉันชอบเขียนวิธีทดสอบสองวิธีขึ้นไปเพื่อดำเนินการกับเธรดแบบขนานและแต่ละวิธีจะทำการเรียกไปยังวัตถุภายใต้การทดสอบ ฉันใช้การโทร Sleep () เพื่อประสานลำดับของการโทรจากเธรดต่าง ๆ แต่นั่นไม่น่าเชื่อถือ นอกจากนี้ยังช้ากว่ามากเพราะคุณต้องนอนหลับให้นานพอที่เวลาจะทำงาน

ฉันพบไลบรารี Multithreaded TC Javaจากกลุ่มเดียวกับที่เขียน FindBugs ช่วยให้คุณระบุลำดับเหตุการณ์โดยไม่ต้องใช้ Sleep () และเชื่อถือได้ ฉันยังไม่ได้ลองเลย

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

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

อัปเดต:ฉันเคยเล่นบ้างด้วยไลบรารี Multithreaded TC Java และใช้งานได้ดี ฉันยังรังเพลิงบางส่วนของคุณสมบัติของมันเป็นรุ่น .NET ผมเรียกTickingTest


5

ฉันจัดการกับการทดสอบหน่วยของส่วนประกอบแบบเธรดในลักษณะเดียวกับที่ฉันจัดการกับการทดสอบหน่วยใด ๆ นั่นคือการกลับตัวของการควบคุมและกรอบการแยก ฉันพัฒนาใน. Net-arena และออกจากกล่องการทำเกลียว (เหนือสิ่งอื่นใด) นั้นยากมาก (ฉันจะบอกว่าเป็นไปไม่ได้เกือบ) ในการแยกตัวออกจากกันอย่างสมบูรณ์

ดังนั้นฉันจึงได้เขียนชุดคลุมที่มีลักษณะดังนี้ (ประยุกต์):

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

จากนั้นฉันสามารถแทรก IThreadingManager ลงในส่วนประกอบของฉันได้อย่างง่ายดายและใช้กรอบการแยกที่ฉันเลือกเพื่อทำให้เธรดทำงานตามที่คาดไว้ระหว่างการทดสอบ

มันทำงานได้ดีมากสำหรับฉันและฉันใช้วิธีการเดียวกันกับกลุ่มเธรดสิ่งต่างๆใน System.Environment, Sleep ฯลฯ


5

ดูคำตอบที่เกี่ยวข้องของฉันที่

การออกแบบคลาสทดสอบสำหรับ Barrier ที่กำหนดเอง

มันมีอคติต่อ Java แต่มีข้อสรุปที่สมเหตุสมผลเกี่ยวกับตัวเลือกต่างๆ

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

การวิเคราะห์แบบสแตติกและวิธีการอย่างเป็นทางการ (ดู, การทำงานพร้อมกัน: แบบจำลองของรัฐและโปรแกรม Java ) เป็นตัวเลือก แต่ฉันพบว่ามันถูกใช้อย่าง จำกัด ในการพัฒนาเชิงพาณิชย์

อย่าลืมว่าการทดสอบรูปแบบการโหลด / การแช่ใด ๆ จะรับประกันได้ยากที่จะเน้นปัญหา

โชคดี!


คุณควรพูดถึงtempus-fugitห้องสมุดของคุณที่นี่ด้วยhelps write and test concurrent code;)
ไอดอล

4

ฉันเพิ่งค้นพบ (สำหรับ Java) เครื่องมือที่เรียกว่า Threadsafe มันเป็นเครื่องมือวิเคราะห์แบบสแตติกเช่นเดียวกับ findbugs แต่โดยเฉพาะเพื่อจุดปัญหาหลายเธรด ไม่ใช่การแทนที่สำหรับการทดสอบ แต่ฉันสามารถแนะนำให้เป็นส่วนหนึ่งของการเขียน Java แบบมัลติเธรดที่เชื่อถือได้

มันยังจับประเด็นปัญหาที่ละเอียดอ่อนบางอย่างรอบ ๆ สิ่งต่าง ๆ เช่นการรวมย่อยของคลาสการเข้าถึงวัตถุที่ไม่ปลอดภัยผ่านคลาสที่เกิดขึ้นพร้อมกันและการปรับเปลี่ยนสารระเหยที่ขาดหายไป

หากคุณเขียน Java แบบมัลติเธรดให้มันยิง


3

บทความต่อไปนี้แนะนำ 2 วิธีแก้ไข การส่งสัญญาณ (CountDownLatch) และเพิ่มฟังก์ชันการทำงานเช่นส่งข้อมูลออกจากเธรดภายใน อีกวิธีหนึ่งในการบรรลุวัตถุประสงค์นี้คือการใช้กลุ่มเธรด (ดูจุดที่น่าสนใจ)

Sprinkler - วัตถุการซิงโครไนซ์ขั้นสูง


3
โปรดอธิบายวิธีการที่นี่ลิงก์ภายนอกอาจตายในอนาคต
Uooo

2

ฉันใช้เวลาส่วนใหญ่ในสัปดาห์ที่แล้วที่ห้องสมุดมหาวิทยาลัยเพื่อศึกษาการแก้ไขข้อบกพร่องของรหัสที่เกิดขึ้นพร้อมกัน ปัญหาที่สำคัญคือรหัสที่เกิดขึ้นพร้อมกันนั้นไม่สามารถกำหนดได้ โดยทั่วไปแล้วการดีบักทางวิชาการได้เข้าสู่หนึ่งในสามค่ายที่นี่:

  1. เหตุการณ์ร่องรอย / รีเพลย์ สิ่งนี้ต้องมีการตรวจสอบเหตุการณ์แล้วตรวจสอบเหตุการณ์ที่ส่งไป ในเฟรมเวิร์ก UT สิ่งนี้จะเกี่ยวข้องกับการส่งเหตุการณ์ด้วยตนเองเป็นส่วนหนึ่งของการทดสอบแล้วทำการตรวจสอบภายหลังการชันสูตร
  2. สคริปต์ นี่คือที่ที่คุณโต้ตอบกับโค้ดที่ใช้กับชุดทริกเกอร์ "บน x> foo, baz ()" สิ่งนี้สามารถตีความได้ในเฟรมเวิร์ก UT ซึ่งคุณมีระบบรันไทม์ที่จะทำการทดสอบตามเงื่อนไขที่กำหนด
  3. เชิงโต้ตอบ. สิ่งนี้เห็นได้ชัดว่าจะไม่ทำงานในสถานการณ์การทดสอบอัตโนมัติ ;)

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

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

ขอให้โชคดีและทำงานต่อไปในปัญหา


2

ฉันมีงานที่โชคร้ายในการทดสอบโค้ดที่เป็นเธรดและมันเป็นการทดสอบที่ยากที่สุดที่ฉันเคยเขียน

เมื่อเขียนข้อสอบฉันใช้ตัวแทนและเหตุการณ์หลายอย่างร่วมกัน โดยพื้นฐานแล้วทั้งหมดเกี่ยวกับการใช้PropertyNotifyChangedกิจกรรมที่มีโพลนั้นWaitCallbackหรือบางประเภทConditionalWaiter

ฉันไม่แน่ใจว่านี่เป็นวิธีที่ดีที่สุดหรือเปล่า แต่มันก็ได้ผลสำหรับฉันแล้ว


1

สมมติว่าภายใต้รหัส "มัลติเธรด" หมายถึงสิ่งที่เป็น

  • stateful และไม่แน่นอน
  • และเข้าถึง / แก้ไขโดยหลายกระทู้พร้อมกัน

กล่าวอีกนัยหนึ่งเรากำลังพูดถึงการทดสอบระดับ / เมธอด / ยูนิต / หน่วยระดับความปลอดภัยที่กำหนดเองซึ่งควรจะเป็นสัตว์ร้ายที่หายากมากในปัจจุบัน

เนื่องจากสัตว์ร้ายนี้หายากอันดับแรกเราต้องตรวจสอบให้แน่ใจว่ามีข้อแก้ตัวที่ถูกต้องทั้งหมดในการเขียน

ขั้นตอนที่ 1พิจารณาแก้ไขสถานะในบริบทการซิงโครไนซ์เดียวกัน

วันนี้มันเป็นเรื่องง่ายที่จะเขียนรหัสพร้อมกันและแบบอะซิงโครนัสที่ IO หรือการดำเนินการช้าอื่น ๆ offloaded เป็นพื้นหลัง แต่สถานะที่ใช้ร่วมกันมีการปรับปรุงและสอบถามในบริบทการประสานหนึ่ง เช่นงาน async / await และ Rx ใน. NET เป็นต้น - พวกเขาทั้งหมดสามารถทดสอบได้โดยการออกแบบงาน "ตัวจริง" และตัวตั้งเวลาสามารถทดแทนได้เพื่อให้การทดสอบกำหนดขึ้นมา

มันอาจฟังดูมีข้อ จำกัด มาก แต่วิธีนี้ใช้ได้ดีอย่างน่าประหลาดใจ เป็นไปได้ที่จะเขียนแอพทั้งหมดในรูปแบบนี้โดยไม่จำเป็นต้องตั้งค่าสถานะให้ปลอดภัย (ฉันทำ)

ขั้นตอนที่ 2หากการจัดการสถานะที่ใช้ร่วมกันในบริบทการซิงโครไนส์เดียวเป็นไปไม่ได้อย่างแน่นอน

ตรวจสอบให้แน่ใจว่าล้อไม่ได้ถูกคิดค้นใหม่ / ไม่มีทางเลือกมาตรฐานที่สามารถปรับให้เหมาะกับงานได้ น่าจะเป็นไปได้ว่ารหัสนั้นมีความเหนียวแน่นมากและมีอยู่ภายในหน่วยเดียวเช่นมีโอกาสที่ดีที่มันเป็นกรณีพิเศษของโครงสร้างข้อมูลแบบ thread-safe มาตรฐานบางอย่างเช่นการแมปแฮชหรือการรวบรวมหรืออะไรก็ตาม

หมายเหตุ: ถ้ารหัสมีขนาดใหญ่ / ขยายข้ามหลายคลาสและต้องการการจัดการสถานะแบบหลายเธรดแล้วมีโอกาสสูงมากที่การออกแบบไม่ดีให้พิจารณาขั้นตอนที่ 1 ใหม่

ขั้นตอนที่ 3หากขั้นตอนนี้จะมาถึงแล้วเราต้องทดสอบของเราที่กำหนดเองของตัวเอง stateful ด้ายปลอดภัยระดับ

ฉันจะตายอย่างซื่อสัตย์: ฉันไม่เคยต้องเขียนการทดสอบที่เหมาะสมสำหรับรหัสดังกล่าว เวลาส่วนใหญ่ที่ฉันไปที่ขั้นตอนที่ 1 บางครั้งในขั้นตอนที่ 2 ครั้งสุดท้ายที่ฉันต้องเขียนโค้ดเธรดที่ปลอดภัยแบบกำหนดเองเป็นเวลาหลายปีที่ผ่านมาว่ามันเป็นก่อนที่ฉันจะนำการทดสอบหน่วย / อาจจะไม่ต้องเขียน ด้วยความรู้ในปัจจุบันอยู่แล้ว

หากฉันต้องทดสอบรหัสดังกล่าว ( สุดท้ายคำตอบจริง ) จากนั้นฉันจะลองทำสองสามอย่างด้านล่างนี้

  1. การทดสอบความเครียดแบบไม่กำหนดค่า เช่นรัน 100 เธรดพร้อมกันและตรวจสอบว่าผลลัพธ์สุดท้ายนั้นสอดคล้องกัน นี่เป็นเรื่องปกติมากขึ้นสำหรับการทดสอบระดับสูง / การรวมกลุ่มของผู้ใช้หลายคน แต่ยังสามารถใช้ในระดับหน่วยได้

  2. เปิดเผยการทดสอบ 'hooks' โดยที่การทดสอบสามารถฉีดรหัสบางอย่างเพื่อช่วยในการกำหนดสถานการณ์ที่เธรดหนึ่งต้องดำเนินการก่อนการทดสอบอื่น น่าเกลียดเหมือนที่เป็นอยู่ฉันไม่สามารถคิดอะไรได้เลยดีกว่า

  3. การทดสอบที่ขับเคลื่อนด้วยความล่าช้าเพื่อให้เธรดทำงานและดำเนินการตามลำดับเฉพาะ การทดสอบที่พูดอย่างเคร่งครัดนั้นก็ไม่ใช่แบบไม่ จำกัด เช่นกัน (มีโอกาสที่ระบบจะหยุดการสะสม / หยุดการทำงานของโลกซึ่งอาจบิดเบือนความล่าช้าในการเตรียมการอื่น ๆ ) นอกจากนี้ยังเป็นเรื่องที่น่าเกลียด


0

สำหรับรหัส J2E ฉันใช้ SilkPerformer, LoadRunner และ JMeter เพื่อทดสอบการใช้เธรดพร้อมกัน พวกเขาทุกคนทำในสิ่งเดียวกัน. โดยทั่วไปแล้วพวกเขาให้อินเทอร์เฟซที่ค่อนข้างง่ายสำหรับคุณในการดูแลพร็อกซีเซิร์ฟเวอร์เวอร์ชันที่จำเป็นเพื่อวิเคราะห์สตรีมข้อมูล TCP / IP และจำลองผู้ใช้หลายคนทำการร้องขอพร้อมกันไปยังเซิร์ฟเวอร์แอปของคุณ พร็อกซีเซิร์ฟเวอร์สามารถให้ความสามารถในการทำสิ่งต่างๆเช่นวิเคราะห์คำขอที่ทำโดยนำเสนอทั้งหน้าและ URL ที่ส่งไปยังเซิร์ฟเวอร์รวมถึงการตอบสนองจากเซิร์ฟเวอร์หลังจากประมวลผลคำขอ

คุณสามารถค้นหาข้อบกพร่องบางอย่างในโหมด http ที่ไม่ปลอดภัยซึ่งอย่างน้อยคุณสามารถวิเคราะห์ข้อมูลในแบบฟอร์มที่ถูกส่งและแก้ไขอย่างเป็นระบบสำหรับผู้ใช้แต่ละคน แต่การทดสอบที่แท้จริงคือเมื่อคุณทำงานใน https (Secured Socket Layers) จากนั้นคุณต้องต่อสู้กับการเปลี่ยนแปลงเซสชันและข้อมูลคุกกี้อย่างเป็นระบบซึ่งอาจทำให้คุณสับสนมากขึ้น

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

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

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


0

การเกิดขึ้นพร้อมกันคือการทำงานร่วมกันที่ซับซ้อนระหว่างรูปแบบหน่วยความจำฮาร์ดแวร์แคชและรหัสของเรา ในกรณีของ Java ในการทดสอบดังกล่าวอย่างน้อยส่วนหนึ่งได้รับการแก้ไขโดยส่วนใหญ่jcstress ผู้สร้างไลบรารีนั้นเป็นที่รู้จักกันว่าเป็นผู้แต่งคุณสมบัติ JVM, GC และ Java พร้อมกัน

แต่แม้กระทั่งไลบรารีนี้ต้องการความรู้ที่ดีเกี่ยวกับข้อมูลจำเพาะ Java Memory Model เพื่อให้เรารู้ว่าเรากำลังทดสอบอะไรอยู่ แต่ฉันคิดว่าจุดเน้นของความพยายามนี้คือ mircobenchmarks ไม่ใช่แอปพลิเคชันทางธุรกิจขนาดใหญ่


0

มีบทความในหัวข้อโดยใช้ Rust เป็นภาษาในรหัสตัวอย่าง:

https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a

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

จากนั้นหากเป็นวิธีที่คุณจัดโครงสร้าง "ส่วนประกอบ" วิธีที่ง่ายที่สุดในการทดสอบคือการใช้แชนเนลเพื่อส่งข้อความถึงพวกเขาแล้วบล็อกช่องอื่นเพื่อยืนยันว่าส่วนประกอบส่งข้อความที่คาดหวัง

บทความที่เชื่อมโยงไปยังเขียนโดยใช้การทดสอบหน่วย


-1

หากคุณกำลังทดสอบThread ใหม่อย่างง่าย(runnable) .run () คุณสามารถเยาะเย้ย Thread เพื่อรัน runnable ตามลำดับ

ตัวอย่างเช่นถ้ารหัสของวัตถุที่ทดสอบเรียกใช้เธรดใหม่เช่นนี้

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

จากนั้นการเยาะเย้ยเธรดใหม่และรันอาร์กิวเมนต์ที่รันได้ตามลำดับสามารถช่วยได้

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}

-3

(ถ้าเป็นไปได้) อย่าใช้เธรดให้ใช้นักแสดง / วัตถุที่ใช้งานอยู่ ทดสอบง่าย


2
@OMTheEternity อาจจะเป็นคำตอบที่ดีที่สุด
Dill

-5

คุณสามารถใช้ EasyMock.makeThreadSafe เพื่อทำการทดสอบ threadsafe


นี่ไม่ใช่วิธีที่เป็นไปได้ในการทดสอบรหัสแบบมัลติเธรด ปัญหาไม่ใช่ว่ารหัสทดสอบรันหลายเธรด แต่คุณทดสอบโค้ดที่มักรันหลายเธรด และคุณไม่สามารถซิงโครไนซ์ทุกสิ่งออกไปได้เพราะจริงๆแล้วคุณไม่ได้ทดสอบการแข่งขันข้อมูลอีกต่อไป
bennidi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.