ฉันกำลังสร้างห้องสมุดชั้นเรียนที่จะมีวิธีการของรัฐและเอกชน ฉันต้องการที่จะทดสอบหน่วยวิธีการส่วนตัว (ส่วนใหญ่ในขณะที่การพัฒนา แต่ยังเป็นประโยชน์สำหรับการปรับโครงสร้างในอนาคต)
วิธีที่ถูกต้องในการทำเช่นนี้คืออะไร?
ฉันกำลังสร้างห้องสมุดชั้นเรียนที่จะมีวิธีการของรัฐและเอกชน ฉันต้องการที่จะทดสอบหน่วยวิธีการส่วนตัว (ส่วนใหญ่ในขณะที่การพัฒนา แต่ยังเป็นประโยชน์สำหรับการปรับโครงสร้างในอนาคต)
วิธีที่ถูกต้องในการทำเช่นนี้คืออะไร?
คำตอบ:
#if DEBUG
รอบInternalsVisibleTo
แอตทริบิวต์ที่จะทำให้มันไม่ได้นำไปใช้กับรหัสการเปิดตัว?
#if RELEASE_TEST
ไปรอบ ๆเหมือนไมค์แสดงให้เห็นและทำสำเนาของการสร้างการกำหนดค่าการปล่อยของคุณที่กำหนดInternalsVisibleTo
RELEASE_TEST
คุณจะได้รับการทดสอบรหัสการปล่อยด้วยการปรับให้เหมาะสม แต่เมื่อคุณสร้างเพื่อการปล่อยจริงการทดสอบของคุณจะถูกตัดออก
หากคุณต้องการทดสอบหน่วยวิธีส่วนตัวอาจมีบางอย่างผิดปกติ การทดสอบหน่วยเป็น (โดยทั่วไปการพูด) หมายถึงการทดสอบส่วนต่อประสานของชั้นเรียนหมายถึงวิธีสาธารณะ (และได้รับการป้องกัน) แน่นอนคุณสามารถ "แฮ็ค" วิธีการแก้ปัญหานี้ (แม้ว่าเพียงแค่ทำให้วิธีการสาธารณะ) แต่คุณอาจต้องการพิจารณา:
อาจไม่มีประโยชน์ในการทดสอบวิธีการส่วนตัว อย่างไรก็ตามบางครั้งฉันก็ชอบที่จะเรียกวิธีการส่วนตัวจากวิธีการทดสอบ ส่วนใหญ่เพื่อป้องกันการทำสำเนารหัสสำหรับการสร้างข้อมูลทดสอบ ...
Microsoft จัดเตรียมสองกลไกสำหรับสิ่งนี้:
accessors
อย่างไรก็ตามกลไกบางครั้งค่อนข้างยากเมื่อพูดถึงการเปลี่ยนแปลงอินเทอร์เฟซของคลาสดั้งเดิม ดังนั้นส่วนใหญ่ฉันหลีกเลี่ยงการใช้นี้
คลาส PrivateObject วิธีอื่นคือใช้ Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject
// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );
// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );
// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
ฉันไม่เห็นด้วยกับปรัชญา "คุณควรสนใจทดสอบอินเทอร์เฟซภายนอก" เท่านั้น มันเหมือนกับว่าร้านซ่อมรถยนต์ควรมีการทดสอบเพื่อดูว่าล้อหมุนหรือไม่ ใช่ในที่สุดฉันสนใจในพฤติกรรมภายนอก แต่ฉันชอบการทดสอบภายในส่วนตัวของฉันเองเพื่อเจาะจงและเจาะจงมากขึ้น ใช่ถ้าฉัน refactor ฉันอาจต้องเปลี่ยนการทดสอบบางส่วน แต่ถ้าเป็นการ refactor ขนาดใหญ่ฉันจะต้องเปลี่ยนเพียงไม่กี่และความจริงที่ว่าการทดสอบภายในอื่น ๆ (ไม่เปลี่ยนแปลง) ยังคงทำงานเป็นตัวบ่งชี้ที่ดีที่ การรีแฟคเตอร์ประสบความสำเร็จ
คุณสามารถลองครอบคลุมทุกกรณีภายในโดยใช้เพียงส่วนต่อประสานสาธารณะและในทางทฤษฎีเป็นไปได้ที่จะทดสอบทุกวิธีภายใน (หรืออย่างน้อยทุก ๆ เรื่อง) โดยใช้ส่วนต่อประสานสาธารณะ แต่คุณอาจต้องลุกขึ้นยืนบนหัวเพื่อให้บรรลุ สิ่งนี้และการเชื่อมต่อระหว่างกรณีทดสอบที่เรียกใช้ผ่านอินเทอร์เฟซสาธารณะและส่วนภายในของโซลูชันที่ออกแบบมาเพื่อทดสอบอาจเป็นเรื่องยากหรือเป็นไปไม่ได้ที่จะแยกแยะ การทดสอบแบบแยกส่วนที่รับประกันว่าเครื่องจักรภายในทำงานได้อย่างเหมาะสมนั้นคุ้มค่ากับการเปลี่ยนแปลงการทดสอบเล็กน้อยที่เกิดขึ้นจากการปรับโครงสร้างใหม่ - อย่างน้อยนั่นก็เป็นประสบการณ์ของฉัน หากคุณต้องทำการเปลี่ยนแปลงขนาดใหญ่ในการทดสอบของคุณสำหรับการปรับโครงสร้างทุกครั้งอาจจะไม่สมเหตุสมผล แต่ในกรณีนี้คุณอาจต้องคิดทบทวนการออกแบบใหม่ทั้งหมด
FooService
ที่ต้องทำX
สิ่งที่คุณควรใส่ใจคือมันควรทำX
เมื่อมีการร้องขอ วิธีมันไม่ได้สำคัญว่าไม่ควร หากมีปัญหาในชั้นเรียนไม่ได้มองเห็นได้ผ่านอินเตอร์เฟซ (น่า) FooService
ก็ยังคงเป็นที่ถูกต้อง ถ้ามันเป็นปัญหาที่เป็นที่มองเห็นได้ผ่านอินเตอร์เฟซการทดสอบในสมาชิกของประชาชนควรตรวจสอบได้ จุดทั้งหมดควรเป็นแบบนั้นตราบใดที่ล้อหมุนอย่างถูกต้องก็สามารถใช้เป็นล้อได้
PrivMethod
การทดสอบPubMethod
ที่โทรPrivMethod
ควรเปิดเผยหรือไม่ จะเกิดอะไรขึ้นเมื่อคุณเปลี่ยนSimpleSmtpService
ไปGmailService
? ทันทีที่การทดสอบส่วนตัวของคุณชี้ไปที่โค้ดที่ไม่มีอยู่อีกต่อไปหรืออาจทำงานแตกต่างกันและอาจล้มเหลวแม้ว่าแอปพลิเคชันอาจทำงานได้อย่างสมบูรณ์ตามที่ออกแบบไว้ หากมีการประมวลผลที่ซับซ้อนที่จะนำไปใช้กับผู้ส่งอีเมลทั้งสองอาจเป็นสิ่งที่ควรEmailProcessor
นำมาใช้กับทั้งสองและทดสอบแยกต่างหาก
ในกรณีที่ไม่ค่อยเกิดขึ้นฉันต้องการทดสอบฟังก์ชั่นส่วนตัวฉันมักจะแก้ไขมันเพื่อปกป้องแทนและฉันก็เขียน subclass ด้วยฟังก์ชั่น wrapper สาธารณะ
ห้องเรียน:
...
protected void APrivateFunction()
{
...
}
...
คลาสย่อยสำหรับการทดสอบ:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
ฉันคิดว่าคำถามพื้นฐานที่ควรถามเพิ่มเติมคือทำไมคุณพยายามทดสอบวิธีส่วนตัวตั้งแต่แรก นั่นคือกลิ่นรหัสที่คุณพยายามทดสอบวิธีส่วนตัวผ่านอินเทอร์เฟซสาธารณะของคลาสนั้นในขณะที่วิธีนั้นมีความเป็นส่วนตัวเนื่องจากเป็นรายละเอียดการใช้งาน สิ่งหนึ่งที่ควรเกี่ยวข้องกับพฤติกรรมของส่วนต่อประสานสาธารณะนั้นไม่ได้เกี่ยวข้องกับวิธีการนำไปใช้ภายใต้หน้าปก
หากฉันต้องการทดสอบพฤติกรรมของวิธีการส่วนตัวโดยใช้ refactorings ทั่วไปฉันสามารถแยกรหัสของมันออกเป็นคลาสอื่น (อาจมีการแสดงระดับแพ็กเกจเพื่อให้แน่ใจว่าไม่ใช่ส่วนหนึ่งของ API สาธารณะ) ฉันสามารถทดสอบพฤติกรรมของมันแบบแยกตัวได้
ผลิตภัณฑ์ของการปรับโครงสร้างหมายถึงวิธีการส่วนตัวตอนนี้เป็นชั้นแยกต่างหากที่ได้กลายเป็นผู้ทำงานร่วมกับชั้นเรียนเดิม พฤติกรรมของมันจะเป็นที่เข้าใจกันดีผ่านการทดสอบหน่วยของตัวเอง
ฉันสามารถเยาะเย้ยพฤติกรรมของมันเมื่อฉันพยายามทดสอบคลาสดั้งเดิมเพื่อที่ฉันจะได้มีสมาธิในการทดสอบพฤติกรรมของอินเทอร์เฟซสาธารณะของคลาสนั้นแทนที่จะต้องทดสอบการระเบิดแบบ combinatorial ของอินเทอร์เฟซสาธารณะและพฤติกรรมของวิธีการส่วนตัวทั้งหมด .
ฉันเห็นสิ่งนี้คล้ายคลึงกับการขับขี่รถยนต์ เมื่อฉันขับรถฉันไม่ได้ขับด้วยฝากระโปรงหน้าขึ้นเพื่อดูว่าเครื่องยนต์กำลังทำงาน ฉันพึ่งอินเทอร์เฟซที่รถมีให้นั่นคือตัวนับรอบและมาตรวัดความเร็วที่จะรู้ว่าเครื่องยนต์กำลังทำงาน ฉันวางใจในความจริงที่ว่ารถเคลื่อนที่จริง ๆ เมื่อฉันกดคันเร่ง ถ้าฉันต้องการทดสอบเครื่องยนต์ฉันสามารถทำการตรวจสอบในส่วนที่แยกได้ : D
แน่นอนการทดสอบวิธีการส่วนตัวโดยตรงอาจเป็นทางเลือกสุดท้ายหากคุณมีแอปพลิเคชันแบบดั้งเดิม แต่ฉันต้องการให้รหัสเดิมนั้นได้รับการปรับโครงสร้างใหม่เพื่อให้การทดสอบดีขึ้น Michael Feathers ได้เขียนหนังสือยอดเยี่ยมเกี่ยวกับเรื่องนี้มาก http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
ประเภทส่วนตัว, internals และสมาชิกส่วนตัวนั้นเป็นเพราะเหตุผลบางอย่างและบ่อยครั้งที่คุณไม่ต้องการยุ่งกับพวกเขาโดยตรง และถ้าคุณทำก็มีโอกาสที่คุณจะแตกในภายหลังเพราะไม่มีการรับประกันได้ว่าคนที่สร้างชุดประกอบเหล่านั้นจะทำให้การใช้งานส่วนตัว / ภายในเป็นเช่นนั้น
แต่ในบางครั้งเมื่อทำการแฮ็ก / สำรวจกลุ่มชุดที่รวบรวมหรือบุคคลที่สามฉันมีตัวเองต้องการที่จะเริ่มต้นเรียนส่วนตัวหรือชั้นเรียนที่มีตัวสร้างส่วนตัวหรือภายใน หรือบางครั้งเมื่อต้องรับมือกับไลบรารีดั้งเดิมที่รวบรวมไว้ล่วงหน้าซึ่งฉันไม่สามารถเปลี่ยนแปลงได้ - ฉันสิ้นสุดการเขียนการทดสอบบางอย่างกับวิธีส่วนตัว
ดังนั้นจึงเกิด AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - มันเป็นคลาส wrapper ด่วนที่จะทำให้งานง่ายขึ้นโดยใช้คุณลักษณะและการสะท้อนแบบไดนามิกของ C # 4.0
คุณสามารถสร้างประเภทภายใน / ส่วนตัวเช่น
//Note that the wrapper is dynamic
dynamic wrapper = AccessPrivateWrapper.FromType
(typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");
//Access the private members
wrapper.PrivateMethodInPrivateClass();
คุณสามารถทดสอบวิธีการส่วนตัวได้สองวิธี
คุณสามารถสร้างอินสแตนซ์ของPrivateObject
คลาสไวยากรณ์ได้ดังนี้
PrivateObject obj= new PrivateObject(PrivateClass);
//now with this obj you can call the private method of PrivateCalss.
obj.PrivateMethod("Parameters");
คุณสามารถใช้การสะท้อน
PrivateClass obj = new PrivateClass(); // Class containing private obj
Type t = typeof(PrivateClass);
var x = t.InvokeMember("PrivateFunc",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance, null, obj, new object[] { 5 });
PrivateClass
แรกและใช้สิ่งนั้น stackoverflow.com/questions/9122708/…
ฉันยังใช้วิธีการ InternalsVisibleToAttribute หากคุณรู้สึกไม่สะดวกใจที่จะใช้วิธีการส่วนตัวก่อนหน้านี้ภายในเพื่อที่จะบรรลุเป้าหมายนี้บางทีพวกเขาก็ไม่ควรทำการทดสอบหน่วยโดยตรง
ท้ายที่สุดคุณกำลังทดสอบพฤติกรรมของชั้นเรียนของคุณแทนที่จะเป็นการใช้งานเฉพาะ - คุณสามารถเปลี่ยนสิ่งหลังได้โดยไม่ต้องเปลี่ยนแบบทดสอบเดิมและการทดสอบของคุณก็ควรผ่านไป
วิธีการส่วนตัวมี 2 ประเภท วิธีการแบบสแตติกส่วนตัวและวิธีการแบบสแตติกส่วนตัว (วิธีอินสแตนซ์) บทความ 2 บทความต่อไปนี้อธิบายวิธีทดสอบหน่วยวิธีส่วนตัวพร้อมตัวอย่าง
MS Test มีคุณสมบัติที่ดีในตัวซึ่งทำให้สมาชิกส่วนตัวและวิธีการในโครงการสามารถสร้างไฟล์ที่เรียกว่า VSCodeGenAccessors
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class BaseAccessor
{
protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
{
m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
}
protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
:
this(null, type)
{
}
internal virtual object Target
{
get
{
return m_privateObject.Target;
}
}
public override string ToString()
{
return this.Target.ToString();
}
public override bool Equals(object obj)
{
if (typeof(BaseAccessor).IsInstanceOfType(obj))
{
obj = ((BaseAccessor)(obj)).Target;
}
return this.Target.Equals(obj);
}
public override int GetHashCode()
{
return this.Target.GetHashCode();
}
}
ด้วยคลาสที่สืบทอดมาจาก BaseAccessor
เช่น
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{
protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
internal SomeClassAccessor(global::Namespace.Someclass target)
: base(target, m_privateType)
{
}
internal static string STATIC_STRING
{
get
{
string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
return ret;
}
set
{
m_privateType.SetStaticField("STATIC_STRING", value);
}
}
internal int memberVar {
get
{
int ret = ((int)(m_privateObject.GetField("memberVar")));
return ret;
}
set
{
m_privateObject.SetField("memberVar", value);
}
}
internal int PrivateMethodName(int paramName)
{
object[] args = new object[] {
paramName};
int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
typeof(int)}, args)));
return ret;
}
ใน CodeProject มีบทความที่กล่าวสั้น ๆ ถึงข้อดีข้อเสียของการทดสอบวิธีการส่วนตัว จากนั้นจะให้รหัสภาพสะท้อนบางส่วนเพื่อเข้าถึงวิธีการส่วนตัว (คล้ายกับรหัสที่ Marcus ให้ไว้ข้างต้น) ปัญหาเดียวที่ฉันพบกับตัวอย่างคือโค้ดไม่ได้คำนึงถึงวิธีที่ใช้มากเกินไป
คุณสามารถค้นหาบทความได้ที่นี่:
ประกาศพวกเขาinternal
แล้วใช้InternalsVisibleToAttribute
เพื่ออนุญาตให้แอสเซมบลีการทดสอบหน่วยของคุณเพื่อดูพวกเขา
ฉันมักจะไม่ใช้คำสั่งคอมไพเลอร์เพราะพวกเขายุ่งเหยิงอย่างรวดเร็ว วิธีหนึ่งที่จะลดความมันลงได้ถ้าคุณต้องการพวกมันจริงๆคือการใส่มันไว้ในคลาสบางส่วนและให้บิลด์ของคุณไม่สนใจไฟล์. cs เมื่อสร้างเวอร์ชันที่ใช้งานจริง
คุณไม่ควรทำการทดสอบวิธีการส่วนตัวของรหัสของคุณตั้งแต่แรก คุณควรทดสอบ 'อินเทอร์เฟซสาธารณะ' หรือ API สิ่งสาธารณะในชั้นเรียนของคุณ API เป็นวิธีสาธารณะทั้งหมดที่คุณเปิดเผยต่อผู้โทรภายนอก
เหตุผลก็คือเมื่อคุณเริ่มทดสอบวิธีการส่วนตัวและ internals ของชั้นเรียนของคุณคุณกำลังเชื่อมต่อการใช้งานในชั้นเรียนของคุณ (สิ่งส่วนตัว) เพื่อการทดสอบของคุณ ซึ่งหมายความว่าเมื่อคุณตัดสินใจที่จะเปลี่ยนรายละเอียดการใช้งานของคุณคุณจะต้องเปลี่ยนการทดสอบของคุณ
ด้วยเหตุผลนี้คุณควรหลีกเลี่ยงการใช้ InternalsVisibleToAtrribute
นี่คือการพูดคุยที่ยอดเยี่ยมจาก Ian Cooper ซึ่งครอบคลุมหัวข้อนี้: Ian Cooper: TDD มันผิดพลาดที่ไหน
บางครั้งอาจเป็นการดีที่จะทดสอบการประกาศส่วนตัว โดยพื้นฐานแล้วคอมไพเลอร์มีวิธีพับลิกเพียงวิธีเดียวเท่านั้น: คอมไพล์ (สตริงเอาต์พุตFileName, พารามิเตอร์สตริง [] sourceSFileNames) ฉันแน่ใจว่าคุณเข้าใจว่าจะเป็นการยากที่จะทดสอบวิธีการดังกล่าวโดยไม่ต้องทดสอบประกาศ "ซ่อน" แต่ละรายการ!
นั่นเป็นเหตุผลที่เราได้สร้าง Visual T #: เพื่อทำการทดสอบง่ายขึ้น มันเป็น. NET ภาษาการเขียนโปรแกรมฟรี (เข้ากันได้กับ C # v2.0)
เราได้เพิ่มตัวดำเนินการ '.-' แล้ว มันแค่ทำตัวเหมือน '.' โอเปอเรเตอร์ยกเว้นคุณสามารถเข้าถึงการประกาศที่ซ่อนอยู่ใด ๆ จากการทดสอบของคุณโดยไม่ต้องเปลี่ยนแปลงอะไรในโครงการทดสอบของคุณ
ลองดูที่เว็บไซต์ของเรา: ดาวน์โหลดมันฟรี
ฉันประหลาดใจที่ไม่มีใครพูดเรื่องนี้ แต่วิธีแก้ปัญหาที่ฉันจ้างคือการสร้างวิธีการคงที่ในชั้นเรียนเพื่อทดสอบตัวเอง สิ่งนี้ช่วยให้คุณสามารถเข้าถึงทุกสิ่งที่สาธารณะและส่วนตัวเพื่อทดสอบด้วย
นอกจากนี้ในภาษาสคริปต์ (ด้วยความสามารถของ OO เช่น Python, Ruby และ PHP) คุณสามารถสร้างไฟล์ทดสอบตัวเองเมื่อทำงาน วิธีที่รวดเร็วในการตรวจสอบให้แน่ใจว่าการเปลี่ยนแปลงของคุณไม่ทำลายอะไรเลย สิ่งนี้ทำให้โซลูชันที่ปรับขนาดได้เพื่อทดสอบคลาสของคุณทั้งหมด: เพียงแค่รันมันทั้งหมด (คุณสามารถทำสิ่งนี้ในภาษาอื่นด้วยหลักโมฆะซึ่งจะทำการทดสอบเสมอ)
ฉันต้องการสร้างตัวอย่างรหัสที่ชัดเจนที่นี่ซึ่งคุณสามารถใช้ในชั้นเรียนใด ๆ ที่คุณต้องการทดสอบวิธีการส่วนตัว
ในชั้นเรียนการทดสอบของคุณเพียงแค่รวมวิธีการเหล่านี้แล้วจ้างพวกเขาตามที่ระบุไว้
/**
*
* @var Class_name_of_class_you_want_to_test_private_methods_in
* note: the actual class and the private variable to store the
* class instance in, should at least be different case so that
* they do not get confused in the code. Here the class name is
* is upper case while the private instance variable is all lower
* case
*/
private $class_name_of_class_you_want_to_test_private_methods_in;
/**
* This uses reflection to be able to get private methods to test
* @param $methodName
* @return ReflectionMethod
*/
protected static function getMethod($methodName) {
$class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
$method = $class->getMethod($methodName);
$method->setAccessible(true);
return $method;
}
/**
* Uses reflection class to call private methods and get return values.
* @param $methodName
* @param array $params
* @return mixed
*
* usage: $this->_callMethod('_someFunctionName', array(param1,param2,param3));
* {params are in
* order in which they appear in the function declaration}
*/
protected function _callMethod($methodName, $params=array()) {
$method = self::getMethod($methodName);
return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
}
$ this -> _ callMethod ('_ someFunctionName', อาร์เรย์ (param1, param2, param3));
เพียงแค่ออกพารามิเตอร์ตามลำดับที่ปรากฏในฟังก์ชันส่วนตัวดั้งเดิม
สำหรับทุกคนที่ต้องการเรียกใช้วิธีการส่วนตัวโดยไม่ต้องวุ่นวายและวุ่นวาย สิ่งนี้ใช้ได้กับกรอบการทดสอบหน่วยใด ๆ โดยไม่ใช้อะไรเลยนอกจาก Reflection แบบเก่าที่ดี
public class ReflectionTools
{
// If the class is non-static
public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
{
Type t = objectUnderTest.GetType();
return t.InvokeMember(method,
BindingFlags.InvokeMethod |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static,
null,
objectUnderTest,
args);
}
// if the class is static
public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
{
MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
foreach(var member in members)
{
if (member.Name == method)
{
return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
}
}
return null;
}
}
จากการทดสอบจริงคุณสามารถทำสิ่งนี้:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");
MbUnit มีเสื้อคลุมที่ดีสำหรับตัวสะท้อนแสงนี้
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
คุณยังสามารถตั้งค่าและรับค่าจากคุณสมบัติได้
dogReflector.GetProperty("Age");
เกี่ยวกับ "การทดสอบส่วนตัว" ฉันยอมรับว่า .. ในโลกที่สมบูรณ์แบบ ไม่มีประโยชน์ในการทำแบบทดสอบหน่วยส่วนตัว แต่ในโลกแห่งความเป็นจริงคุณอาจต้องการเขียนแบบทดสอบส่วนตัวแทนรหัส refactoring
Reflector
ถูกแทนที่ด้วยพลังที่มากขึ้นMirror
ใน Gallio / MbUnit v3.2 ( gallio.org/wiki/doku.php?id=mbunit:mirror )
นี่คือบทความที่ดีเกี่ยวกับการทดสอบหน่วยของวิธีการส่วนตัว แต่ฉันไม่แน่ใจว่าจะมีอะไรดีไปกว่านี้แล้วที่จะทำให้แอปพลิเคชันของคุณได้รับการออกแบบมาเป็นพิเศษสำหรับการทดสอบ ค่อนข้างแน่ใจว่าพวกเราส่วนใหญ่จะเลือกวิธีที่สอง
ในความคิดของฉันคุณควรทดสอบหน่วย API สาธารณะของคลาสของคุณเท่านั้น
ทำให้วิธีการสาธารณะเพื่อทดสอบหน่วยแบ่ง encapsulation เปิดเผยรายละเอียดการใช้งาน
API สาธารณะที่ดีจะแก้เป้าหมายทันทีของรหัสลูกค้าและแก้ไขเป้าหมายนั้นได้อย่างสมบูรณ์
ฉันใช้คลาสPrivateObject แต่ดังที่กล่าวไว้ก่อนหน้านี้ดีกว่าเพื่อหลีกเลี่ยงการทดสอบวิธีการส่วนตัว
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
CC -Dprivate=public
"CC" เป็นคอมไพเลอร์บรรทัดคำสั่งในระบบที่ฉันใช้ ไม่เทียบเท่า-Dfoo=bar
#define foo bar
ดังนั้นตัวเลือกการรวบรวมนี้จะเปลี่ยนสิ่งส่วนตัวทั้งหมดให้เป็นสาธารณะได้อย่างมีประสิทธิภาพ
นี่คือตัวอย่างแรกลงนามวิธีการ:
private string[] SplitInternal()
{
return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
.Cast<Match>()
.Select(m => m.Value)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
}
นี่คือการทดสอบ:
/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
object[] values = new object[] { 2, "Martin" };
XPathString xp = new XPathString(path, values);
PrivateObject param0 = new PrivateObject(xp);
XPathString_Accessor target = new XPathString_Accessor(param0);
string[] expected = new string[] {
"pair[path/to/@Key={0}]",
"Items",
"Item[Name={1}]",
"Date"
};
string[] actual;
actual = target.SplitInternal();
CollectionAssert.AreEqual(expected, actual);
}
วิธีการทำเช่นนี้คือการมีวิธีการของคุณprotected
และเขียนฟิกซ์เจอร์ทดสอบซึ่งสืบทอดคลาสของคุณเพื่อทำการทดสอบ วิธีนี้คุณไม่ได้เปลี่ยนวิธีการของคุณpublic
แต่คุณเปิดใช้งานการทดสอบ
1) ถ้าคุณมีรหัสดั้งเดิมวิธีเดียวที่จะทดสอบวิธีการส่วนตัวก็คือการไตร่ตรอง
2) หากเป็นรหัสใหม่คุณจะมีตัวเลือกดังต่อไปนี้:
ฉันชอบวิธีการใส่คำอธิบายอย่างง่ายและซับซ้อนที่สุด ปัญหาเดียวคือเราเพิ่มการมองเห็นซึ่งฉันคิดว่าไม่ใช่เรื่องใหญ่ เราควรเข้ารหัสให้อินเทอร์เฟซดังนั้นถ้าเรามีอินเทอร์เฟซ MyService และการใช้งาน MyServiceImpl เราสามารถมีคลาสการทดสอบที่สอดคล้องกันนั่นคือ MyServiceTest (วิธีทดสอบอินเทอร์เฟซ) และ MyServiceImplTest (ทดสอบวิธีส่วนตัว) ลูกค้าทุกคนควรใช้อินเทอร์เฟซดังนั้นในลักษณะที่แม้ว่าการมองเห็นของวิธีส่วนตัวได้รับการเพิ่มขึ้นมันไม่ควรสำคัญ
คุณสามารถประกาศเป็นสาธารณะหรือภายใน (ด้วย InternalsVisibleToAttribute) ในขณะที่สร้างในโหมดดีบัก:
/// <summary>
/// This Method is private.
/// </summary>
#if DEBUG
public
#else
private
#endif
static string MyPrivateMethod()
{
return "false";
}
มันขยายโค้ด แต่จะอยู่private
ในรุ่นบิลด์
คุณสามารถสร้างวิธีการทดสอบสำหรับวิธีการส่วนตัวจาก Visual Studio 2008 เมื่อคุณสร้างการทดสอบหน่วยสำหรับวิธีการส่วนตัวโฟลเดอร์อ้างอิงการทดสอบจะถูกเพิ่มเข้าไปในโครงการทดสอบของคุณและมีการเพิ่ม accessor ในโฟลเดอร์นั้น accessor ยังอ้างถึงในตรรกะของวิธีการทดสอบหน่วย accessor นี้ช่วยให้การทดสอบหน่วยของคุณเรียกวิธีการส่วนตัวในรหัสที่คุณกำลังทดสอบ ดูรายละเอียดได้ที่
โปรดทราบว่า InternalsVisibleToAtrribute มีข้อกำหนดว่าชุดประกอบของคุณจะมีชื่อที่แข็งแกร่งซึ่งจะสร้างปัญหาขึ้นมาเองหากคุณกำลังทำงานในโซลูชันที่ไม่เคยมีข้อกำหนดมาก่อน ฉันใช้ accessor เพื่อทดสอบวิธีการส่วนตัว ดูคำถามนี้ว่าสำหรับตัวอย่างของสิ่งนี้
InternalsVisibleToAttribute
ไม่ไม่ จำเป็นต้องมีที่ประกอบคุณจะตั้งชื่ออย่างยิ่ง ขณะนี้ฉันใช้มันในโครงการที่ไม่เป็นเช่นนั้น
pre-historic
ในแง่ของปีอินเทอร์เน็ต แต่การทดสอบหน่วยของวิธีการส่วนตัวตอนนี้ทั้งง่ายและตรงไปตรงมากับ Visual Studio ผลิตคลาส accessor ที่จำเป็นเมื่อต้องการและ เติมตรรกะการทดสอบล่วงหน้าด้วยตัวอย่างเล็ก ๆ น้อย ๆ ที่ใกล้เคียงกับสิ่งที่เราอาจต้องการสำหรับการทดสอบการทำงานที่เรียบง่าย ดูตัวอย่าง msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx