การทดสอบหน่วยเป็นโมฆะวิธี?


170

เป็นวิธีที่ดีที่สุดในการทดสอบหน่วยวิธีที่ไม่คืนอะไร? โดยเฉพาะใน c #

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


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

คำตอบ:


151

หากวิธีการใดไม่ส่งคืนสิ่งใดสิ่งหนึ่งสิ่งใดสิ่งหนึ่งดังต่อไปนี้

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

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

void DeductFromBalance( dAmount ) 

สามารถทดสอบได้โดยการตรวจสอบว่ายอดเงินที่โพสต์ข้อความนี้น้อยกว่าค่าเริ่มต้นโดย dAmount

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

void OnAccountDebit( dAmount )  // emails account holder with info

สามารถทดสอบได้โดยตรวจสอบว่ามีการส่งอีเมลหรือไม่

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

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

String [] สามารถตรวจสอบได้ง่ายโดยจัดทำวิธีแรกด้วยไฟล์ดัมมี่และสตริงที่คาดหวัง อันที่สองนั้นยุ่งยากเล็กน้อย .. คุณสามารถใช้ Mock (google หรือ search stackoverflow บน mocking frameworks) เพื่อเลียนแบบฐานข้อมูลหรือกดฐานข้อมูลจริงและตรวจสอบว่ามีการแทรกสตริงในตำแหน่งที่ถูกต้องหรือไม่ ตรวจสอบกระทู้นี้สำหรับหนังสือที่ดีบางเล่ม ... ฉันขอแนะนำการทดสอบหน่วยปฏิบัติจริงถ้าคุณกำลังอยู่ในภาวะวิกฤติ
ในรหัสมันจะถูกใช้เช่น

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );

1
เฮ้ gishu คำตอบที่ดี ตัวอย่างที่คุณให้ ... พวกเขาไม่ใช่การทดสอบการรวม ... และถ้าเป็นเช่นนั้นคำถามยังคงมีอยู่ใครจะทดสอบวิธีการที่เป็นโมฆะจริง ๆ ... เป็นไปไม่ได้อย่างไร?
andy

2
@andy - ขึ้นอยู่กับคำจำกัดความของ 'การทดสอบรวม' วิธีการที่จำเป็นมักจะเปลี่ยนสถานะดังนั้นคุณสามารถตรวจสอบได้โดยการทดสอบหน่วยซึ่งสอบถามสถานะของวัตถุ วิธีการให้ข้อมูลสามารถตรวจสอบได้โดยการทดสอบหน่วยที่ปลั๊กในฟังการเลียนแบบ / ผู้ทำงานร่วมกันเพื่อให้แน่ใจว่าเรื่องการทดสอบการแจ้งเตือนที่ถูกต้อง ฉันคิดว่าทั้งคู่สามารถทดสอบได้อย่างสมเหตุสมผลผ่านการทดสอบหน่วย
Gishu

@ ฐานข้อมูลสามารถจำลอง / แยกโดยอินเทอร์เฟซ accessor ดังนั้นจึงอนุญาตให้ทำการทดสอบโดยข้อมูลที่ส่งผ่านไปยังวัตถุจำลอง
Peter Geiger

62

ทดสอบผลข้างเคียงของมัน รวมถึง:

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

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


31

เช่นเคย: ทดสอบสิ่งที่ควรทำ!

มันควรจะเปลี่ยนสถานะโลก (uuh รหัสกลิ่น!) ที่ไหนสักแห่ง?

มันควรจะโทรเข้าไปในอินเตอร์เฟสหรือไม่?

มันควรจะเกิดข้อยกเว้นเมื่อเรียกด้วยพารามิเตอร์ที่ไม่ถูกต้องหรือไม่?

มันควรจะไม่มีข้อยกเว้นเมื่อเรียกด้วยพารามิเตอร์ที่ถูกต้องหรือไม่?

ควรเป็น ...?


11

โมฆะประเภทผลตอบแทน / รูทีนย่อยเป็นข่าวเก่า ฉันไม่ได้ทำ Void return type (ยกเว้นว่าฉันขี้เกียจมาก) ใน 8 ปี (ตั้งแต่เวลาที่ตอบคำถามนี้ดังนั้นก่อนที่จะถามคำถามนี้สักหน่อย)

แทนที่จะเป็นวิธีการเช่น:

public void SendEmailToCustomer()

สร้างวิธีการที่เป็นไปตามกระบวนทัศน์ int.TryParse () ของ Microsoft:

public bool TrySendEmailToCustomer()

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

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

public StateEnum TrySendEmailToCustomer()

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

หากคุณกำลังทำ data access stuff ใน method-in-question คุณต้อง refactor ในสถาปัตยกรรม n-tier'd หรือ n-layer'd แต่เราสามารถสันนิษฐานได้ว่าเมื่อคุณพูดว่า "สตริงนั้นถูกแทรกลงในฐานข้อมูล" คุณหมายถึงว่าคุณกำลังเรียกเลเยอร์ตรรกะทางธุรกิจหรือบางอย่าง ใช่เราจะสมมติว่า

