การทดสอบหน่วยทำงานอย่างไร


23

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

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        assert(adder.add(0, 0) == 0);
        assert(adder.add(-1, -2) == -3);
        assert(adder.add(-1, 1) == 0);
        assert(adder.add(1234, 988) == 2222);
    }
}

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

assert(adder.add(a, b) == (a+b));

แต่นี่เป็นเพียงการเขียนโค้ดฟังก์ชันในการทดสอบ ใครสามารถให้ตัวอย่างกับการทดสอบหน่วยที่เป็นประโยชน์จริง ๆ FYI ฉันกำลังเข้ารหัสส่วนใหญ่ฟังก์ชั่น "ขั้นตอน" ที่ใช้ ~ 10 booleans และสองสาม ints และให้ฉันผล int ตามนี้ฉันรู้สึกเหมือนการทดสอบหน่วยเดียวที่ฉันสามารถทำได้คือเพียงแค่รหัสขั้นตอนวิธีใน ทดสอบ. แก้ไข: ฉันควรจะคิดว่านี่คือในขณะที่ย้าย (อาจออกแบบไม่ดี) รหัสทับทิมรหัส (ที่ฉันไม่ได้ทำ)


14
How does unit testing work?ไม่มีใครจริงๆรู้ :)
Yannis

30
"คุณต้องคำนวณผลลัพธ์ที่ต้องการด้วยตนเอง" นั่นคือ "ไร้ประโยชน์โดยสิ้นเชิง" อย่างไร คุณมั่นใจได้อย่างไรว่าคำตอบนั้นถูกต้อง?
S.Lott

9
@ S.Lott: มันเรียกว่าความคืบหน้าในสมัยโบราณผู้คนใช้คอมพิวเตอร์เพื่อหาตัวเลขและกระท่อนกระแท่นในยุคสมัยที่ผู้คนใช้เวลาเพื่อให้แน่ใจว่าคอมพิวเตอร์สามารถบีบอัดตัวเลขได้: D
Coder

2
@Coder: จุดประสงค์ของการทดสอบหน่วยไม่ใช่ "เพื่อเร่งตัวเลขและประหยัดเวลา";)
Andres F.

7
@lezebulon: ตัวอย่างจาก Wikipedia นั้นไม่ค่อยดีนัก แต่นั่นเป็นปัญหาของกรณีทดสอบเฉพาะนั้นไม่ใช่การทดสอบหน่วยโดยทั่วไป ข้อมูลการทดสอบประมาณครึ่งหนึ่งจากตัวอย่างไม่ได้เพิ่มอะไรใหม่ทำให้ซ้ำซ้อน (ฉันกลัวที่จะคิดว่าผู้เขียนการทดสอบนั้นจะทำอย่างไรกับสถานการณ์ที่ซับซ้อนมากขึ้น) การทดสอบที่มีความหมายมากกว่านี้จะแบ่งพาร์ติชันข้อมูลการทดสอบอย่างน้อยในสถานการณ์ต่อไปนี้: "สามารถเพิ่มจำนวนลบได้หรือไม่", "เป็นกลางหรือไม่?", "สามารถเพิ่มจำนวนลบและบวกได้หรือไม่"
Andres F.

คำตอบ:


26

การทดสอบหน่วยหากคุณกำลังทดสอบหน่วยที่มีขนาดเล็กพอจะยืนยันความเด่นชัดอยู่เสมอ

เหตุผลที่add(x, y)ได้รับการกล่าวถึงการทดสอบหน่วยเป็นเพราะบางครั้งในภายหลังใครบางคนจะเข้าไปaddและใส่รหัสการจัดการตรรกะภาษีพิเศษโดยไม่ทราบว่าการเพิ่มจะใช้ทุกที่

การทดสอบหน่วยเป็นเรื่องเกี่ยวกับหลักการเชื่อมโยง: ถ้า A ทำ B และ B ทำ C ดังนั้น A ก็จะทำ C "A ทำ C" เป็นการทดสอบระดับที่สูงขึ้น ตัวอย่างเช่นพิจารณารหัสธุรกิจที่ถูกต้องตามกฎหมายต่อไปนี้:

public void LoginUser (string username, string password) {
    var user = db.FetchUser (username);

    if (user.Password != password)
        throw new Exception ("invalid password");

    var roles = db.FetchRoles (user);

    if (! roles.Contains ("member"))
        throw new Exception ("not a member");

    Session["user"] = user;
}

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

public void LoginUser (string username, string password) {

    var user = _userRepo.FetchValidUser (username, password);

    _rolesRepo.CheckUserForRole (user, "member");

    _localStorage.StoreValue ("user", user);
}

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

class UserRepository : IUserRepository
{
    public User FetchValidUser (string username, string password)
    {
        var user = db.FetchUser (username);

        if (user.Password != password)
            throw new Exception ("invalid password");

        return user;
    }
}

class RoleRepository : IRoleRepository
{
    public void CheckUserForRole (User user, string role)
    {
        var roles = db.FetchRoles (user);

        if (! roles.Contains (role))
            throw new Exception ("not a member");
    }
}

class SessionStorage : ILocalStorage
{
    public void StoreValue (string key, object value)
    {
        Session[key] = value;
    }
}

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

หวังว่าจะช่วย :)


13

ขณะนี้ฉันกำลังเขียนฟังก์ชั่น "ขั้นตอน" ส่วนใหญ่ที่ใช้ ~ 10 booleans และสองสาม ints และให้ผล int ตามนี้ฉันรู้สึกเหมือนการทดสอบหน่วยเดียวที่ฉันสามารถทำได้คือเพียงแค่เขียนโค้ดอัลกอริทึมใหม่ในการทดสอบ

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


+1 สำหรับการเรียกใช้รหัสที่มีอยู่และบันทึกผลลัพธ์ ในสถานการณ์เช่นนี้นั่นอาจเป็นแนวทางปฏิบัติ
MarkJ

12

เนื่องจากดูเหมือนว่าไม่มีใครให้ตัวอย่างจริง:

    public void testRoman() {
        RomanNumeral numeral = new RomanNumeral();
        assert( numeral.toRoman(1) == "I" )
        assert( numeral.toRoman(4) == "IV" )
        assert( numeral.toRoman(5) == "V" )
        assert( numeral.toRoman(9) == "IX" )
        assert( numeral.toRoman(10) == "X" )
    }
    public void testSqrt() {
        assert( sqrt(4) == 2 )
        assert( sqrt(9) == 3 )
    }

คุณพูด:

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

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

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

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

FYI ฉันกำลังเข้ารหัสส่วนใหญ่ฟังก์ชั่น "ขั้นตอน" ที่ใช้ ~ 10 booleans และสองสาม ints และให้ฉันผล int ตามนี้ฉันรู้สึกเหมือนการทดสอบหน่วยเดียวที่ฉันสามารถทำได้คือเพียงแค่รหัสขั้นตอนวิธีใน ทดสอบ

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

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

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

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


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

11

ฉันรู้สึกว่าการทดสอบหน่วยเดียวที่ฉันทำได้ก็คือการเขียนโค้ดอัลกอริทึมใหม่ในการทดสอบ

คุณเกือบถูกต้องสำหรับชั้นเรียนที่เรียบง่ายเช่นนี้

ลองใช้กับเครื่องคิดเลขที่ซับซ้อนกว่า .. เช่นเครื่องคิดเลขคะแนนโบว์ลิ่ง

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

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


4
+1 เมื่อสังเกตว่ามีประโยชน์มากขึ้นสำหรับฟังก์ชั่นที่ซับซ้อน ถ้าคุณตัดสินใจขยาย adder.add () ไปเป็นค่าทศนิยม เมทริกซ์? ค่าบัญชี Leger?
joshin4colours

6

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

ขณะนี้ฉันกำลังเขียนฟังก์ชั่น "ขั้นตอน" ส่วนใหญ่ที่ใช้ ~ 10 booleans และสองสาม ints และให้ผล int ตามนี้

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

ฉันไม่จำเป็นต้องเข้าสู่ OO ของฉันดีกว่าการเขียนโปรแกรมตามขั้นตอน ...


ในกรณีนี้ "ลายเซ็น" ของวิธีการนั้นไม่ใหญ่มากฉันเพิ่งอ่านจาก std :: vector <bool> ที่เป็นสมาชิกของคลาส ฉันควรจะได้ precised ว่าฉัน porting (อาจจะออกแบบมาไม่ดี) รหัสทับทิม (ที่ผมไม่ได้ทำ)
lezebulon

2
@lezebulon โดยไม่คำนึงถึงหากมีปัจจัยการผลิตที่เป็นไปได้มากว่าวิธีการเดียวที่จะยอมรับแล้วว่าวิธีการจะทำมากเกินไป
maple_shaft

3

ในมุมมองของการทดสอบหน่วยของฉันมีประโยชน์แม้ในระดับเล็ก ๆ น้อย ๆ ของคุณ adder: อย่าคิดว่า "recoding" อัลกอริทึมและคิดว่ามันเป็นกล่องดำที่มีความรู้เพียงอย่างเดียวที่คุณมีคือพฤติกรรมการทำงาน (ถ้าคุณคุ้นเคย ด้วยการคูณที่รวดเร็วคุณจะรู้ได้เร็วขึ้น แต่ความพยายามที่ซับซ้อนกว่าการใช้ "a * b") และส่วนต่อประสานสาธารณะของคุณ กว่าที่คุณควรถามตัวเองว่า "อะไรจะเกิดความผิดพลาด" ...

ในกรณีส่วนใหญ่มันเกิดขึ้นที่เส้นขอบ (ฉันเห็นว่าคุณทดสอบแล้วเพิ่มรูปแบบเหล่านี้แล้ว ++, -, +,, 00 - เวลาที่จะเสร็จสมบูรณ์เหล่านี้โดย - +, 0+, 0-, +0, -0) นึกถึงสิ่งที่เกิดขึ้นที่ MAX_INT และ MIN_INT เมื่อเพิ่มหรือลบ (เพิ่มรายการเชิงลบ;)) ที่นั่น หรือพยายามทำให้แน่ใจว่าการทดสอบของคุณมีลักษณะเหมือนกันว่าเกิดอะไรขึ้นที่ศูนย์และรอบ ๆ

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

คำแนะนำสำหรับการทดสอบในชั้นเรียนของคุณ: พยายามที่จะเขียนเพียงหนึ่งยืนยันในวิธีการ ตั้งชื่อวิธีการที่ดี (เช่น "testAddingToMaxInt", "testAddingTwoNegatives") เพื่อให้ข้อเสนอแนะที่ดีที่สุดเมื่อการทดสอบของคุณล้มเหลวหลังจากการเปลี่ยนรหัส


2

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

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

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


2

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

I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be

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

Can someone provide me with an example where unit testing is actually useful?

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

void TestBusinessLayer()
{
   int employeeID = 1234
   Employee employee = Employee.GetEmployee(employeeID)
   BusinessLayer bl = new BusinessLayer()
   Assert.isTrue(bl.Add(employee))//assume Add returns true on pass
}

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

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

I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.

แม้แต่กระบวนการเชิงตรรกะก็สามารถห่อหุ้มภายในฟังก์ชันได้อย่างง่ายดาย Encapsulate, อินสแตนซ์และผ่านใน int / ดั้งเดิมที่จะทดสอบ (หรือวัตถุจำลอง) อย่าคัดลอกวางรหัสลงในการทดสอบหน่วย นั่นเอาชนะ DRY นอกจากนี้ยังเอาชนะการทดสอบทั้งหมดเพราะคุณไม่ได้ทดสอบรหัส แต่เป็นสำเนาของรหัส หากรหัสที่ควรได้รับการทดสอบการเปลี่ยนแปลงการทดสอบยังคงผ่าน!


<pedantry> "gamut" ไม่ใช่ "gambit" </
pedantry

@ chao lol เรียนรู้สิ่งใหม่ทุกวัน
P.Brian.Mackey

2

ยกตัวอย่างของคุณ (ด้วยการปรับโครงสร้างเล็กน้อย)

assert(a + b, math.add(a, b));

ไม่ช่วย:

  • เข้าใจวิธีการmath.addทำงานภายใน
  • รู้ว่าสิ่งที่จะเกิดขึ้นกับกรณีขอบ

มันค่อนข้างจะบอกว่า:

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

นี่หมายความว่าคุณไม่จำเป็นต้องเพิ่มการทดสอบเช่น:

assert(3, math.add(1, 2));
assert(4, math.add(2, 2));

พวกเขาไม่ได้ช่วยหรืออย่างน้อยเมื่อคุณยืนยันครั้งแรกแล้วข้อที่สองก็ไม่มีประโยชน์อะไรเลย

แต่จะทำอย่างไรกับ:

const numeric Pi = 3.1415926535897932384626433832795;
const numeric Expected = 4.1415926535897932384626433832795;
assert(Expected, math.add(Pi, 1),
    "Adding an integer to a long numeric doesn't give a long numeric result.");
assert(Expected, math.add(1, Pi),
    "Adding a long numeric to an integer doesn't give a long numeric result.");

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

Test TestNumeric() failed on assertion 2, line 5: Adding a long numeric to an
integer doesn't give a long numeric result.

Expected value: 4.1415926535897932384626433832795
Actual value: 4

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

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

ด้วยเหตุผลเดียวกันนี้การยืนยันสองข้อต่อไปนี้จึงสมเหตุสมผลในบางภาษา:

// We don't expect a concatenation. `math` library is not intended for this.
assert(0, math.add("Hello", "World"));

// We expect the method to convert every string as if it was a decimal.
assert(5, math.add("0x2F", 5));

นอกจากนี้สิ่งที่เกี่ยวกับ:

assert(numeric.Infinity, math.add(numeric.Infinity, 1));

อธิบายตนเองเช่นกัน: คุณต้องการให้วิธีการของคุณจัดการกับอินฟินิตี้ได้อย่างถูกต้อง การก้าวไปอย่างไร้ขอบเขตหรือการโยนข้อยกเว้นไม่ใช่พฤติกรรมที่คาดหวัง

หรืออาจขึ้นอยู่กับภาษาของคุณ

/**
 * Ensures that when adding numbers which exceed the maximum value, the method
 * fails with OverflowException, instead of restarting at numeric.Minimum + 1.
 */
TestOverflow()
{
    UnitTest.ExpectException(ofType(OverflowException));

    numeric result = math.add(numeric.Maximum, 1));

    UnitTest.Fail("The tested code succeeded, while an OverflowException was
        expected.");
}

1

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

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

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


1

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


0

บางทีคุณคิดว่าการเพิ่ม () ถูกนำไปใช้กับคำสั่ง ADD หากโปรแกรมเมอร์อาวุโสหรือวิศวกรฮาร์ดแวร์บางคนได้เพิ่มฟังก์ชั่น add () โดยใช้ ANDS / ORS / XORS, บิตกลับด้านและเลื่อนคุณอาจต้องการทดสอบหน่วยกับคำสั่ง ADD

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


0

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

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

นั่นคือสิ่งที่การทดสอบหน่วยของคุณเป็นของตนเอง

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

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