ไฟล์เดียวหรือหลายไฟล์สำหรับการทดสอบหน่วยในชั้นเดียว?


20

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

Fwiw ฉันหมายถึง "การทดสอบหน่วย" ในความหมายที่บริสุทธิ์ว่าเป็นการทดสอบในกล่องสีขาวที่กำหนดเป้าหมายไปที่คลาสเดี่ยวหนึ่งการยืนยันต่อการทดสอบหนึ่งการอ้างอิงทั้งหมดที่ล้อเลียนเป็นต้น

ตัวอย่างสถานการณ์เป็นคลาส (เรียกว่าเอกสาร) ที่มีสองวิธี: CheckIn และ CheckOut แต่ละวิธีใช้กฎต่าง ๆ ฯลฯ ที่ควบคุมพฤติกรรมของพวกเขา ตามกฎข้อหนึ่งต่อการทดสอบฉันจะมีการทดสอบหลายวิธีสำหรับแต่ละวิธี ฉันสามารถวางทั้งหมดของการทดสอบในครั้งเดียวDocumentTestsระดับที่มีชื่อชอบและCheckInShouldThrowExceptionWhenUserIsUnauthorizedCheckOutShouldThrowExceptionWhenUserIsUnauthorized

หรือผมอาจจะมีสองชั้นทดสอบเฉพาะกิจการ: และCheckInShould CheckOutShouldในกรณีนี้ชื่อการทดสอบของฉันจะสั้นลง แต่พวกเขาจะจัดระเบียบเพื่อให้การทดสอบทั้งหมดสำหรับพฤติกรรมที่เฉพาะเจาะจง (วิธีการ) อยู่ด้วยกัน

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


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

12
@ เบอร์นาร์ด: มันถือเป็นการปฏิบัติที่ไม่ดีที่จะมีการยืนยันหลายวิธีต่อวิธีชื่อวิธีการทดสอบแบบยาวจะไม่ถือว่าเป็นการปฏิบัติที่ไม่ดี เรามักจะบันทึกว่าวิธีการทดสอบในชื่อตัวเอง เช่น constructor_nullSomeList (), setUserName_UserNameIsNotValid () ฯลฯ ...
c_maker

4
@ เบอร์นาร์ด: ฉันใช้ชื่อการทดสอบแบบยาว ฉันรักพวกเขาเพราะมันชัดเจนว่าอะไรไม่ทำงาน (ถ้าชื่อของคุณเป็นตัวเลือกที่ดี;)
เซบาสเตียนบาวเออร์

การยืนยันหลายครั้งไม่ใช่วิธีปฏิบัติที่ไม่ดีหากพวกเขาทดสอบผลลัพธ์เดียวกันทั้งหมด เช่น. คุณจะไม่ต้องtestResponseContainsSuccessTrue(), และtestResponseContainsMyData() testResponseStatusCodeIsOk()คุณจะมีพวกเขาในครั้งเดียวtestResponse()ซึ่งได้สามอ้าง: assertEquals(200, response.status), assertEquals({"data": "mydata"}, response.data)และassertEquals(true, response.success)
จูฮา Untinen

คำตอบ:


18

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


3
ตัวอย่างที่ดีว่าทำไมจึงนำเสนอวิธีการดังกล่าวแก่ฉันตั้งแต่แรก ตัวอย่างเช่นเมื่อฉันต้องการทดสอบวิธีเช็คอินฉันต้องการให้วัตถุที่อยู่ภายใต้การทดสอบติดตั้งแบบทางเดียวเสมอ แต่เมื่อฉันทดสอบวิธี CheckOut ฉันต้องการการตั้งค่าที่แตกต่างกัน จุดดี.
SonOfPirate

