วิธีการแบบค่อยเป็นค่อยไปในการฉีดพึ่งพา


12

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

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

public MyObject createMyObject(args) {
  return new MyObject(args);
}

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

นี่เป็นวิธีที่ดีหรือไม่? มีข้อเสียหรือไม่?

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

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


คุณช่วยอธิบายได้ไหมว่าทำไมพวกเขาถึงไม่สามารถทำการทดสอบได้ในตอนนี้?
Austin Salonen

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

คำตอบ:


13

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

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

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


เพียงแค่พูดถึงเรื่องนี้ "เมื่อคุณมีการทดสอบคุณสามารถ refactor ได้อย่างอิสระมากขึ้น" - ถ้าคุณไม่มีการทดสอบคุณจะไม่ refactoring คุณเพียงแค่เปลี่ยนรหัส
ocodo

@Slomojo ฉันเห็นจุดของคุณ แต่คุณยังสามารถทำการปรับเปลี่ยนได้อย่างปลอดภัยโดยไม่ต้องทดสอบโดยเฉพาะอย่างยิ่ง IDE ractorings อัตโนมัติเช่นเช่น (ใน eclipse สำหรับ Java) การแยกรหัสที่ซ้ำกันเป็นวิธีการเปลี่ยนชื่อทุกอย่างย้ายชั้นเรียนและแยกฟิลด์ค่าคงที่ ฯลฯ
Alb

ฉันแค่อ้างถึงต้นกำเนิดของคำว่า Refactoring ซึ่งกำลังแก้ไขโค้ดในรูปแบบที่ปรับปรุงซึ่งปลอดภัยจากข้อผิดพลาดในการถดถอยโดยกรอบการทดสอบ แน่นอนว่าการปรับเปลี่ยนตาม IDE ทำให้ซอฟต์แวร์ของ 'refactor' มีความสำคัญมากขึ้น แต่ฉันมักจะหลีกเลี่ยงการหลงตัวเองถ้าซอฟต์แวร์ของฉันไม่มีการทดสอบและฉัน "refactor" ฉันแค่เปลี่ยนรหัส แน่นอนว่านั่นอาจไม่ใช่สิ่งที่ฉันบอกคนอื่น)
ocodo

1
@ อัลการรีแฟคเตอร์โดยไม่มีการทดสอบมักมีความเสี่ยงมากกว่า refactorings อัตโนมัติช่วยลดความเสี่ยง แต่ไม่เป็นศูนย์ มีตัวเรือนขอบที่ยุ่งยากซึ่งเครื่องมืออาจไม่สามารถจัดการได้อย่างถูกต้อง ในกรณีที่ดีกว่าผลลัพธ์คือข้อความแสดงข้อผิดพลาดจากเครื่องมือ แต่ในกรณีที่แย่ที่สุดมันอาจแนะนำข้อบกพร่องเล็กน้อย ดังนั้นขอแนะนำให้คุณทำการเปลี่ยนแปลงที่ง่ายที่สุดและปลอดภัยที่สุดแม้จะใช้เครื่องมืออัตโนมัติ
PéterTörök

3

Guice-folks แนะนำให้ใช้

@Inject
public void setX(.... X x) {
   this.x = x;
}

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


3

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

ดังนั้นคลาสที่ทดสอบได้ของหน่วยใหม่ของฉันจะมีตัวสร้าง 2 ตัว:

public MyClass() { 
  this(new Client1(), new Client2()); 
}

public MyClass(Client1 client1, Client2 client2) { 
  this.client1 = client1; 
  this.client2 = client2; 
}

2

คุณอาจต้องการพิจารณาสำนวน "getter สร้าง" จนกว่าคุณจะสามารถใช้การพึ่งพาการฉีด

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

public class Example {
  private MyObject myObject=null;

  public MyObject getMyObject() {
    if (myObject==null) {
      myObject=new MyObject();
    } 
    return myObject;
  }

  public void setMyObject(MyObject myObject) {
    this.myObject=myObject;
  }

  private void myMethod() {
    if (getMyObject().doSomething()) {
      // Instance automatically created
    }
  }
}

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

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

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


ขอบคุณ ฉันกำลังพิจารณาวิธีการนี้ในบางกรณี แต่คุณจะทำอย่างไรเมื่อตัวสร้างของ MyObject ต้องการพารามิเตอร์ที่ใช้ได้เฉพาะจากภายในคลาสของคุณ หรือเมื่อคุณต้องการสร้างมากกว่าหนึ่ง MyObject ในช่วงชีวิตของชั้นเรียนของคุณ? คุณต้องฉีด MyObjectFactory หรือไม่?
JW01

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

นั่นเป็นซิงเกิล อย่าใช้ซิงเกิล O_O
CoffeDeveloper

@DarioOO จริงๆแล้วมันเป็นซิงเกิลที่แย่มาก ๆ การใช้งานที่ดีขึ้นจะใช้ Enum ตาม Josh Bloch Singletons เป็นรูปแบบการออกแบบที่ถูกต้องและมีประโยชน์ (การสร้างแบบครั้งเดียวที่มีราคาแพงเป็นต้น)
Gary Rowe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.