วิธีเขียนการทดสอบหน่วยก่อนทำการเปลี่ยนโครงสร้างใหม่?


55

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

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

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

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

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

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

นี่เป็นตัวอย่างสั้น ๆ

MyDocumentService.java (ปัจจุบัน)

public class MyDocumentService {
   ...
   public List<Document> findAllDocuments() {
      DataResultSet rs = documentDAO.findAllDocuments();
      List<Document> documents = new ArrayList<>();
      for(DataObject do: rs.getRows()) {
         //get row data create new document add it to 
         //documents list
      }

      return documents;
   }
}

MyDocumentService.java (ปรับปรุงใหม่ / ออกแบบใหม่ทุกอย่าง)

public class MyDocumentService {
   ...
   public List<Document> findAllDocuments() {
      //Code dealing with DataResultSet moved back up to DAO
      //DAO now returns a List<Document> instead of a DataResultSet
      return documentDAO.findAllDocuments();
   }
}

14
คือจริงๆมันrefactoringคุณวางแผนที่จะทำหรือออกแบบ ? เพราะคำตอบอาจแตกต่างกันในสองกรณี
herby

4
ฉันกำลังดำเนินการเกี่ยวกับคำจำกัดความ "ด้วยการรีฟอร์เรชั่นตามนิยามคุณจะไม่เปลี่ยนแปลงสิ่งที่ซอฟต์แวร์ของคุณทำ ดังนั้นฉันเชื่อในกรณีนี้มันคือ refactoring อย่าลังเลที่จะแก้ไขความเข้าใจคำศัพท์
PDStat

21
ไม่เขียนการทดสอบการรวม "การปรับโครงสร้างใหม่" ที่คุณกำลังวางแผนนั้นสูงกว่าระดับการทดสอบหน่วย หน่วยทดสอบเฉพาะคลาสใหม่ (หรือคลาสเก่าที่คุณรู้ว่าคุณเก็บไว้)
OrangeDog

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

4
คุณมีแนวโน้มที่จะต้องทำการปรับเปลี่ยนเล็กน้อยโดยไม่ต้องมีการทดสอบความปลอดภัยเพื่อที่จะได้รับการทดสอบ คำแนะนำที่ดีที่สุดที่ฉันสามารถให้คุณได้คือหาก IDE หรือเครื่องมือการเปลี่ยนโครงสร้างของคุณจะไม่ทำเพื่อคุณอย่าทำด้วยมือ ปรับใช้การรีแฟคเตอร์อัตโนมัติต่อไปจนกว่าคุณจะได้ CUT เป็นสายรัด แน่นอนว่าคุณต้องการรับสำเนาของ Michael Feather "การทำงานอย่างมีประสิทธิภาพด้วยรหัสมรดก"
RubberDuck

คำตอบ:


56

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

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

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


1
กล่าวคือคุณน่าจะทำการรวมหรือทดสอบระบบมากกว่าการทดสอบหน่วย คุณอาจยังคงใช้เครื่องมือ "การทดสอบหน่วย" แต่คุณจะต้องกดรหัสมากกว่าหนึ่งหน่วยในการทดสอบแต่ละครั้ง

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

40

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

มันสมเหตุสมผลที่สุดสำหรับการทดสอบ pin-down เหล่านี้ให้อยู่ในระดับสูงเช่นการรวมเข้าด้วยกันไม่ใช่การทดสอบหน่วย

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


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

ฉันชอบการทดสอบวลี 'ขาลง'
Brian Agnew

12

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

[.. ] ปัญหาที่ฉันเห็นก็คือเมื่อฉันทำ refactor แล้วการทดสอบเหล่านั้นจะแตกเมื่อฉันเปลี่ยนตรรกะที่ทำบางอย่างและการทดสอบจะถูกเขียนด้วยโครงสร้างก่อนหน้าในใจ (ล้อเลียนอ้างอิง ฯลฯ ) [ .. ]

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

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


1
นี้. การทดสอบจะดูน่ากลัวแต่จะครอบคลุมถึงพฤติกรรมที่มีอยู่ จากนั้นเมื่อโค้ดได้รับการ refactored ดังนั้นทำการทดสอบในขั้นตอนล็อค ทำซ้ำจนกว่าคุณจะมีสิ่งที่คุณภูมิใจ ++
RubberDuck

1
ฉันขอแนะนำหนังสือทั้งสองเล่มนี้ - ฉันมีคำแนะนำที่ใกล้เคียงกันเสมอเมื่อฉันต้องจัดการกับรหัสที่ไม่มีการทดสอบ
Toby Speight