14

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

  1. ไม่จำเป็นต้องทำซ้ำ (และเก็บรักษารหัสการตั้งค่าหลายรุ่น)
  2. ง่ายกว่าในการรันการทดสอบทั้งหมดสำหรับคลาสจาก IDE หากพวกเขาเป็นกลุ่มในหนึ่งคลาสทดสอบ
  3. การแก้ไขปัญหาที่ง่ายขึ้นด้วยการทำแผนที่แบบหนึ่งต่อหนึ่งระหว่างคลาสการทดสอบและคลาส

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

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

ใน JUnit เช่นคุณอาจต้องการทดสอบสิ่งต่าง ๆ โดยใช้ตัววิ่งที่แตกต่างกันซึ่งนำไปสู่ความต้องการคลาสที่แตกต่างกัน
Joachim Nilsson

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

7

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

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


อนิจจาในโลกแห่งความเป็นจริงไม่ใช่ทุกคลาสที่เป็นไปตาม SRP และอื่น ๆ ... และนี่จะกลายเป็นปัญหาเมื่อคุณทำการทดสอบรหัสดั้งเดิม
PéterTörök

3
ไม่แน่ใจว่า SRP เกี่ยวข้องกับที่นี่ได้อย่างไร ฉันมีหลายวิธีในชั้นเรียนของฉันและฉันจำเป็นต้องเขียนการทดสอบหน่วยสำหรับทุกความต้องการที่แตกต่างกันซึ่งขับเคลื่อนพฤติกรรมของพวกเขา มันจะดีกว่าหรือไม่ที่จะมีคลาสการทดสอบหนึ่งคลาสที่มี 50 วิธีทดสอบหรือ 5 คลาสที่มี 10 การทดสอบที่เกี่ยวข้องกับวิธีการเฉพาะในชั้นเรียนภายใต้การทดสอบ?
SonOfPirate

2
@SonOfPirate: หากคุณต้องการการทดสอบจำนวนมากนั่นอาจหมายถึงวิธีการของคุณกำลังทำมากเกินไป ดังนั้น SRP จึงมีความเกี่ยวข้องอย่างมากที่นี่ หากคุณใช้ SRP กับคลาสรวมถึงวิธีการต่างๆคุณจะมีชั้นเรียนขนาดเล็กจำนวนมากและวิธีการที่ง่ายต่อการทดสอบและคุณไม่จำเป็นต้องมีวิธีการทดสอบมากมาย (แต่ฉันเห็นด้วยกับPéterTörökว่าเมื่อคุณทดสอบรหัสดั้งเดิมแนวทางปฏิบัติที่ดีอยู่นอกบ้านและคุณต้องทำสิ่งที่ดีที่สุดที่คุณสามารถทำได้กับสิ่งที่คุณได้รับ ... )
c_maker

ตัวอย่างที่ดีที่สุดที่ฉันสามารถให้ได้คือวิธีการเช็คอินที่เรามีกฎเกณฑ์ทางธุรกิจที่กำหนดว่าใครได้รับอนุญาตให้ดำเนินการ (การอนุญาต) หากวัตถุอยู่ในสถานะที่เหมาะสมที่จะตรวจสอบเช่นถ้ามันเช็คเอาท์และหากมีการเปลี่ยนแปลง เคยทำแล้ว. เราจะมีการทดสอบหน่วยที่ตรวจสอบว่ามีการบังคับใช้กฎการให้สิทธิ์รวมถึงการทดสอบเพื่อตรวจสอบว่าวิธีการทำงานนั้นถูกต้องหาก CheckIn ถูกเรียกเมื่อวัตถุไม่ได้เช็คเอาท์ไม่เปลี่ยนแปลง ฯลฯ นั่นคือเหตุผลที่เราลงท้ายด้วยหลาย ๆ การทดสอบ คุณแนะนำว่าสิ่งนี้ผิดหรือเปล่า?
SonOfPirate

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

2

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

ในกรณีที่การทดสอบที่แตกต่างกันต้องการการตั้งค่าที่แตกต่างกันอย่างมากฉันได้เห็นผู้ปฏิบัติงานของ TDD บางคนใส่ "ฟิกซ์เจอร์" หรือ "การตั้งค่า" ในคลาส / ไฟล์ต่าง ๆ แต่ไม่ใช่การทดสอบด้วยตนเอง


