ทดสอบหน่วยวิธีส่วนตัวใน c ++ โดยใช้คลาสเพื่อน


15

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

1) สร้างคลาสเพื่อนที่เป็นคลาสที่ฉันต้องการทดสอบ

2) ในคลาสเพื่อนสร้างวิธีสาธารณะที่เรียกวิธีส่วนตัวของคลาสที่ทดสอบ

3) ทดสอบวิธีสาธารณะของเพื่อนชั้น

นี่เป็นตัวอย่างง่ายๆที่แสดงให้เห็นถึงขั้นตอนข้างต้น:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

แก้ไข:

ฉันเห็นว่าในการอภิปรายต่อไปนี้หนึ่งในคำตอบที่ผู้คนสงสัยเกี่ยวกับรหัสฐานของฉัน

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

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


5
ต่อไปนี้เป็นคำแนะนำที่น่าสงสัยสำหรับคำถามที่สงสัย ฉันไม่ชอบการมีเพศสัมพันธ์ของเพื่อนเพราะตอนนี้รหัสที่วางจำหน่ายต้องรู้เกี่ยวกับการทดสอบ คำตอบของ Nir ด้านล่างเป็นวิธีหนึ่งในการบรรเทาปัญหานี้ แต่ฉันยังไม่ชอบเปลี่ยนชั้นเรียนเพื่อให้สอดคล้องกับแบบทดสอบ เนื่องจากฉันไม่ได้พึ่งพาการสืบทอดบ่อยครั้งบางครั้งฉันเพียงแค่ทำให้วิธีการส่วนตัวได้รับการคุ้มครองและมีชั้นทดสอบและสืบทอดตามที่ต้องการ ฉันคาดหวังอย่างน้อยสาม "boo hisses" สำหรับความคิดเห็นนี้ แต่ความจริงคือ API สาธารณะและ API ทดสอบอาจแตกต่างกันและยังคงแตกต่างจาก API ส่วนตัว Meh
J Trana

4
@JTrana: ทำไมไม่เขียนมันขึ้นมาเป็นคำตอบที่เหมาะสม?
Bart van Ingen Schenau

ตกลง. นี่ไม่ใช่หนึ่งในสิ่งที่คุณรู้สึกภูมิใจเป็นพิเศษ แต่หวังว่ามันจะช่วยได้
J Trana

คำตอบ:


23

อีกทางเลือกหนึ่งสำหรับเพื่อน (ในแง่หนึ่ง) ที่ฉันใช้บ่อย ๆ เป็นรูปแบบที่ฉันได้รู้จักในชื่อ access_by มันง่ายมาก:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

ทีนี้สมมติว่าคลาส B มีส่วนร่วมในการทดสอบ A. คุณสามารถเขียนสิ่งนี้:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

จากนั้นคุณสามารถใช้ความเชี่ยวชาญพิเศษนี้ของ access_by เพื่อเรียกใช้เมธอดส่วนตัวของ A. โดยทั่วไปสิ่งนี้จะทำให้ความรับผิดชอบของการประกาศมิตรภาพลงในไฟล์ส่วนหัวของชั้นเรียนที่ต้องการเรียกวิธีการส่วนตัวของ A นอกจากนี้ยังช่วยให้คุณเพิ่มเพื่อนใน A โดยไม่ต้องเปลี่ยนแหล่งที่มาของ A ตามปรกติมันยังบ่งบอกว่าใครก็ตามที่อ่านแหล่งที่มาของ A ที่ไม่ได้ระบุว่า B เป็นเพื่อนแท้ในแง่ของการขยายส่วนต่อประสาน แต่อินเทอร์เฟซของ A เสร็จสมบูรณ์ตามที่กำหนดและ B ต้องการการเข้าถึงพิเศษใน A (การทดสอบเป็นตัวอย่างที่ดีฉันยังใช้รูปแบบนี้เมื่อใช้การเพิ่มการผูก python บางครั้งฟังก์ชันที่ต้องเป็นส่วนตัวใน C ++ นั้นสะดวก เปิดเผยในเลเยอร์หลามสำหรับการนำไปใช้)


อยากรู้ว่ากรณีการใช้ที่ถูกต้องสำหรับการทำfriend access_byไม่ใช่คนแรกที่ไม่เป็นเพื่อน - การเป็นโครงสร้างที่ซ้อนกันมันจะสามารถเข้าถึงทุกสิ่งภายใน A หรือไม่? เช่น. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
อัมพิล

