Là sử dụng các bài kiểm tra đơn vị để kể một câu chuyện là một ý tưởng tốt?


13

Vì vậy, tôi có một mô-đun xác thực tôi đã viết cách đây một thời gian. Bây giờ tôi đang nhìn thấy các lỗi của cách của tôi và viết bài kiểm tra đơn vị cho nó. Trong khi viết bài kiểm tra đơn vị, tôi có một thời gian khó khăn để đưa ra tên tốt và khu vực tốt để kiểm tra. Chẳng hạn, tôi có những thứ như

  • Yêu cầuLogin_should_redirect_when_not_logged_in
  • Yêu cầuLogin_should_pass_ENC_when_logged_in
  • Đăng nhập_should_work_when_given_proper_credentials

Cá nhân, tôi nghĩ nó hơi xấu, mặc dù nó có vẻ "phù hợp". Tôi cũng gặp khó khăn trong việc phân biệt giữa các thử nghiệm bằng cách chỉ quét qua chúng (tôi phải đọc tên phương thức ít nhất hai lần để biết điều gì vừa thất bại)

Vì vậy, tôi nghĩ rằng có thể thay vì viết các bài kiểm tra hoàn toàn kiểm tra chức năng, có thể viết một tập các bài kiểm tra bao gồm các tình huống.

Chẳng hạn, đây là một bài kiểm tra mà tôi đã nghĩ ra:

public class Authentication_Bill
{
    public void Bill_has_no_account() 
    { //assert username "bill" not in UserStore
    }
    public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
    { //Calls RequiredLogin and should redirect to login page
    }
    public void Bill_creates_account()
    { //pretend the login page doubled as registration and he made an account. Add the account here
    }
    public void Bill_logs_in_with_new_account()
    { //Login("bill", "password"). Assert not redirected to login page
    }
    public void Bill_can_now_post_comment()
    { //Calls RequiredLogin, but should not kill request or redirect to login page
    }
}

Đây có phải là một nghe nói về mô hình? Tôi đã thấy những câu chuyện chấp nhận và như vậy, nhưng điều này về cơ bản là khác nhau. Sự khác biệt lớn là tôi sẽ đưa ra các kịch bản để "buộc" các bài kiểm tra ra. Thay vì cố gắng tự tìm ra các tương tác có thể mà tôi cần kiểm tra. Ngoài ra, tôi biết rằng điều này khuyến khích các bài kiểm tra đơn vị không kiểm tra chính xác một phương thức và lớp. Tôi nghĩ rằng điều này là OK mặc dù. Ngoài ra, tôi biết rằng điều này sẽ gây ra sự cố cho ít nhất một số khung thử nghiệm, vì họ thường cho rằng các thử nghiệm độc lập với nhau và thứ tự không thành vấn đề (trong trường hợp này sẽ xảy ra).

Dù sao, đây có phải là một mô hình khuyến khích không? Hoặc, đây có phải là một sự phù hợp hoàn hảo cho các thử nghiệm tích hợp API của tôi thay vì thử nghiệm "đơn vị" không? Đây chỉ là một dự án cá nhân, vì vậy tôi mở các thử nghiệm có thể hoặc không thể thực hiện tốt.


4
Các dòng giữa đơn vị, tích hợp và kiểm tra chức năng bị mờ, nếu tôi phải chọn một tên cho cuống thử nghiệm của mình, thì đó là chức năng.
yannis

Tôi nghĩ đó là vấn đề của hương vị. Cá nhân tôi sử dụng tên của bất cứ điều gì tôi kiểm tra có _testgắn thêm và sử dụng các bình luận để ghi chú những kết quả mà tôi mong đợi. Nếu đó là một dự án cá nhân, hãy tìm một số phong cách mà bạn cảm thấy thoải mái và tuân theo nó.
Ông Lister

1
Tôi đã viết một câu trả lời với các chi tiết về cách viết bài kiểm tra đơn vị truyền thống hơn bằng cách sử dụng mẫu Arrange / Act / Assert, nhưng một người bạn đã gặt hái được nhiều thành công khi sử dụng github.com/cucumber/cucumber/wiki/Gherkin , đó là được sử dụng cho thông số kỹ thuật và afaik có thể tạo ra các bài kiểm tra dưa chuột.
Người sử dụng Stuper

Mặc dù tôi sẽ không sử dụng phương pháp mà bạn đã thể hiện với nunit hoặc tương tự, nspec có hỗ trợ xây dựng bối cảnh và thử nghiệm theo tính chất hướng câu chuyện nhiều hơn: nspec.org
Mike

1
thay đổi "Bill" thành "Người dùng" và bạn đã hoàn thành
Steven A. Lowe

Câu trả lời:


15

Có, đó là một ý tưởng tốt để cung cấp cho các bài kiểm tra của bạn tên của các kịch bản ví dụ bạn đang kiểm tra. Và sử dụng công cụ kiểm tra đơn vị của bạn không chỉ đơn thuần là kiểm tra đơn vị cũng có thể ok, rất nhiều người làm điều này thành công (tôi cũng vậy).

Nhưng không, chắc chắn không phải là một ý tưởng tốt để viết các bài kiểm tra của bạn theo cách mà thứ tự thực hiện các bài kiểm tra có vấn đề. Ví dụ, NUnit cho phép người dùng chọn tương tác thử nghiệm nào anh ta muốn thực hiện, do đó, điều này sẽ không hoạt động theo cách dự định nữa.

Bạn có thể tránh điều này một cách dễ dàng ở đây bằng cách tách phần thử nghiệm chính của mỗi thử nghiệm (bao gồm cả "xác nhận") khỏi các bộ phận đặt hệ thống của bạn ở trạng thái ban đầu chính xác. Sử dụng ví dụ của bạn ở trên: viết phương pháp tạo tài khoản, đăng nhập và đăng nhận xét - mà không có bất kỳ khẳng định nào. Sau đó sử dụng lại các phương pháp đó trong các thử nghiệm khác nhau. Bạn cũng sẽ phải thêm một số mã vào [Setup]phương thức kiểm tra đồ đạc của mình để đảm bảo hệ thống ở trạng thái ban đầu được xác định đúng (ví dụ: không có tài khoản nào trong cơ sở dữ liệu, không có ai kết nối cho đến nay, v.v.).

EDIT: Tất nhiên, điều này dường như trái với bản chất "câu chuyện" trong các bài kiểm tra của bạn, nhưng nếu bạn đưa ra phương pháp trợ giúp cho những cái tên có ý nghĩa, bạn sẽ tìm thấy câu chuyện của mình trong mỗi bài kiểm tra.

Vì vậy, nó sẽ trông như thế này:

[TestFixture]
public class Authentication_Bill
{
    [Setup]
    public void Init()
    {  // bring the system in a predefined state, with noone logged in so far
    }

    [Test]
    public void Test_if_Bill_can_create_account()
    {
         CreateAccountForBill();
         // assert that the account was created properly 
    }

    [Test]
    public void Test_if_Bill_can_post_comment_after_login()
    { 
         // here is the "story" now
         CreateAccountForBill();
         LoginWithBillsAccount();
         AddCommentForBill();
        //  assert that the right things happened
    }

    private void CreateAccountForBill()
    {
        // ...
    }
    // ...
}

Tôi sẽ đi xa hơn và nói rằng sử dụng một công cụ xUnit để chạy các bài kiểm tra chức năng là tốt, miễn là bạn không nhầm lẫn công cụ này với loại thử nghiệm và bạn tách các thử nghiệm này ra khỏi các thử nghiệm đơn vị thực tế, để các nhà phát triển có thể vẫn chạy thử nghiệm đơn vị nhanh chóng tại thời điểm cam kết. Đây có thể là chậm hơn nhiều so với các bài kiểm tra đơn vị.
bdsl

4

Một vấn đề với việc kể một câu chuyện bằng các bài kiểm tra đơn vị là nó không nói rõ rằng các bài kiểm tra đơn vị nên được sắp xếp và chạy hoàn toàn độc lập với nhau.

Một thử nghiệm đơn vị tốt phải được cách ly hoàn toàn khỏi tất cả các mã phụ thuộc khác, đó là đơn vị mã nhỏ nhất có thể được kiểm tra.