5

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

ลองดูตัวอย่างของคุณ:

public class MyDocumentService {
   ...
   public List<Document> findAllDocuments() {
      DataResultSet rs = documentDAO.findAllDocuments();
      List<Document> documents = new ArrayList<>();
      for(DataObject do: rs.getRows()) {
         //get row data create new document add it to 
         //documents list
      }

      return documents;
   }
}

การทดสอบของคุณอาจมีลักษณะเช่นนี้:

DocumentDao documentDao = Mock.create(DocumentDao.class);
Mock.when(documentDao.findAllDocuments())
    .thenReturn(DataResultSet.create(...))
assertEquals(..., new MyDocumentService(documentDao).findAllDocuments());

แทนการเยาะเย้ย DocumentDao เยาะเย้ยการพึ่งพา:

DocumentDao documentDao = new DocumentDao(db);
Mock.when(db...)
    .thenReturn(...)
assertEquals(..., new MyDocumentService(documentDao).findAllDocuments());

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


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

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

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

3

อย่างที่คุณพูดถ้าคุณเปลี่ยนพฤติกรรมแล้วมันเป็นการเปลี่ยนแปลงไม่ใช่การเปลี่ยนรูป ในระดับใดคุณเปลี่ยนพฤติกรรมเป็นสิ่งที่สร้างความแตกต่าง

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

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

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


3

นี่คือแนวทางของฉัน มีค่าใช้จ่ายในแง่ของเวลาเนื่องจากเป็นการทดสอบการปรับโครงสร้างใหม่ใน 4 ขั้นตอน

สิ่งที่ฉันจะเปิดเผยอาจจะประกอบได้ดีกว่าในองค์ประกอบที่มีความซับซ้อนมากกว่าสิ่งที่ปรากฏในตัวอย่างของคำถาม

อย่างไรก็ตามกลยุทธ์นั้นใช้ได้สำหรับผู้สมัครองค์ประกอบใด ๆ ที่จะทำให้เป็นมาตรฐานโดยอินเทอร์เฟซ (DAO, Services, Controllers, ... )

1. ส่วนต่อประสาน

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

public interface DocumentService {

   List<Document> getAllDocuments();

   //more methods here...
}

จากนั้นเราบังคับให้MyDocumentServiceใช้อินเทอร์เฟซใหม่นี้

จนถึงตอนนี้ดีมาก ไม่มีการเปลี่ยนแปลงที่สำคัญเราเคารพสัญญาปัจจุบันและ behaivos ยังคงไม่ถูกแตะต้อง

public class MyDocumentService implements DocumentService {

 @Override
 public List<Document> getAllDocuments(){
         //legacy code here as it is.
        // with no changes ...
  }
}

2. การทดสอบหน่วยของรหัสดั้งเดิม

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

ตอนนี้แทนที่จะทดสอบMyDocumentServiceเราจะใช้ส่วนต่อประสานเป็นสัญญาที่จะทำการทดสอบ

ฉันจะไม่ลงรายละเอียดดังนั้นโปรดยกโทษให้ฉันหากรหัสของฉันดูง่ายเกินไปหรือไม่เชื่อเรื่องพระเจ้ามากเกินไป

public class DocumentServiceTestSuite {

   @Mock
   MyDependencyA mockDepA;

   @Mock
   MyDependencyB mockDepB;

    //... More mocks

   DocumentService service;

  @Before
   public void initService(){
       service = MyDocumentService(mockDepA, mockDepB);
      //this is purposed way to inject 
      //dependencies. Replace it with one you like more.  
   }

   @Test
   public void getAllDocumentsOK(){
         // here I mock depA and depB
         // wanted behaivors...

         List<Document> result = service.getAllDocuments();

          Assert.assertX(result);
          Assert.assertY(result);
           //... As many you think appropiate
    } 
 }

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

หมายเหตุ: เนื่องจากไม่มีการเปลี่ยนแปลงที่สำคัญเกิดขึ้นและ behaivor ยังคงไม่ถูกแตะต้อง ฉันแนะนำให้ทำแท็กที่นี่ใน SCM แท็กหรือสาขาไม่สำคัญ เพียงแค่ทำรุ่น

เราต้องการสำหรับการย้อนกลับการเปรียบเทียบรุ่นและอาจเป็นการประมวลผลแบบขนานของรหัสเก่าและรหัสใหม่

3. การปรับโครงสร้างใหม่