10

ถ้ามันยากที่จะทดสอบมันเขียนไม่ดี

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

แตกเมธอดไพรเวตที่คุณต้องการทดสอบเป็นคลาสใหม่ (หรือคลาส) และทำให้เป็นแบบสาธารณะ ทดสอบคลาสใหม่

นอกเหนือจากการทำให้โค้ดทดสอบง่ายขึ้นการเปลี่ยนใหม่นี้จะทำให้โค้ดเข้าใจและบำรุงรักษาได้ง่ายขึ้น


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

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

1
@Nir: ทำสิ่งเล็กน้อย: แยกชั้นเรียนด้วยวิธีการเหล่านั้นทั้งหมดเป็นสาธารณะและทำให้ชั้นที่มีอยู่ของคุณเป็นอาคารรอบชั้นใหม่
วินไคลน์

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

4

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

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


6
จุดของวิธีการส่วนตัวคือการทำให้การพัฒนาง่ายขึ้น (โดยแยกความกังวลหรือรักษา DRY หรือสิ่งต่าง ๆ จำนวนมาก) แต่มีไว้เพื่อไม่ถาวร พวกเขาเป็นส่วนตัวด้วยเหตุผลนั้น พวกเขาอาจปรากฏหายไปหรือเปลี่ยนแปลงการใช้งานอย่างมากจากการใช้งานครั้งหนึ่งไปสู่การปฏิบัติครั้งต่อไปซึ่งเป็นสาเหตุที่การผูกไว้กับการทดสอบหน่วยไม่ได้ใช้งานได้จริงหรือแม้แต่มีประโยชน์
Ampt

8
มันอาจจะไม่เป็นประโยชน์หรือมีประโยชน์เสมอไป แต่มันเป็นหนทางที่ไกลเกินกว่าจะบอกได้ว่าคุณไม่ควรทำการทดสอบ คุณกำลังพูดถึงวิธีการส่วนตัวราวกับว่าพวกเขาเป็นวิธีการส่วนตัวของคนอื่น "พวกมันอาจปรากฏหายไป ... " ไม่พวกเขาทำไม่ได้ หากคุณเป็นหน่วยทดสอบพวกเขาโดยตรงก็ควรจะเป็นเพราะคุณรักษาด้วยตนเอง ถ้าคุณเปลี่ยนการใช้งานคุณเปลี่ยนการทดสอบ ในระยะสั้นคำสั่งแบบครอบคลุมของคุณจะไม่เป็นธรรม ในขณะที่มันเป็นการดีที่จะเตือน OP เกี่ยวกับเรื่องนี้คำถามของเขายังคงเป็นธรรมและคำตอบของคุณไม่ได้ตอบ
Nir Friedman

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

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

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

3

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

  • คุณสามารถเพิ่มการประกาศของเพื่อน (คลาสหรือฟังก์ชัน) ลงในคลาสที่ทดสอบได้

  • คุณสามารถเพิ่ม#define private publicไปยังจุดเริ่มต้นของไฟล์ทดสอบของคุณก่อนที่จะ#includeทำการทดสอบโค้ด ในกรณีที่รหัสที่ทดสอบนั้นเป็นไลบรารีที่คอมไพล์แล้ว แต่อาจทำให้ส่วนหัวไม่ตรงกับโค้ดไบนารีที่คอมไพล์แล้วอีกต่อไป (และทำให้ UB)

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


2

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

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

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


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

0

คุณสามารถเขียนรหัสด้วยวิธีแก้ปัญหามากมายเพื่อหยุดการใช้เพื่อน

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

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

คุณต้องชั่งน้ำหนักว่ามันจะคุ้มหรือไม่ และจำนวนมากขึ้นอยู่กับว่าใครจะเห็นส่วนหัวของคุณ

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

การทดสอบเป็นหน้าที่ของนักเขียนของไลบรารีไม่ใช่ผู้ใช้

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

หากผู้ใช้ในชั้นเรียนของคุณอยู่ใน บริษัท หรือทีมของคุณเองคุณสามารถผ่อนคลายมากขึ้นเกี่ยวกับกลยุทธ์นั้นโดยสมมติว่าเป็นมาตรฐานการเข้ารหัสของ บริษัท ของคุณ

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