Điều này mang lại lợi ích cũng như xác nhận mã hoạt động, nếu thử nghiệm thất bại, bạn sẽ có được chẩn đoán chính xác nơi mã sai miễn phí. Nếu một bài kiểm tra không bị cô lập, bạn phải xem xét những gì nó phụ thuộc để tìm ra chính xác những gì đã sai và bỏ lỡ một lợi ích lớn của kiểm tra đơn vị. Có thứ tự của vấn đề thực thi cũng có thể gây ra nhiều tiêu cực sai, nếu một thử nghiệm thất bại thì các thử nghiệm sau có thể thất bại mặc dù mã mà chúng kiểm tra hoạt động hoàn toàn tốt.

Một bài viết tốt sâu hơn là cổ điển về các bài kiểm tra lai bẩn .

Để làm cho các lớp, phương thức và kết quả có thể đọc được, phép thử Art of Unit tuyệt vời sử dụng quy ước đặt tên

Lớp kiểm tra:

ClassUnderTestTests

Phương pháp thử:

Phương thứcUnderTest_Condition_ExpectedResult

Để sao chép ví dụ của @Doc Brown, thay vì sử dụng [Cài đặt] chạy trước mỗi thử nghiệm, tôi viết các phương thức trợ giúp để xây dựng các đối tượng bị cô lập để kiểm tra.

[TestFixture]
public class AuthenticationTests
{
    private Authentication GetAuthenticationUnderTest()
    {
        // create an isolated Authentication object ready for test
    }

    [Test]
    public void CreateAccount_WithValidCredentials_CreatesAccount()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         //Act
         Account result = codeUnderTest.CreateAccount("some", "valid", "data");
         //Assert
         //some assert
    }

    [Test]
    public void CreateAccount_WithInvalidCredentials_ThrowsException()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         Exception result;
         //Act
         try
         {
             codeUnderTest.CreateAccount("some", "invalid", "data");
         }
         catch(Exception e)
         {
             result = e;
         }
         //Assert
         //some assert
    }
}

Vì vậy, các bài kiểm tra thất bại có một tên có ý nghĩa cung cấp cho bạn một số tường thuật về chính xác phương pháp thất bại, điều kiện và kết quả mong đợi.

Đó là cách tôi luôn viết bài kiểm tra đơn vị, nhưng một người bạn đã gặt hái được nhiều thành công với Gerkin .


1
Mặc dù tôi nghĩ rằng đây là một bài viết tốt, tôi không đồng ý về những gì bài báo được liên kết nói về bài kiểm tra "lai". Tất nhiên, có các thử nghiệm tích hợp "nhỏ" (ngoài ra, không phải là các thử nghiệm đơn vị thuần túy ), IMHO có thể rất hữu ích, ngay cả khi chúng không thể cho bạn biết chính xác phương pháp nào chứa mã sai. Nếu các thử nghiệm đó có thể duy trì được tùy thuộc vào mức độ sạch của mã đối với các thử nghiệm đó, thì chúng không bị "bẩn". Và tôi nghĩ mục tiêu của những thử nghiệm đó có thể rất rõ ràng (như trong ví dụ về OP).
Doc Brown

3

Những gì bạn đang mô tả nghe giống như Thiết kế hướng hành vi (BDD) hơn là thử nghiệm đơn vị với tôi. Hãy xem SpecFlow , một công nghệ .NET BDD dựa trên DSL Gherkin .

Những thứ mạnh mẽ mà bất kỳ con người nào cũng có thể đọc / viết mà không biết gì về mã hóa. Nhóm thử nghiệm của chúng tôi đang tận hưởng thành công lớn tận dụng nó cho các bộ thử nghiệm tích hợp của chúng tôi.

Về các quy ước cho các bài kiểm tra đơn vị, câu trả lời của @ DocBrown có vẻ chắc chắn.


Về thông tin, BDD giống hệt như TDD, đó chỉ là phong cách viết thay đổi. Ví dụ: TDD = assert(value === expected)BDD = value.should.equals(expected)+ bạn mô tả các tính năng trong các lớp giải quyết vấn đề "độc lập thử nghiệm đơn vị". Đây là một phong cách tuyệt vời!
Offirmo
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.