Kiểm tra đơn vị để kiểm tra việc tạo Đối tượng Miền


11

Tôi có một bài kiểm tra đơn vị, trông như thế này:

[Test]
public void Should_create_person()
{
     Assert.DoesNotThrow(() => new Person(Guid.NewGuid(), new DateTime(1972, 01, 01));
}

Tôi khẳng định rằng một đối tượng Person được tạo ở đây tức là xác nhận không thất bại. Ví dụ: nếu Guid là null hoặc ngày sinh sớm hơn 01/01/1900, thì xác thực sẽ thất bại và một ngoại lệ sẽ bị ném (có nghĩa là thử nghiệm thất bại).

Hàm tạo trông như thế này:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Đây có phải là một ý tưởng tốt cho một bài kiểm tra?

Lưu ý : Tôi đang theo cách tiếp cận Cổ điển để Kiểm tra Đơn vị Mô hình Miền nếu điều đó có bất kỳ ảnh hưởng nào.


Liệu constructor có logic nào đáng được khẳng định sau - trong quá trình khởi tạo không?
Laiv

2
Không bao giờ bận tâm kiểm tra các nhà xây dựng !!! Xây dựng nên thẳng tiến. Bạn có mong đợi thất bại trong Guid.NewGuid () hoặc nhà xây dựng của DateTime không?
ivenxu

@Laiv, vui lòng xem cập nhật cho câu hỏi.
w0051977

1
Không có gì đáng để thực hiện một bài kiểm tra như bài bạn đã chia sẻ. Tuy nhiên, tôi cũng sẽ kiểm tra ngược lại. Tôi sẽ kiểm tra trường hợp sinh raDate gây ra lỗi. Đó là sự bất biến của lớp bạn muốn được kiểm soát và kiểm tra.
Laiv

3
Bài kiểm tra có vẻ ổn, tiết kiệm cho một điều: tên. Should_create_person? Điều gì nên tạo ra một người? Đặt cho nó một cái tên có ý nghĩa, như thế Creating_person_with_valid_data_succeeds.
David Arno

Câu trả lời:


18

Đây là một thử nghiệm hợp lệ (mặc dù khá hăng hái) và đôi khi tôi làm nó để kiểm tra logic của nhà xây dựng, tuy nhiên như Laiv đã đề cập trong các ý kiến ​​bạn nên tự hỏi tại sao.

Nếu nhà xây dựng của bạn trông như thế này:

public Person(Guid guid, DateTime dob)
{
  this.Guid = guid;
  this.Dob = dob;
}

Có rất nhiều điểm trong thử nghiệm cho dù nó ném? Cho dù các tham số được gán chính xác tôi có thể hiểu nhưng kiểm tra của bạn là quá mức cần thiết.

Tuy nhiên, nếu thử nghiệm của bạn làm một cái gì đó như thế này:

public Person(Guid guid, DateTime dob)
{
  if(guid == default(Guid)) throw new ArgumentException("Guid is invalid");
  if(dob == default(DateTime)) throw new ArgumentException("Dob is invalid");

  this.Guid = guid;
  this.Dob = dob;
}

Sau đó, bài kiểm tra của bạn trở nên phù hợp hơn (vì bạn thực sự đang ném ngoại lệ ở đâu đó trong mã).

Một điều tôi muốn nói, nói chung, đó là cách thực hành tồi để có nhiều logic trong công cụ xây dựng của bạn. Xác nhận cơ bản (như kiểm tra null / mặc định tôi đang làm ở trên) đều ổn. Nhưng nếu bạn đang kết nối với cơ sở dữ liệu và tải dữ liệu của ai đó thì đó là nơi mã bắt đầu thực sự có mùi ...

Bởi vì điều này, nếu nhà xây dựng của bạn đáng để thử nghiệm (vì có rất nhiều logic sẽ xảy ra) thì có thể có điều gì đó không ổn.

Bạn gần như chắc chắn sẽ có các thử nghiệm khác bao gồm lớp này trong các lớp logic nghiệp vụ, các hàm tạo và các phép gán biến gần như chắc chắn sẽ có được phạm vi bảo hiểm hoàn chỉnh từ các thử nghiệm này. Do đó, có thể vô nghĩa khi thêm các thử nghiệm cụ thể cụ thể cho hàm tạo. Tuy nhiên, không có gì là đen trắng và tôi sẽ không có gì chống lại các thử nghiệm này nếu tôi đang xem xét mã chúng - nhưng tôi đặt câu hỏi liệu chúng có thêm nhiều giá trị ở trên và ngoài các thử nghiệm ở nơi khác trong giải pháp của bạn không.

Trong ví dụ của bạn:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Bạn không chỉ thực hiện xác nhận, mà bạn còn gọi một nhà xây dựng cơ sở. Đối với tôi, điều này cung cấp thêm lý do để thực hiện các thử nghiệm này vì chúng có logic xây dựng / xác thực hiện được chia thành hai lớp làm giảm khả năng hiển thị và tăng nguy cơ thay đổi bất ngờ.

TLD

Có một số giá trị cho các thử nghiệm này, tuy nhiên logic xác thực / gán có thể sẽ được bao phủ bởi các thử nghiệm khác trong giải pháp của bạn. Nếu có rất nhiều logic trong các hàm tạo này yêu cầu thử nghiệm quan trọng thì nó gợi ý cho tôi rằng có một mùi mã khó chịu ẩn nấp trong đó.


@Laith, Vui lòng xem bản cập nhật cho câu hỏi của tôi
w0051977

Tôi nhận thấy rằng bạn đang gọi một nhà xây dựng cơ sở trong ví dụ của bạn. IMHO điều này làm tăng thêm giá trị của bài kiểm tra của bạn, logic của hàm tạo hiện được chia thành hai lớp và do đó có nguy cơ thay đổi cao hơn một chút do đó đưa ra nhiều lý do để kiểm tra nó.
Liath

"Tuy nhiên, nếu thử nghiệm của bạn làm điều gì đó như thế này:" <Ý bạn là "nếu nhà xây dựng của bạn làm điều gì đó như thế này" ?
Kodos Johnson

"Có một số giá trị cho các thử nghiệm này" - thật thú vị đối với tôi, giá trị đang cho thấy rằng chúng tôi có thể làm cho thử nghiệm này trở nên dư thừa bằng cách sử dụng một lớp mới để đại diện cho dob của người đó (ví dụ PersonBirthdate) thực hiện xác nhận ngày sinh. Tương tự Guidkiểm tra có thể được thực hiện trên Idlớp. Điều này có nghĩa là bạn thực sự không cần phải có logic xác thực đó trong hàm Persontạo nữa vì không thể xây dựng một logic có dữ liệu không hợp lệ - ngoại trừ các nullref. Tất nhiên, bạn phải viết bài kiểm tra cho hai lớp còn lại :)
Stephen Byrne

12

Đã có một câu trả lời tốt ở đây, nhưng tôi nghĩ một điều nữa là đáng nói.

Khi thực hiện TDD "bằng cuốn sách", trước tiên người ta cần viết một bài kiểm tra để gọi hàm tạo, ngay cả trước khi hàm tạo được triển khai. Thử nghiệm đó thực sự có thể trông giống như thử nghiệm mà bạn đã trình bày, ngay cả khi sẽ không có logic xác thực bằng không bên trong triển khai của nhà xây dựng.

Cũng lưu ý rằng đối với TDD, trước tiên bạn nên viết một bài kiểm tra khác như

  Assert.Throws<ArgumentException>(() => new Person(Guid.NewGuid(), 
        new DateTime(1572, 01, 01));

trước khi thêm kiểm tra cho DateTime(1900,01,01)hàm tạo.

Trong bối cảnh TDD, thử nghiệm hiển thị có ý nghĩa hoàn hảo.


Góc đẹp tôi chưa xem xét!
Liath

1
Điều này cho tôi thấy tại sao một dạng TDD cứng nhắc như vậy lại lãng phí thời gian: bài kiểm tra nên có giá trị sau khi mã được viết hoặc bạn chỉ viết mỗi dòng mã hai lần, một lần như một xác nhận và một lần là mã. Tôi sẽ lập luận rằng bản thân hàm tạo không phải là một phần logic cần thử nghiệm; quy tắc kinh doanh "những người sinh ra trước năm 1900 không thể được đại diện" là có thể kiểm chứng được và nhà xây dựng là nơi quy tắc đó xảy ra để thực hiện, nhưng khi nào thì thử nghiệm của một nhà xây dựng trống sẽ thêm giá trị cho dự án?
IMSoP

Có thực sự tdd bởi cuốn sách? Tôi sẽ tạo cá thể và gọi phương thức của nó ngay lập tức trong một mã. Sau đó, tôi sẽ viết thử nghiệm cho phương thức đó và bằng cách đó tôi cũng sẽ phải tạo cá thể cho phương thức đó, vì vậy cả hàm tạo và phương thức sẽ được đề cập trong thử nghiệm đó. Trừ khi trong constructor có một số logic, nhưng phần đó được bao phủ bởi Liath.
Rafał użyński

@ RafałŁużyński: TDD "bởi cuốn sách" là về bài kiểm tra viết đầu tiên . Nó thực sự có nghĩa là luôn luôn viết một bài kiểm tra thất bại trước tiên (không tính tổng số là thất bại). Vì vậy, trước tiên bạn viết một bài kiểm tra gọi hàm tạo ngay cả khi không có hàm tạo . Sau đó, bạn cố gắng biên dịch (không thành công), sau đó bạn triển khai một hàm tạo rỗng, biên dịch, chạy thử nghiệm, result = green. Sau đó, bạn viết bài kiểm tra thất bại đầu tiên và chạy nó - result = red, sau đó bạn thêm chức năng để làm cho bài kiểm tra "xanh" trở lại, v.v.
Doc Brown

Tất nhiên. Tôi không có nghĩa là tôi viết thực hiện đầu tiên, sau đó kiểm tra. Tôi chỉ viết "cách sử dụng" của mã đó ở một mức trên, sau đó kiểm tra mã đó, sau đó tôi thực hiện nó. Tôi thường làm "Bên ngoài TDD".
Rafał użyński
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.