Refactor กำลังจะถูกนำไปใช้กับส่วนประกอบใหม่ เราจะไม่ทำการเปลี่ยนแปลงใด ๆ กับรหัสที่มีอยู่ ขั้นตอนแรกนั้นง่ายเหมือนการคัดลอกและวางMyDocumentServiceและเปลี่ยนชื่อเป็นCustomDocumentService (ตัวอย่าง)

คลาสใหม่ใช้DocumentServiceต่อไป จากนั้นไปปรับโครงสร้างgetAllDocuments ()อีกครั้ง (ให้เริ่มทีละหนึ่ง Pin-refactors)

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

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

public class CustomDocumentService implements DocumentService {

 @Override
 public List<Document> getAllDocuments(){
         //new code here ...
         //due to im refactoring service 
         //I do the less changes possible on its dependencies (DAO).
         //these changes will come later 
         //and they will have their own tests
  }
 }

4. การอัปเดต DocumentServiceTestSuite

ตกลงตอนนี้ส่วนที่ง่ายขึ้น เพื่อเพิ่มการทดสอบขององค์ประกอบใหม่

public class DocumentServiceTestSuite {

   @Mock
   MyDependencyA mockDepA;

   @Mock
   MyDependencyB mockDepB;

   DocumentService service;
   DocumentService customService;

  @Before
   public void initService(){
       service = MyDocumentService(mockDepA, mockDepB);
        customService = CustomDocumentService(mockDepA, mockDepB);
       // this is purposed way to inject 
       //dependencies. Replace it with the one you like more
   }

   @Test
   public void getAllDocumentsOK(){
         // here I mock depA and depB
         // wanted behaivors...

         List<Document> oldResult = service.getAllDocuments();

          Assert.assertX(oldResult);
          Assert.assertY(oldResult);
           //... As many you think appropiate

          List<Document> newResult = customService.getAllDocuments();

          Assert.assertX(newResult);
          Assert.assertY(newResult);
           //... The very same made to oldResult

          //this is optional
Assert.assertEquals(oldResult,newResult);
    } 
 }

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

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

หมายเหตุ

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

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

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

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

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


3

tl; drอย่าเขียนการทดสอบหน่วย เขียนข้อสอบในระดับที่เหมาะสมยิ่งขึ้น


กำหนดนิยามการทำงานของการ refactoring ของคุณ:

คุณไม่เปลี่ยนแปลงสิ่งที่ซอฟต์แวร์ทำคุณเปลี่ยนวิธีใช้ซอฟต์แวร์

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

ไม่ว่าจะมีการดำเนินการ refactoring / redesign ระดับใดก็ตามสิ่งสำคัญคือต้องมีการทดสอบที่ทำงานในระดับนั้นหรือสูงกว่า

การทดสอบอัตโนมัติมักจำแนกตามระดับดังนี้:

  • การทดสอบหน่วย - องค์ประกอบส่วนบุคคล (คลาส, วิธีการ)

  • การทดสอบบูรณาการ - การมีปฏิสัมพันธ์ระหว่างส่วนประกอบ

  • การทดสอบระบบ - แอปพลิเคชันที่สมบูรณ์

เขียนระดับการทดสอบที่สามารถทนต่อการปรับโครงสร้างซ้ำได้โดยไม่ถูกแตะต้อง

คิด:

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


2

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

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

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


สนใจเหตุผลของการโหวตลงเสมอ!
topo morto

1

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

[TestMethod]
public void simple_addition()
{
    Assert.AreEqual(7, Eval("3 + 4"));
}

[TestMethod]
public void order_of_operations()
{
    Assert.AreEqual(52, Eval("2 + 5 * 10"));
}

[TestMethod]
public void absolute_value()
{
    Assert.AreEqual(9, Eval("abs(-9)"));
    Assert.AreEqual(5, Eval("abs(5)"));
    Assert.AreEqual(0, Eval("abs(0)"));
}

static object Eval(string expression)
{
    // This is the code under test.
    // I can refactor this as much as I want without changing the tests.
    var settings = new EvaluatorSettings();
    Evaluator.Settings = settings;
    Evaluator.Evaluate(expression);
    return Evaluator.LastResult;
}

การทดสอบนั้นดี แต่รหัสภายใต้การทดสอบมี API ที่ไม่ดี ฉันสามารถ refactor ได้โดยไม่ต้องเปลี่ยนการทดสอบเพียงแค่อัปเดตเลเยอร์อะแดปเตอร์ของฉัน:

static object Eval(string expression)
{
    // After refactoring...
    var settings = new EvaluatorSettings();
    var evaluator = new Evaluator(settings);
    return evaluator.Evaluate(expression);
}

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

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

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