1

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

นี่คือตัวอย่างวิธีใช้วิธีการนี้กับสถานการณ์เดิมของฉัน:

public abstract class DocumentTests : TestBase
{
    [TestClass()]
    public sealed class CheckInShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }

    [TestClass()]
    public sealed class CheckOutShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }
}

ผลลัพธ์นี้ในการทดสอบต่อไปนี้ปรากฏในรายการทดสอบ:

DocumentTests+CheckInShould.ThrowExceptionWhenUserIsNotAuthorized
DocumentTests+CheckOutShould.ThrowExceptionWhenUserIsNotAuthorized

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

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


1

ลองคิดวิธีอื่น ๆ ประมาณหนึ่งนาที

ทำไมคุณต้องมีหนึ่งคลาสทดสอบต่อหนึ่งคลาส? คุณกำลังทำการทดสอบในชั้นเรียนหรือการทดสอบหน่วย? คุณขึ้นอยู่กับชั้นเรียนด้วยหรือไม่

การทดสอบหน่วยควรจะทดสอบพฤติกรรมที่เฉพาะเจาะจงในบริบทที่เฉพาะเจาะจง ความจริงที่ว่าชั้นเรียนของคุณมีCheckInวิธีการที่ควรมีพฤติกรรมที่ต้องการในสถานที่แรก

คุณคิดยังไงกับรหัสหลอกนี้:

// check_in_test.file

class CheckInTest extends TestCase {
        /** @test */
        public function unauthorized_users_cannot_check_in() {
                $this->expectException();

                $document = new Document($unauthorizedUser);

                $document->checkIn();
        }
}

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

ไฟล์ทดสอบหนึ่งไฟล์สำหรับหนึ่งคลาสทำให้ยากต่อการปรับโครงสร้างใหม่เมื่อใดก็ตามที่คุณต้องการและการทดสอบก็ไม่สมเหตุสมผลในด้านโดเมน / ตรรกะของโค้ดเอง


0

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

โดยปกติฉันจะเริ่มด้วยคลาสการทดสอบหนึ่งหน่วยต่อคลาสการผลิต แต่ในที่สุดอาจแบ่งคลาสการทดสอบหน่วยนั้นออกเป็นคลาสการทดสอบหลาย ๆ คลาสตามลักษณะการทดสอบที่ใช้ ในคำอื่น ๆ ฉันขอแนะนำให้แยกชั้นเรียนทดสอบเป็น CheckInShould และ CheckOutShould แต่แยกออกตามพฤติกรรมของหน่วยทดสอบ


0

1. พิจารณาสิ่งที่น่าจะผิดพลาด

ในระหว่างการพัฒนาเริ่มแรกคุณรู้ดีว่าคุณกำลังทำอะไรและทั้งสองวิธีอาจจะใช้ได้

มันน่าสนใจมากขึ้นเมื่อการทดสอบล้มเหลวหลังจากการเปลี่ยนแปลงในภายหลัง มีความเป็นไปได้สองอย่าง:

  1. คุณเสียอย่างจริงจังCheckIn(หรือCheckOut) อีกครั้งทั้งไฟล์เดี่ยวและโซลูชันสองไฟล์ก็โอเคในกรณีนั้น
  2. คุณได้ปรับเปลี่ยนทั้งสองCheckInและCheckOut(และอาจทดสอบ) ในลักษณะที่เหมาะสมสำหรับแต่ละคน แต่ไม่ใช่สำหรับทั้งสองเข้าด้วยกัน คุณหักการเชื่อมโยงของคู่ ในกรณีดังกล่าวการแยกการทดสอบมากกว่าสองไฟล์จะทำให้ยากต่อการเข้าใจปัญหา

2. พิจารณาว่าจะใช้การทดสอบแบบใด

การทดสอบมีจุดประสงค์หลักสองประการ:

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

ดังนั้น?

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

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