Làm thế nào để đơn vị kiểm tra một đối tượng với các truy vấn cơ sở dữ liệu


153

Tôi đã nghe nói rằng thử nghiệm đơn vị là "hoàn toàn tuyệt vời", "thực sự tuyệt vời" và "tất cả các cách tốt" nhưng 70% hoặc nhiều tệp của tôi liên quan đến truy cập cơ sở dữ liệu (một số đọc và một số ghi) và tôi không chắc làm thế nào để viết một bài kiểm tra đơn vị cho các tập tin này.

Tôi đang sử dụng PHP và Python nhưng tôi nghĩ đó là một câu hỏi áp dụng cho hầu hết / tất cả các ngôn ngữ sử dụng quyền truy cập cơ sở dữ liệu.

Câu trả lời:


82

Tôi sẽ đề nghị loại bỏ các cuộc gọi của bạn đến cơ sở dữ liệu. Mocks về cơ bản là các đối tượng trông giống như đối tượng bạn đang cố gắng gọi một phương thức trên, theo nghĩa là chúng có cùng thuộc tính, phương thức, v.v. có sẵn cho người gọi. Nhưng thay vì thực hiện bất kỳ hành động nào họ được lập trình để thực hiện khi một phương thức cụ thể được gọi, nó bỏ qua hoàn toàn và chỉ trả về một kết quả. Kết quả đó thường được xác định bởi bạn trước thời hạn.

Để thiết lập các đối tượng của bạn để chế nhạo, có lẽ bạn cần phải sử dụng một số kiểu đảo ngược của mẫu tiêm kiểm soát / phụ thuộc, như trong mã giả sau đây:

class Bar
{
    private FooDataProvider _dataProvider;

    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}

class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

Bây giờ trong bài kiểm tra đơn vị của bạn, bạn tạo một bản giả lập FooDataProvider, cho phép bạn gọi phương thức GetAllFoos mà không cần phải thực sự nhấn vào cơ sở dữ liệu.

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);

        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}

        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)

        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()

        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

Một kịch bản chế giễu phổ biến, một cách ngắn gọn. Tất nhiên, bạn vẫn có thể muốn kiểm tra các cuộc gọi cơ sở dữ liệu thực tế của mình, mà bạn sẽ cần phải truy cập cơ sở dữ liệu.


Tôi biết điều này đã cũ nhưng những gì về việc tạo một bảng trùng lặp với bảng đã có trong DB. Bằng cách đó bạn có thể xác nhận các cuộc gọi DB hoạt động?
bretterer

1
Tôi đã sử dụng PDO của PHP làm quyền truy cập cơ sở dữ liệu ở mức thấp nhất mà tôi đã trích xuất một giao diện. Sau đó, tôi đã xây dựng một lớp cơ sở dữ liệu nhận thức ứng dụng trên đó. Đây là lớp chứa tất cả các truy vấn SQL thô và thông tin khác. Phần còn lại của ứng dụng tương tác với cơ sở dữ liệu cấp cao hơn này. Tôi đã thấy điều này hoạt động khá tốt để thử nghiệm đơn vị; Tôi kiểm tra các trang ứng dụng của mình theo cách chúng tương tác với cơ sở dữ liệu ứng dụng. Tôi kiểm tra cơ sở dữ liệu ứng dụng của mình theo cách nó tương tác với PDO. Tôi giả sử PDO hoạt động mà không có lỗi. Mã nguồn: manx.codeplex.com
hợp pháp hóa

1
@bretterer - Tạo một bảng trùng lặp là tốt cho thử nghiệm tích hợp. Để kiểm tra đơn vị, bạn thường sử dụng một đối tượng giả, cho phép bạn kiểm tra một đơn vị mã bất kể cơ sở dữ liệu.
Sinh ra ToCode

2
Giá trị trong việc loại bỏ các cuộc gọi cơ sở dữ liệu trong các bài kiểm tra đơn vị của bạn là gì? Điều này có vẻ không hữu ích vì bạn có thể thay đổi việc triển khai để trả về một kết quả khác, nhưng bài kiểm tra đơn vị của bạn sẽ (không chính xác) vượt qua.
bmay2

