นี่เป็นหัวข้อที่ฉันสนใจมากมีนักสอนหลายคนที่บอกว่าคุณไม่ควรทดสอบเทคโนโลยีเช่น EF และ NHibernate พวกเขาถูกต้องพวกเขาถูกทดสอบอย่างเข้มงวดแล้วและตามคำตอบก่อนหน้าระบุว่ามักจะไม่มีประโยชน์ที่จะใช้เวลาทดสอบมากมายในสิ่งที่คุณไม่ได้เป็นเจ้าของ
อย่างไรก็ตามคุณมีฐานข้อมูลอยู่ด้านล่าง! นี่เป็นวิธีที่ความคิดของฉันแตกสลายคุณไม่จำเป็นต้องทดสอบว่า EF / NH ทำงานอย่างถูกต้อง คุณต้องทดสอบว่าการแม็พ / การนำไปใช้งานทำงานกับฐานข้อมูลของคุณ ในความคิดของฉันนี่เป็นหนึ่งในส่วนที่สำคัญที่สุดของระบบที่คุณสามารถทดสอบได้
พูดอย่างเคร่งครัด แต่เรากำลังย้ายออกจากโดเมนของการทดสอบหน่วยและเป็นการทดสอบการรวม แต่หลักการยังคงเหมือนเดิม
สิ่งแรกที่คุณต้องทำคือการเยาะเย้ย DAL ของคุณเพื่อให้ BLL ของคุณสามารถทดสอบได้โดยอิสระจาก EF และ SQL นี่คือการทดสอบหน่วยของคุณ ต่อไปคุณต้องออกแบบการทดสอบการรวมระบบเพื่อพิสูจน์ DAL ของคุณในความคิดของฉันสิ่งเหล่านี้มีความสำคัญทุกประการ
มีสองสิ่งที่ควรพิจารณา:
- ฐานข้อมูลของคุณต้องอยู่ในสถานะที่ทราบพร้อมการทดสอบแต่ละครั้ง ระบบส่วนใหญ่ใช้การสำรองข้อมูลหรือสร้างสคริปต์สำหรับสิ่งนี้
- การทดสอบแต่ละครั้งจะต้องทำซ้ำได้
- การทดสอบแต่ละครั้งจะต้องเป็นอะตอม
มีสองวิธีหลักในการตั้งค่าฐานข้อมูลของคุณวิธีแรกคือการเรียกใช้สคริปต์สร้าง UnitTest DB สิ่งนี้ช่วยให้มั่นใจได้ว่าฐานข้อมูลการทดสอบหน่วยของคุณจะอยู่ในสถานะเดิมเมื่อเริ่มต้นการทดสอบแต่ละครั้ง (คุณอาจรีเซ็ตหรือรันการทดสอบแต่ละครั้งในธุรกรรมเพื่อให้แน่ใจว่านี่)
ตัวเลือกอื่นของคุณคือสิ่งที่ฉันทำเรียกใช้การตั้งค่าเฉพาะสำหรับการทดสอบแต่ละรายการ ฉันเชื่อว่านี่เป็นวิธีที่ดีที่สุดสำหรับสองเหตุผลหลัก:
- ฐานข้อมูลของคุณง่ายขึ้นคุณไม่จำเป็นต้องมีสคีมาทั้งหมดสำหรับการทดสอบแต่ละครั้ง
- การทดสอบแต่ละครั้งมีความปลอดภัยมากขึ้นหากคุณเปลี่ยนค่าหนึ่งค่าในสคริปต์สร้างของคุณการทดสอบนั้นจะไม่ทำให้การทดสอบอื่น ๆ
น่าเสียดายที่คุณประนีประนอมที่นี่ความเร็ว ต้องใช้เวลาในการเรียกใช้การทดสอบเหล่านี้เพื่อเรียกใช้สคริปต์การตั้งค่า / การฉีกขาดเหล่านี้ทั้งหมด
ประเด็นสุดท้ายข้อหนึ่งอาจเป็นเรื่องยากมากที่จะเขียน SQL จำนวนมากเพื่อทดสอบ ORM ของคุณ นี่คือที่ฉันใช้วิธีที่น่ารังเกียจมาก (ครูสอนที่นี่จะไม่เห็นด้วยกับฉัน) ฉันใช้ ORM ของฉันเพื่อสร้างแบบทดสอบของฉัน! แทนที่จะมีสคริปต์แยกต่างหากสำหรับการทดสอบ DAL ทุกครั้งในระบบของฉันฉันมีขั้นตอนการตั้งค่าการทดสอบที่สร้างวัตถุแนบไปกับบริบทและบันทึกไว้ จากนั้นฉันก็ทำการทดสอบ
นี่ยังห่างไกลจากทางออกที่ดีที่สุด แต่ในทางปฏิบัติแล้วฉันพบว่ามันง่ายกว่ามากในการจัดการ (โดยเฉพาะเมื่อคุณมีการทดสอบหลายพันครั้ง) ไม่อย่างนั้นคุณกำลังสร้างสคริปต์จำนวนมาก การปฏิบัติจริงมากกว่าความบริสุทธิ์
ฉันจะไม่ต้องสงสัยเลยดูคำตอบนี้ในอีกไม่กี่ปี (เดือน / วัน) และไม่เห็นด้วยกับตัวเองเนื่องจากวิธีการของฉันมีการเปลี่ยนแปลง - แต่นี่เป็นวิธีการปัจจุบันของฉัน
หากต้องการลองและสรุปทุกอย่างที่ฉันได้กล่าวไปข้างต้นนี่คือการทดสอบการรวมฐานข้อมูลโดยทั่วไปของฉัน:
[Test]
public void LoadUser()
{
this.RunTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
return user.UserID;
}, id => // the ID of the entity we need to load
{
var user = LoadMyUser(id); // load the entity
Assert.AreEqual("Mr", user.Title); // test your properties
Assert.AreEqual("Joe", user.Firstname);
Assert.AreEqual("Bloggs", user.Lastname);
}
}
สิ่งสำคัญที่ควรสังเกตที่นี่คือเซสชันของลูปทั้งสองนั้นเป็นอิสระอย่างสมบูรณ์ ในการติดตั้ง RunTest คุณต้องมั่นใจว่าบริบทมีความมุ่งมั่นและถูกทำลายและข้อมูลของคุณสามารถมาจากฐานข้อมูลของคุณในส่วนที่สองเท่านั้น
แก้ไข 13/10/2557
ฉันบอกว่าฉันอาจจะแก้ไขโมเดลนี้ในอีกไม่กี่เดือนข้างหน้า ในขณะที่ฉันยืนหยัดด้วยวิธีการที่ฉันสนับสนุนด้านบนฉันได้ปรับปรุงกลไกการทดสอบของฉันเล็กน้อย ตอนนี้ฉันมักจะสร้างเอนทิตีใน TestSetup และ TestTearDown
[SetUp]
public void Setup()
{
this.SetupTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
});
}
[TearDown]
public void TearDown()
{
this.TearDownDatabase();
}
จากนั้นทดสอบคุณสมบัติแต่ละรายการแยกกัน
[Test]
public void TestTitle()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Mr", user.Title);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Bloggs", user.Lastname);
}
มีเหตุผลหลายประการสำหรับวิธีนี้:
- ไม่มีการเรียกฐานข้อมูลเพิ่มเติม (การตั้งค่าเดียวการฉีกขาดหนึ่งครั้ง)
- การทดสอบมีความละเอียดมากยิ่งขึ้นแต่ละการทดสอบจะตรวจสอบคุณสมบัติหนึ่งรายการ
- ตรรกะการติดตั้ง / TearDown จะถูกลบออกจากวิธีการทดสอบด้วยตนเอง
ฉันรู้สึกว่าสิ่งนี้ทำให้คลาสการทดสอบง่ายขึ้นและการทดสอบละเอียดยิ่งขึ้น (การยืนยันครั้งเดียวดี )
แก้ไข 5/3/2558
การแก้ไขเกี่ยวกับวิธีการนี้อีก ในขณะที่การตั้งค่าระดับชั้นมีประโยชน์อย่างมากสำหรับการทดสอบเช่นการโหลดคุณสมบัติจะมีประโยชน์น้อยกว่าเมื่อต้องการการตั้งค่าแบบต่างๆ ในกรณีนี้การตั้งค่าคลาสใหม่สำหรับแต่ละกรณีนั้นมากเกินไป
หากต้องการความช่วยเหลือเกี่ยวกับเรื่องนี้ตอนนี้ผมมักจะมีสองชั้นฐานและSetupPerTest
SingleSetup
สองคลาสเหล่านี้เปิดเผยเฟรมเวิร์กตามต้องการ
ในSingleSetup
เรามีกลไกที่คล้ายกันมากตามที่อธิบายไว้ในการแก้ไขครั้งแรกของฉัน ตัวอย่างจะเป็น
public TestProperties : SingleSetup
{
public int UserID {get;set;}
public override DoSetup(ISession session)
{
var user = new User("Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Bloggs", user.Lastname);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
}
อย่างไรก็ตามการอ้างอิงซึ่งทำให้แน่ใจว่ามีการโหลดการเข้าใช้ที่ถูกต้องเท่านั้นอาจใช้วิธี SetupPerTest
public TestProperties : SetupPerTest
{
[Test]
public void EnsureCorrectReferenceIsLoaded()
{
int friendID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriend();
session.Save(user);
friendID = user.Friends.Single().FriendID;
} () =>
{
var user = GetUser();
Assert.AreEqual(friendID, user.Friends.Single().FriendID);
});
}
[Test]
public void EnsureOnlyCorrectFriendsAreLoaded()
{
int userID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriends(2);
var user2 = CreateUserWithFriends(5);
session.Save(user);
session.Save(user2);
userID = user.UserID;
} () =>
{
var user = GetUser(userID);
Assert.AreEqual(2, user.Friends.Count());
});
}
}
โดยสรุปวิธีการทั้งสองทำงานขึ้นอยู่กับสิ่งที่คุณพยายามทดสอบ