เมื่อวัตถุของคุณเป็นอินสแตนซ์ตอนนี้คุณเข้าใจว่าวัตถุของคุณมีการอ้างอิง นี่คือเมื่อคุณจำเป็นต้องตัดสินใจว่าคุณจะทำการพึ่งพาการฉีดบนวัตถุหรือวิธีการ นั่นหมายถึง Constructor ของคุณหรือ method-in-question ต้องการพารามิเตอร์ใหม่:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

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

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

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

  1. สถานะของ "รูทีนย่อย" ซึ่งขณะนี้เป็นวิธีลองกระบวนทัศน์
  2. สถานะของIBusinessDataEtcวัตถุจำลองของคุณ

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


อันแรกของคำตอบที่ยอดเยี่ยมนี้ทำหน้าที่เป็นคำแนะนำทั่วไปที่น่าอัศจรรย์สำหรับโปรแกรมเมอร์มือใหม่ / ผู้ที่มีความรู้ระดับกลาง
pimbrouwers

มันไม่ควรเป็นแบบนี้ public void sendEmailToCustomer() throws UndeliveredMailExceptionเหรอ?
A.Emad

1
@ A.Emad เป็นคำถามที่ดี แต่ไม่ใช่ การควบคุมการไหลของรหัสของคุณโดยอาศัยข้อยกเว้นการขว้างปาเป็นวิธีปฏิบัติที่ไม่ดีซึ่งรู้จักกันมานาน แม้ว่าในvoidวิธีการโดยเฉพาะอย่างยิ่งในภาษาเชิงวัตถุมันเป็นตัวเลือกที่แท้จริงเท่านั้น ทางเลือกที่เก่าแก่ที่สุดของ Microsoft คือ Try-Paradigm I discuss และกระบวนทัศน์แบบ functional เช่น Monads / Maybes ดังนั้นคำสั่ง (ใน CQS) ยังสามารถส่งคืนข้อมูลสถานะที่มีค่าแทนการใช้การโยนซึ่งคล้ายกับGOTO(ซึ่งเรารู้ว่าไม่ดี) การขว้าง (และกลับไป) ช้า, ยากต่อการดีบั๊กและไม่ใช่วิธีการที่ดี
Suamere

ขอบคุณสำหรับการล้างข้อมูลนี้ เป็นภาษา C # หรือเป็นวิธีปฏิบัติที่ไม่ดีโดยทั่วไปในการโยนข้อยกเว้นแม้ในภาษาอย่าง Java และ C ++?
A.Emad

9

ลองสิ่งนี้:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}

1
สิ่งนี้ไม่จำเป็น แต่สามารถทำได้เช่นกัน
Nathan Alard

ยินดีต้อนรับสู่ StackOverflow! โปรดลองเพิ่มคำอธิบายลงในรหัสของคุณ ขอบคุณ.
Aurasphere

2
ExpectedAttributeถูกออกแบบเพื่อให้การทดสอบนี้ได้ชัดเจนมากขึ้น
Martin Liversage

8

คุณสามารถลองด้วยวิธีนี้:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}

1
นี่เป็นวิธีที่ง่ายที่สุดที่ฉันคิด
Navin Pandit

5

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


4

น่าจะเป็นวิธีการที่ทำอะไรบางอย่างและไม่เพียงแค่กลับมา?

สมมติว่าเป็นกรณีนี้จากนั้น:

  1. หากมันแก้ไขสถานะของวัตถุเจ้าของคุณควรทดสอบว่าสถานะมีการเปลี่ยนแปลงอย่างถูกต้อง
  2. หากใช้ในวัตถุบางอย่างเป็นพารามิเตอร์และปรับเปลี่ยนวัตถุนั้นคุณควรทดสอบวัตถุที่มีการแก้ไขอย่างถูกต้อง
  3. หากมีการโยนข้อยกเว้นเป็นบางกรณีทดสอบว่าข้อยกเว้นเหล่านั้นถูกโยนอย่างถูกต้อง
  4. หากพฤติกรรมของมันแตกต่างกันไปตามสถานะของวัตถุของตัวเองหรือวัตถุอื่น ๆ ให้ตั้งค่าสถานะและทดสอบวิธีที่มี Ithrough ที่ถูกต้องหนึ่งในสามวิธีการทดสอบด้านบน)

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


3

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


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

2

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


เห็นด้วย - การตรวจสอบพฤติกรรมของการทดสอบ mocks จะเป็นวิธีหนึ่ง
Jeff Schumacher

0

อินสแตนซ์ใดที่คุณใช้ในการเรียกใช้วิธีการโมฆะคุณสามารถใช้Verfiy

ตัวอย่างเช่น:

ในกรณีของฉันมัน_Logเป็นตัวอย่างและLogMessageเป็นวิธีที่จะทดสอบ:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

เป็นVerifyพ่นยกเว้นเนื่องจากความล้มเหลวของวิธีการทดสอบจะล้มเหลว?

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