2
@ bmay2 Bạn không sai. Câu trả lời ban đầu của tôi đã được viết cách đây rất lâu (9 năm!) Khi nhiều người không viết mã theo cách có thể kiểm tra được và khi các công cụ kiểm tra bị thiếu trầm trọng. Tôi sẽ không đề xuất phương pháp này nữa. Hôm nay tôi sẽ chỉ thiết lập một cơ sở dữ liệu thử nghiệm và cung cấp cho nó dữ liệu tôi cần cho thử nghiệm và / hoặc thiết kế mã của tôi để tôi có thể kiểm tra càng nhiều logic càng tốt mà không cần cơ sở dữ liệu.
Doug R

25

Tốt nhất, đối tượng của bạn nên không biết gì. Chẳng hạn, bạn nên có một "lớp truy cập dữ liệu", mà bạn sẽ thực hiện các yêu cầu, điều đó sẽ trả về các đối tượng. Bằng cách này, bạn có thể để phần đó ra khỏi các bài kiểm tra đơn vị của mình hoặc kiểm tra chúng một cách cô lập.

Nếu các đối tượng của bạn được liên kết chặt chẽ với lớp dữ liệu của bạn, rất khó để thực hiện kiểm tra đơn vị thích hợp. phần đầu tiên của bài kiểm tra đơn vị, là "đơn vị". Tất cả các đơn vị sẽ có thể được thử nghiệm trong sự cô lập.

Trong các dự án c # của tôi, tôi sử dụng NHibernate với lớp Dữ liệu hoàn toàn riêng biệt. Các đối tượng của tôi sống trong mô hình miền lõi và được truy cập từ lớp ứng dụng của tôi. Lớp ứng dụng nói chuyện với cả lớp dữ liệu và lớp mô hình miền.

Lớp ứng dụng đôi khi cũng được gọi là "Lớp nghiệp vụ".

Nếu bạn đang sử dụng PHP, hãy tạo một tập hợp các lớp cụ thể để truy cập dữ liệu CHỈ . Hãy chắc chắn rằng các đối tượng của bạn không biết làm thế nào chúng được duy trì và kết nối cả hai trong các lớp ứng dụng của bạn.

Một lựa chọn khác là sử dụng mocking / stub.


Tôi đã luôn đồng ý với điều này nhưng trong thực tế do thời hạn và "được rồi, giờ chỉ thêm một tính năng nữa, đến 2 giờ chiều hôm nay" đây là một trong những điều khó đạt được nhất. Mặc dù vậy, loại điều này là mục tiêu chính của tái cấu trúc, nếu sếp của tôi quyết định rằng anh ta đã không nghĩ đến 50 vấn đề mới nổi đòi hỏi các bảng và logic kinh doanh hoàn toàn mới.
Darren Ringer

3
Nếu các đối tượng của bạn được liên kết chặt chẽ với lớp dữ liệu của bạn, rất khó để thực hiện kiểm tra đơn vị thích hợp. phần đầu tiên của bài kiểm tra đơn vị, là "đơn vị". Tất cả các đơn vị sẽ có thể được thử nghiệm trong sự cô lập. giải thích hay
Amitābha

11

Cách dễ nhất để kiểm tra đơn vị đối tượng có quyền truy cập cơ sở dữ liệu là sử dụng phạm vi giao dịch.

