Đây là một chủ đề tôi rất quan tâm. Có nhiều người theo chủ nghĩa thuần túy nói rằng bạn không nên thử nghiệm các công nghệ như EF và NHibernate. Họ đã đúng, họ đã được kiểm tra rất nghiêm ngặt và như một câu trả lời trước đó đã nói rằng việc dành nhiều thời gian để kiểm tra những gì bạn không sở hữu là vô nghĩa.
Tuy nhiên, bạn sở hữu cơ sở dữ liệu bên dưới! Theo quan điểm của tôi, đây là cách tiếp cận bị phá vỡ, bạn không cần phải kiểm tra rằng EF / NH đang thực hiện công việc của họ một cách chính xác. Bạn cần kiểm tra rằng ánh xạ / triển khai của bạn đang hoạt động với cơ sở dữ liệu của bạn. Theo tôi đây là một trong những phần quan trọng nhất của hệ thống mà bạn có thể kiểm tra.
Nói đúng ra, tuy nhiên, chúng tôi đang chuyển ra khỏi lĩnh vực thử nghiệm đơn vị và sang thử nghiệm tích hợp nhưng các nguyên tắc vẫn giữ nguyên.
Điều đầu tiên bạn cần làm là có thể giả lập DAL của bạn để BLL của bạn có thể được kiểm tra độc lập với EF và SQL. Đây là những bài kiểm tra đơn vị của bạn. Tiếp theo, bạn cần thiết kế các Bài kiểm tra Tích hợp để chứng minh DAL của mình, theo tôi đây là những điều quan trọng nhất.
Có một vài điều cần xem xét:
- Cơ sở dữ liệu của bạn cần ở trạng thái đã biết với mỗi bài kiểm tra. Hầu hết các hệ thống sử dụng một bản sao lưu hoặc tạo tập lệnh cho việc này.
- Mỗi bài kiểm tra phải được lặp lại
- Mỗi bài kiểm tra phải là nguyên tử
Có hai cách tiếp cận chính để thiết lập cơ sở dữ liệu của bạn, đầu tiên là chạy tập lệnh DB tạo đơn vị. Điều này đảm bảo rằng cơ sở dữ liệu kiểm tra đơn vị của bạn sẽ luôn ở trạng thái giống nhau ở đầu mỗi thử nghiệm (bạn có thể đặt lại điều này hoặc chạy từng thử nghiệm trong một giao dịch để đảm bảo điều này).
Tùy chọn khác của bạn là những gì tôi làm, chạy các thiết lập cụ thể cho từng thử nghiệm riêng lẻ. Tôi tin rằng đây là cách tiếp cận tốt nhất vì hai lý do chính:
- Cơ sở dữ liệu của bạn đơn giản hơn, bạn không cần toàn bộ lược đồ cho mỗi bài kiểm tra
- Mỗi thử nghiệm sẽ an toàn hơn, nếu bạn thay đổi một giá trị trong tập lệnh tạo của mình thì nó không làm mất hiệu lực hàng chục thử nghiệm khác.
Thật không may, sự thỏa hiệp của bạn ở đây là tốc độ. Phải mất thời gian để chạy tất cả các thử nghiệm này, để chạy tất cả các tập lệnh thiết lập / phá bỏ này.
Một điểm cuối cùng, có thể rất khó để viết một lượng SQL lớn như vậy để kiểm tra ORM của bạn. Đây là nơi tôi thực hiện một cách tiếp cận rất khó chịu (những người theo chủ nghĩa thuần túy ở đây sẽ không đồng ý với tôi). Tôi sử dụng ORM của mình để tạo bài kiểm tra của mình! Thay vì có một tập lệnh riêng cho mọi thử nghiệm DAL trong hệ thống của tôi, tôi có giai đoạn thiết lập thử nghiệm để tạo các đối tượng, gắn chúng vào ngữ cảnh và lưu chúng. Sau đó tôi chạy thử nghiệm của tôi.
Điều này khác xa với giải pháp lý tưởng tuy nhiên trong thực tế tôi thấy nó rất dễ quản lý (đặc biệt là khi bạn có vài nghìn bài kiểm tra), nếu không, bạn đang tạo ra số lượng lớn các tập lệnh. Thực tiễn hơn độ tinh khiết.
Tôi chắc chắn sẽ nhìn lại câu trả lời này trong một vài năm (tháng / ngày) và không đồng ý với bản thân vì cách tiếp cận của tôi đã thay đổi - tuy nhiên đây là cách tiếp cận hiện tại của tôi.
Để thử và tổng hợp mọi thứ tôi đã nói ở trên, đây là thử nghiệm tích hợp DB điển hình của tôi:
[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);
}
}
Điều quan trọng cần lưu ý ở đây là các phiên của hai vòng hoàn toàn độc lập. Khi triển khai RunTest, bạn phải đảm bảo rằng bối cảnh được cam kết và phá hủy và dữ liệu của bạn chỉ có thể đến từ cơ sở dữ liệu của bạn cho phần thứ hai.
Chỉnh sửa ngày 13/10/2014
Tôi đã nói rằng tôi có thể sửa đổi mô hình này trong những tháng tới. Trong khi tôi chủ yếu theo cách tiếp cận mà tôi ủng hộ ở trên, tôi đã cập nhật cơ chế thử nghiệm của mình một chút. Bây giờ tôi có xu hướng tạo các thực thể trong TestSetup và 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();
}
Sau đó kiểm tra từng thuộc tính riêng lẻ
[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);
}
Có một số lý do cho cách tiếp cận này:
- Không có cuộc gọi cơ sở dữ liệu bổ sung (một thiết lập, một lần xé)
- Các xét nghiệm có độ chi tiết cao hơn, mỗi thử nghiệm xác minh một thuộc tính
- Logic thiết lập / TearDown bị xóa khỏi chính các phương thức Test
Tôi cảm thấy điều này làm cho lớp kiểm tra đơn giản hơn và các bài kiểm tra chi tiết hơn (các xác nhận đơn là tốt )
Chỉnh sửa 5/3/2015
Một sửa đổi khác về phương pháp này. Mặc dù các thiết lập cấp độ lớp rất hữu ích cho các thử nghiệm, chẳng hạn như tải các thuộc tính, nhưng chúng ít hữu ích hơn khi yêu cầu các thiết lập khác nhau. Trong trường hợp này, thiết lập một lớp mới cho mỗi trường hợp là quá mức cần thiết.
Để giúp với điều này bây giờ tôi có xu hướng có hai lớp cơ sở SetupPerTest
và SingleSetup
. Hai lớp này phơi bày khung theo yêu cầu.
Trong SingleSetup
chúng tôi có một cơ chế rất giống như được mô tả trong lần chỉnh sửa đầu tiên của tôi. Một ví dụ sẽ là
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);
}
}
Tuy nhiên, các tham chiếu đảm bảo rằng chỉ các mục nhập chính xác được tải mới có thể sử dụng phương pháp 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());
});
}
}
Tóm lại cả hai phương pháp đều hoạt động tùy thuộc vào những gì bạn đang cố gắng kiểm tra.