ใช้การทดสอบหน่วยเพื่อบอกเล่าเรื่องราวความคิดที่ดีหรือไม่?


13

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

  • RequiresLogin_should_redirect_when_not_logged_in
  • RequiresLogin_should_pass_through_when_logged_in
  • Login_should_work_when_given_proper_credentials

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

ดังนั้นฉันคิดว่าอาจจะแทนที่จะเขียนแบบทดสอบที่ทดสอบการทำงานล้วนๆอาจจะเขียนชุดการทดสอบที่ครอบคลุมสถานการณ์

ตัวอย่างเช่นนี่คือส่วนทดสอบที่ฉันสร้างขึ้นด้วย:

public class Authentication_Bill
{
    public void Bill_has_no_account() 
    { //assert username "bill" not in UserStore
    }
    public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
    { //Calls RequiredLogin and should redirect to login page
    }
    public void Bill_creates_account()
    { //pretend the login page doubled as registration and he made an account. Add the account here
    }
    public void Bill_logs_in_with_new_account()
    { //Login("bill", "password"). Assert not redirected to login page
    }
    public void Bill_can_now_post_comment()
    { //Calls RequiredLogin, but should not kill request or redirect to login page
    }
}

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

อย่างไรก็ตามนี่เป็นรูปแบบที่แนะนำหรือไม่? หรือนี่จะเหมาะอย่างยิ่งสำหรับการทดสอบการรวมระบบของ API แทนที่จะเป็นการทดสอบแบบ "หน่วย" หรือไม่? นี่เป็นเพียงโครงการส่วนตัวดังนั้นฉันจึงเปิดให้มีการทดลองที่อาจหรืออาจไม่เป็นไปด้วยดี


4
เส้นแบ่งระหว่างหน่วยการทดสอบการรวมและการใช้งานนั้นเบลอถ้าฉันต้องเลือกชื่อสำหรับการทดสอบของคุณมันก็จะใช้งานได้
yannis

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

1
ฉันเขียนคำตอบพร้อมรายละเอียดเกี่ยวกับวิธีการเขียนแบบทดสอบแบบดั้งเดิมโดยใช้รูปแบบ Arrange / Act / Assert แต่เพื่อนประสบความสำเร็จอย่างมากในการใช้github.com/cucumber/cucumber/wiki/Gherkinซึ่งเป็น ใช้สำหรับรายละเอียดและ afaik สามารถสร้างการทดสอบแตงกวา
StuperUser

แม้ว่าฉันจะไม่ใช้วิธีที่คุณแสดงด้วย nunit หรือสิ่งที่คล้ายกันก็ตาม nspec ได้สนับสนุนการสร้างบริบทและการทดสอบในลักษณะที่ขับเคลื่อนด้วยเรื่องราวเพิ่มเติม: nspec.org
Mike

1
เปลี่ยน "Bill" เป็น "ผู้ใช้" และคุณทำเสร็จแล้ว
Steven A. Lowe

คำตอบ:


15

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

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

คุณสามารถหลีกเลี่ยงสิ่งนี้ได้อย่างง่ายดายที่นี่โดยแยกส่วนการทดสอบหลักของแต่ละการทดสอบ (รวมถึง "ยืนยัน") จากส่วนที่ตั้งค่าระบบของคุณในสถานะเริ่มต้นที่ถูกต้อง ใช้ตัวอย่างของคุณด้านบน: เขียนวิธีการสร้างบัญชีเข้าสู่ระบบและโพสต์ความคิดเห็น - โดยไม่ต้องมีการยืนยันใด ๆ จากนั้นนำวิธีการเหล่านั้นมาใช้ซ้ำในการทดสอบที่แตกต่างกัน คุณจะต้องเพิ่มรหัสลงใน[Setup]วิธีการทดสอบการติดตั้งของคุณเพื่อให้แน่ใจว่าระบบอยู่ในสถานะเริ่มต้นที่กำหนดไว้อย่างถูกต้อง (เช่นไม่มีบัญชีที่อยู่ในฐานข้อมูล

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

ดังนั้นควรมีลักษณะดังนี้:

[TestFixture]
public class Authentication_Bill
{
    [Setup]
    public void Init()
    {  // bring the system in a predefined state, with noone logged in so far
    }

    [Test]
    public void Test_if_Bill_can_create_account()
    {
         CreateAccountForBill();
         // assert that the account was created properly 
    }

    [Test]
    public void Test_if_Bill_can_post_comment_after_login()
    { 
         // here is the "story" now
         CreateAccountForBill();
         LoginWithBillsAccount();
         AddCommentForBill();
        //  assert that the right things happened
    }

    private void CreateAccountForBill()
    {
        // ...
    }
    // ...
}

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

4

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

การทดสอบหน่วยที่ดีควรแยกออกจากรหัสที่ขึ้นต่อกันอื่น ๆ ทั้งหมดซึ่งเป็นหน่วยของรหัสที่เล็กที่สุดที่สามารถทดสอบได้

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

เป็นบทความที่ดีในเชิงลึกเพิ่มเติมเป็นคลาสสิกบนทดสอบไฮบริดที่สกปรก

เพื่อให้ชั้นเรียนวิธีการและผลลัพธ์สามารถอ่านได้การทดสอบ Art of Unitที่ยอดเยี่ยมใช้หลักการตั้งชื่อ

ชั้นทดสอบ:

ClassUnderTestTests

วิธีทดสอบ:

MethodUnderTest_Condition_ExpectedResult

ในการคัดลอกตัวอย่างของ @Doc Brown แทนที่จะใช้ [ตั้งค่า] ซึ่งทำงานก่อนการทดสอบแต่ละครั้งฉันเขียนวิธีการช่วยเหลือเพื่อสร้างวัตถุแยกเพื่อทดสอบ

[TestFixture]
public class AuthenticationTests
{
    private Authentication GetAuthenticationUnderTest()
    {
        // create an isolated Authentication object ready for test
    }

    [Test]
    public void CreateAccount_WithValidCredentials_CreatesAccount()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         //Act
         Account result = codeUnderTest.CreateAccount("some", "valid", "data");
         //Assert
         //some assert
    }

    [Test]
    public void CreateAccount_WithInvalidCredentials_ThrowsException()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         Exception result;
         //Act
         try
         {
             codeUnderTest.CreateAccount("some", "invalid", "data");
         }
         catch(Exception e)
         {
             result = e;
         }
         //Assert
         //some assert
    }
}

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

นั่นคือวิธีที่ผมเคยเขียนทดสอบหน่วยเสมอ แต่เพื่อนยังมีจำนวนมากที่ประสบความสำเร็จกับGerkin


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

3

สิ่งที่คุณกำลังอธิบายฟังดูคล้ายกับBehavior Driven Design (BDD) มากกว่าการทดสอบหน่วยถึงฉัน ดูSpecFlowซึ่งเป็นเทคโนโลยี. NET BDD ที่ยึดตามGherkin DSL

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

เกี่ยวกับอนุสัญญาสำหรับการทดสอบหน่วยคำตอบของ @ DocBrown ดูเหมือนจะมั่นคง


สำหรับข้อมูล BDD นั้นเหมือนกับ TDD มันเป็นแค่สไตล์การเขียนที่เปลี่ยนแปลง ตัวอย่าง: TDD = assert(value === expected)BDD = value.should.equals(expected)+ คุณอธิบายคุณลักษณะในเลเยอร์ที่แก้ปัญหา "ความเป็นอิสระของการทดสอบหน่วย" นี่เป็นสไตล์ที่ยอดเยี่ยม!
Offirmo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.