Ví dụ:

    [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {

        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();

            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

Điều này sẽ trở lại trạng thái của cơ sở dữ liệu, về cơ bản giống như một giao dịch khôi phục để bạn có thể chạy thử nghiệm bao nhiêu lần tùy ý mà không có bất kỳ tác dụng phụ nào. Chúng tôi đã sử dụng phương pháp này thành công trong các dự án lớn. Bản dựng của chúng tôi mất một chút thời gian để chạy (15 phút), nhưng nó không kinh khủng khi có 1800 bài kiểm tra đơn vị. Ngoài ra, nếu thời gian xây dựng là một mối quan tâm, bạn có thể thay đổi quy trình xây dựng để có nhiều bản dựng, một bản dựng để xây dựng src, một bản khác sẽ kích hoạt sau đó để xử lý các bài kiểm tra đơn vị, phân tích mã, đóng gói, v.v ...


1
+1 Tiết kiệm rất nhiều thời gian khi đơn vị kiểm tra các lớp Truy cập dữ liệu. Chỉ cần lưu ý rằng TS thường sẽ cần MSDTC mà có thể không mong muốn (tùy thuộc vào việc ứng dụng của bạn có cần MSDTC không)
StuartLC

Câu hỏi ban đầu là về PHP, ví dụ này có vẻ là C #. Các môi trường rất khác nhau.
hợp pháp hóa

2
Tác giả của câu hỏi nói rằng đó là một câu hỏi chung áp dụng cho tất cả các ngôn ngữ có liên quan đến DB.
Vedran

9
và người bạn thân yêu này, được gọi là kiểm tra tích hợp
AA.

10

Tôi có lẽ có thể cung cấp cho bạn một trải nghiệm của chúng tôi khi chúng tôi bắt đầu xem xét đơn vị thử nghiệm quy trình trung cấp của chúng tôi bao gồm một tấn các hoạt động sql "logic kinh doanh".

Trước tiên, chúng tôi đã tạo một lớp trừu tượng cho phép chúng tôi "đặt vào" bất kỳ kết nối cơ sở dữ liệu hợp lý nào (trong trường hợp của chúng tôi, chúng tôi chỉ đơn giản hỗ trợ một kết nối kiểu ODBC).

Khi đã có, chúng tôi có thể thực hiện một số thứ như thế này trong mã của chúng tôi (chúng tôi làm việc trong C ++, nhưng tôi chắc chắn bạn hiểu ý tưởng đó):

GetDatabase (). ExecuteSQL ("INSERT INTO foo (blah, blah)")

Trong thời gian chạy bình thường, GetDatabase () sẽ trả về một đối tượng đã cung cấp tất cả sql của chúng tôi (bao gồm các truy vấn), thông qua ODBC trực tiếp đến cơ sở dữ liệu.

Sau đó chúng tôi bắt đầu xem xét các cơ sở dữ liệu trong bộ nhớ - tốt nhất theo một cách lâu dài dường như là SQLite. ( http://www.sqlite.org/index.html ). Thật đơn giản để thiết lập và sử dụng, và cho phép chúng tôi phân lớp và ghi đè GetDatabase () để chuyển tiếp sql sang cơ sở dữ liệu trong bộ nhớ được tạo và hủy cho mọi thử nghiệm được thực hiện.

Chúng tôi vẫn đang ở giai đoạn đầu của việc này, nhưng cho đến nay nó vẫn tốt, tuy nhiên chúng tôi phải đảm bảo rằng chúng tôi tạo bất kỳ bảng nào được yêu cầu và điền vào chúng bằng dữ liệu thử nghiệm - tuy nhiên chúng tôi đã giảm khối lượng công việc ở đây bằng cách tạo một tập hợp các hàm trợ giúp chung có thể thực hiện rất nhiều điều này cho chúng ta.

Nhìn chung, nó đã giúp ích rất nhiều cho quá trình TDD của chúng tôi, vì việc tạo ra những thay đổi khá vô hại để sửa một số lỗi nhất định có thể có những ảnh hưởng khá lạ đối với các khu vực khác (khó phát hiện) trong hệ thống của bạn - do tính chất của sql / cơ sở dữ liệu.

Rõ ràng, các trải nghiệm của chúng tôi tập trung vào môi trường phát triển C ++, tuy nhiên tôi chắc chắn rằng bạn có thể có được thứ gì đó tương tự hoạt động theo PHP / Python.

Hi vọng điêu nay co ich.


9

Bạn nên chế độ truy cập cơ sở dữ liệu nếu bạn muốn kiểm tra các lớp của mình. Rốt cuộc, bạn không muốn kiểm tra cơ sở dữ liệu trong một bài kiểm tra đơn vị. Đó sẽ là một bài kiểm tra tích hợp.

Tóm tắt các cuộc gọi đi và sau đó chèn một giả chỉ trả về dữ liệu dự kiến. Nếu các lớp của bạn không làm nhiều hơn việc thực hiện các truy vấn, thì nó thậm chí có thể không đáng để kiểm tra chúng, mặc dù ...


6

Cuốn sách xUnit Test Forms mô tả một số cách để xử lý mã kiểm tra đơn vị đánh vào cơ sở dữ liệu. Tôi đồng ý với những người khác đang nói rằng bạn không muốn làm điều này bởi vì nó chậm, nhưng đôi khi bạn phải làm điều đó, IMO. Việc loại bỏ kết nối db để kiểm tra các công cụ cấp cao hơn là một ý tưởng hay, nhưng hãy xem cuốn sách này để biết các đề xuất về những điều bạn có thể làm để tương tác với cơ sở dữ liệu thực tế.


4

Tùy chọn bạn có:

  • Viết một tập lệnh sẽ xóa sạch cơ sở dữ liệu trước khi bạn bắt đầu kiểm tra đơn vị, sau đó điền db với tập dữ liệu được xác định trước và chạy thử nghiệm. Bạn cũng có thể làm điều đó trước mỗi bài kiểm tra - nó sẽ chậm, nhưng ít bị lỗi hơn.
  • Tiêm cơ sở dữ liệu. (Ví dụ bằng giả Java, nhưng áp dụng cho tất cả các ngôn ngữ OO)

    cơ sở dữ liệu lớp {
     Truy vấn kết quả công khai (Truy vấn chuỗi) {... db thực tại đây ...}
    }

    lớp MockDatabase mở rộng cơ sở dữ liệu { Truy vấn kết quả công khai (Truy vấn chuỗi) { trả về "kết quả giả"; } }

    lớp ObjectThatUsesDB { công khai ObjectThatUsesDB (Cơ sở dữ liệu db) { this.database = db; } }

    Bây giờ trong sản xuất, bạn sử dụng cơ sở dữ liệu bình thường và cho tất cả các thử nghiệm, bạn chỉ cần tiêm cơ sở dữ liệu giả mà bạn có thể tạo ad hoc.

  • Không sử dụng DB ở hầu hết các mã (dù sao đó cũng là một cách làm tồi tệ). Tạo một đối tượng "cơ sở dữ liệu" thay vì trả về kết quả sẽ trả về các đối tượng bình thường (nghĩa là sẽ trả về Userthay vì một tuple {name: "marcin", password: "blah"}) viết tất cả các thử nghiệm của bạn với các đối tượng thực được xây dựng đặc biệt và viết một thử nghiệm lớn phụ thuộc vào cơ sở dữ liệu đảm bảo chuyển đổi này hoạt động tốt.

Tất nhiên những cách tiếp cận này không loại trừ lẫn nhau và bạn có thể trộn và kết hợp chúng khi bạn cần.


3

Kiểm tra đơn vị truy cập cơ sở dữ liệu của bạn là đủ dễ dàng nếu dự án của bạn có độ gắn kết cao và khớp nối lỏng lẻo trong suốt. Bằng cách này, bạn chỉ có thể kiểm tra những thứ mà mỗi lớp cụ thể làm mà không phải kiểm tra mọi thứ cùng một lúc.

Ví dụ: nếu bạn kiểm tra đơn vị lớp giao diện người dùng, các bài kiểm tra bạn viết chỉ nên cố gắng xác minh logic bên trong giao diện người dùng hoạt động như mong đợi, không phải logic nghiệp vụ hoặc hành động cơ sở dữ liệu đằng sau chức năng đó.

Nếu bạn muốn kiểm tra đơn vị truy cập cơ sở dữ liệu thực tế, bạn thực sự sẽ kết thúc với nhiều kiểm tra tích hợp hơn, bởi vì bạn sẽ phụ thuộc vào ngăn xếp mạng và máy chủ cơ sở dữ liệu của bạn, nhưng bạn có thể xác minh rằng mã SQL của bạn thực hiện những gì bạn đã yêu cầu làm

Sức mạnh tiềm ẩn của thử nghiệm đơn vị đối với cá nhân tôi là nó buộc tôi phải thiết kế các ứng dụng của mình theo cách tốt hơn nhiều so với khi không có chúng. Điều này là bởi vì nó thực sự giúp tôi thoát khỏi tâm lý "chức năng này nên làm mọi thứ".

Xin lỗi tôi không có bất kỳ ví dụ mã cụ thể nào cho PHP / Python, nhưng nếu bạn muốn xem một ví dụ .NET tôi có một bài viết mô tả một kỹ thuật tôi đã sử dụng để thực hiện kiểm tra này.


2

Tôi thường cố gắng chia nhỏ các thử nghiệm của mình giữa kiểm tra các đối tượng (và ORM, nếu có) và kiểm tra db. Tôi kiểm tra phía đối tượng của mọi thứ bằng cách chế nhạo các cuộc gọi truy cập dữ liệu trong khi tôi kiểm tra phía db của mọi thứ bằng cách kiểm tra các tương tác đối tượng với db, theo kinh nghiệm của tôi, thường khá hạn chế.

Tôi đã từng nản lòng với việc viết các bài kiểm tra đơn vị cho đến khi tôi bắt đầu chế tạo phần truy cập dữ liệu để tôi không phải tạo db kiểm tra hoặc tạo dữ liệu kiểm tra một cách nhanh chóng. Bằng cách chế nhạo dữ liệu, bạn có thể tạo ra tất cả dữ liệu trong thời gian chạy và chắc chắn rằng các đối tượng của bạn hoạt động đúng với các đầu vào đã biết.


2

Tôi chưa bao giờ thực hiện điều này trong PHP và tôi chưa bao giờ sử dụng Python, nhưng điều bạn muốn làm là giả lập các cuộc gọi đến cơ sở dữ liệu. Để làm điều đó, bạn có thể triển khai một số IoC cho dù là công cụ của bên thứ 3 hoặc bạn tự quản lý nó, sau đó bạn có thể triển khai một số phiên bản giả của người gọi cơ sở dữ liệu, nơi bạn sẽ kiểm soát kết quả của cuộc gọi giả mạo đó.

Một dạng đơn giản của IoC có thể được thực hiện chỉ bằng cách mã hóa thành Giao diện. Điều này đòi hỏi một số loại hướng đối tượng đang diễn ra trong mã của bạn để nó có thể không áp dụng cho những gì bạn đang làm (tôi nói rằng vì tất cả những gì tôi phải tiếp tục là việc bạn đề cập đến PHP và Python)

Hy vọng điều đó hữu ích, nếu không có gì khác bạn có một số thuật ngữ để tìm kiếm bây giờ.


2

Tôi đồng ý với quyền truy cập cơ sở dữ liệu bài đăng đầu tiên nên được loại bỏ thành một lớp DAO thực hiện giao diện. Sau đó, bạn có thể kiểm tra logic của mình chống lại việc triển khai lớp DAO còn sơ khai.


2

Bạn có thể sử dụng các khung mô phỏng để trừu tượng hóa công cụ cơ sở dữ liệu. Tôi không biết nếu PHP / Python có một số nhưng đối với các ngôn ngữ được nhập (C #, Java, v.v.) thì có rất nhiều sự lựa chọn

Nó cũng phụ thuộc vào cách bạn thiết kế các mã truy cập cơ sở dữ liệu đó, bởi vì một số thiết kế dễ kiểm tra đơn vị hơn so với các bài đăng khác đã đề cập trước đó.


2

Thiết lập dữ liệu kiểm tra cho các bài kiểm tra đơn vị có thể là một thách thức.

Khi nói đến Java, nếu bạn sử dụng API mùa xuân để thử nghiệm đơn vị, bạn có thể kiểm soát các giao dịch ở cấp độ đơn vị. Nói cách khác, bạn có thể thực hiện các kiểm tra đơn vị liên quan đến cập nhật / chèn / xóa cơ sở dữ liệu và khôi phục các thay đổi. Khi kết thúc thực hiện, bạn để lại mọi thứ trong cơ sở dữ liệu như trước khi bạn bắt đầu thực hiện. Đối với tôi, nó là tốt như nó có thể nhận được.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.