Nó có nên là “Sắp xếp-Khẳng định-Hành động-Khẳng định” không?


94

Về mẫu thử nghiệm cổ điển của Sắp xếp-Hành động-Khẳng định , tôi thường thấy mình thêm một khẳng định phản bác đứng trước Hành động. Bằng cách này, tôi biết rằng xác nhận đi qua thực sự được chuyển đi do kết quả của hành động.

Tôi nghĩ về nó tương tự như màu đỏ trong bộ tái cấu trúc màu đỏ-xanh lá cây, chỉ khi tôi nhìn thấy thanh màu đỏ trong quá trình thử nghiệm của mình, tôi mới biết rằng thanh màu xanh lục có nghĩa là tôi đã viết mã tạo ra sự khác biệt. Nếu tôi viết một bài kiểm tra vượt qua, thì bất kỳnào sẽ đáp ứng được nó; tương tự, đối với Sắp xếp-Khẳng định-Hành động-Khẳng định, nếu khẳng định đầu tiên của tôi không thành công, tôi biết rằng bất kỳ Hành động nào cũng sẽ thông qua Khẳng định cuối cùng - vì vậy nó không thực sự xác minh bất kỳ điều gì về Hành động.

Các bài kiểm tra của bạn có tuân theo mô hình này không? Tại sao hoặc tại sao không?

Cập nhật Làm rõ: khẳng định ban đầu về cơ bản ngược lại với khẳng định cuối cùng. Nó không phải là một khẳng định rằng Sắp xếp đã hoạt động; đó là một khẳng định rằng Đạo luật vẫn chưa hoạt động.

Câu trả lời:


121

Đây không phải là điều phổ biến nhất để làm, nhưng vẫn đủ phổ biến để có tên riêng. Kỹ thuật này được gọi là Guard Assertion . Bạn có thể tìm thấy mô tả chi tiết về nó ở trang 490 trong cuốn sách tuyệt vời Các mẫu thử nghiệm xUnit của Gerard Meszaros (rất khuyến khích).

Thông thường, bản thân tôi không sử dụng mẫu này, vì tôi thấy đúng hơn khi viết một bài kiểm tra cụ thể xác nhận bất kỳ điều kiện tiên quyết nào mà tôi cảm thấy cần phải đảm bảo. Một bài kiểm tra như vậy sẽ luôn thất bại nếu điều kiện tiên quyết không thành công và điều này có nghĩa là tôi không cần nó nhúng vào tất cả các bài kiểm tra khác. Điều này giúp tách biệt tốt hơn các mối quan tâm, vì một trường hợp thử nghiệm chỉ xác minh một điều.

Có thể có nhiều điều kiện tiên quyết cần được đáp ứng cho một trường hợp thử nghiệm nhất định, vì vậy bạn có thể cần nhiều hơn một Xác nhận bảo vệ. Thay vì lặp lại những điều đó trong tất cả các bài kiểm tra, việc có một (và một lần duy nhất) cho mỗi điều kiện tiên quyết giúp mã kiểm tra của bạn dễ hiểu hơn, vì bạn sẽ ít lặp lại theo cách đó hơn.


+1, câu trả lời rất hay. Phần cuối cùng đặc biệt quan trọng, vì nó cho thấy rằng bạn có thể bảo vệ mọi thứ như một bài kiểm tra đơn vị riêng biệt.
murrekatt

3
Nói chung tôi cũng đã làm theo cách này nhưng có một vấn đề với việc có một bài kiểm tra riêng để đảm bảo các điều kiện tiên quyết (đặc biệt là với một cơ sở mã lớn với các yêu cầu thay đổi) - bài kiểm tra điều kiện trước sẽ được sửa đổi theo thời gian và không đồng bộ với 'chính' kiểm tra giả định những điều kiện tiên quyết đó. Vì vậy, các điều kiện tiên quyết có thể đều tốt và xanh nhưng những điều kiện tiên quyết đó không được thỏa mãn trong bài kiểm tra chính, hiện luôn hiển thị màu xanh lá cây và tốt. Nhưng nếu các điều kiện tiên quyết là trong bài kiểm tra chính, họ sẽ thất bại. Bạn đã gặp vấn đề này và tìm ra một giải pháp tốt cho nó chưa?
nchaud

2
Nếu bạn thay đổi các bài kiểm tra của mình nhiều, bạn có thể gặp các vấn đề khác , vì điều đó sẽ có xu hướng làm cho các bài kiểm tra của bạn kém tin cậy hơn. Ngay cả khi đối mặt với các yêu cầu thay đổi, hãy xem xét thiết kế mã theo kiểu chỉ dành cho phần phụ .
Mark Seemann

@MarkSeemann Bạn nói đúng, rằng chúng ta phải giảm thiểu sự lặp lại, nhưng ở mặt khác, có thể có rất nhiều thứ, có thể ảnh hưởng đến Sắp xếp cho bài kiểm tra cụ thể, mặc dù bản thân kiểm tra Sắp xếp sẽ vượt qua. Ví dụ, việc dọn dẹp cho bài kiểm tra Sắp xếp hoặc sau khi một Bài kiểm tra khác bị lỗi và Sắp xếp sẽ không giống như trong bài kiểm tra Sắp xếp.
Rekshino

32

Nó cũng có thể được chỉ định là Sắp xếp- Giả sử -Act-Khẳng định.

Có một xử lý kỹ thuật cho điều này trong NUnit, như trong ví dụ ở đây: http://nunit.org/index.php?p=theory&r=2.5.7


1
Đẹp! Tôi thích số thứ tư - và khác - và chính xác - "A". Cảm ơn!
Carl Manaster

+1, @Ole! Tôi cũng thích cái này, cho một số trường hợp đặc biệt! Tôi sẽ thử!
John Tobler

8

Đây là một ví dụ.

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

Nó có thể là tôi viết Range.includes()đơn giản để trả lại sự thật. Tôi đã không, nhưng tôi có thể tưởng tượng rằng tôi có thể có. Hoặc tôi có thể đã viết sai theo bất kỳ cách nào khác. Tôi hy vọng và mong đợi rằng với TDD tôi thực sự đã làm đúng - điều đó includes()chỉ hoạt động - nhưng có lẽ tôi đã không làm như vậy. Vì vậy khẳng định đầu tiên là một kiểm tra sự tỉnh táo, để đảm bảo rằng khẳng định thứ hai thực sự có ý nghĩa.

Tự đọc, assertTrue(range.includes(7));đang nói: "khẳng định rằng phạm vi đã sửa đổi bao gồm 7". Đọc trong ngữ cảnh của khẳng định đầu tiên, nó nói: "khẳng định rằng việc gọi encompass () khiến nó bao gồm 7. Và vì encompass là đơn vị chúng tôi đang thử nghiệm, tôi nghĩ đó là một giá trị (nhỏ) nào đó.

Tôi đang chấp nhận câu trả lời của chính mình; nhiều người khác đã hiểu sai câu hỏi của tôi là về việc kiểm tra thiết lập. Tôi nghĩ điều này hơi khác một chút.


Cảm ơn vì đã quay lại với một ví dụ, Carl. Chà, trong phần màu đỏ của chu kỳ TDD, cho đến khi encompass () thực sự làm được điều gì đó; khẳng định đầu tiên là vô nghĩa, nó chỉ là sự trùng lặp của khẳng định thứ hai. Ở màu xanh lá cây, nó bắt đầu hữu ích. Nó có ý nghĩa trong quá trình tái cấu trúc. Có thể rất tuyệt nếu có một khuôn khổ UT thực hiện điều này một cách tự động.
thiện

Giả sử bạn TDD lớp Range đó, sẽ không có một bài kiểm tra thất bại nào khác khi kiểm tra Range ctor, khi bạn sẽ phá vỡ nó?
thiện

1
@philippe: Tôi không chắc mình hiểu câu hỏi. Hàm tạo Phạm vi và bao gồm () có các bài kiểm tra đơn vị của riêng chúng. Bạn có thể nói rõ hơn được không?
Carl Manaster

Để khẳng định chắc chắnFalse (range.includes (7)) đầu tiên không thành công, bạn cần phải có một khiếm khuyết trong Range Constructor. Vì vậy, tôi muốn hỏi liệu các bài kiểm tra cho hàm tạo Phạm vi sẽ không phá vỡ cùng lúc với khẳng định đó. Và điều gì về việc xác nhận sau Hành động trên một giá trị khác: ví dụ: khẳng địnhFalse (range.includes (6))?
thiện 11/09/09

1
Theo tôi, xây dựng phạm vi xuất hiện trước các hàm như include (). Vì vậy, trong khi tôi đồng ý, chỉ một phương thức khởi tạo bị lỗi (hoặc một hàm include ()) bị lỗi mới khiến xác nhận đầu tiên đó không thành công, thử nghiệm của phương thức khởi tạo sẽ không bao gồm lời gọi bao gồm (). Có, tất cả các chức năng cho đến khẳng định đầu tiên đều đã được kiểm tra. Nhưng khẳng định phủ định ban đầu này đang truyền đạt một điều gì đó, và trong tâm trí tôi, điều gì đó hữu ích. Ngay cả khi mọi khẳng định như vậy trôi qua khi nó được viết ban đầu.
Carl Manaster 11/09/09

7

Một Arrange-Assert-Act-Assertbài kiểm tra luôn có thể được cấu trúc lại thành hai bài kiểm tra:

1. Arrange-Assert

2. Arrange-Act-Assert

Thử nghiệm đầu tiên sẽ chỉ xác nhận điều đó đã được thiết lập trong giai đoạn Sắp xếp và thử nghiệm thứ hai sẽ chỉ xác nhận điều đó đã xảy ra trong giai đoạn Hành động.

Điều này có lợi là đưa ra phản hồi chính xác hơn về việc liệu đó là giai đoạn Sắp xếp hay giai đoạn Hành động không thành công, trong khi ở bản gốc, Arrange-Assert-Act-Assertchúng được tổng hợp lại và bạn sẽ phải tìm hiểu sâu hơn và kiểm tra chính xác xác nhận nào không thành công và tại sao nó không thành công để biết nếu đó là Thỏa thuận hoặc Hành động không thành công.

Nó cũng đáp ứng ý định kiểm tra đơn vị tốt hơn, vì bạn đang tách kiểm tra của mình thành các đơn vị độc lập nhỏ hơn.

Cuối cùng, hãy nhớ rằng bất cứ khi nào bạn thấy các phần Sắp xếp tương tự trong các bài kiểm tra khác nhau, bạn nên cố gắng rút những phần này thành các phương pháp trợ giúp được chia sẻ để các bài kiểm tra của bạn KHÔ hơn và dễ bảo trì hơn trong tương lai.


3

Bây giờ tôi đang làm điều này. AAAA thuộc một loại khác

Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions

Ví dụ về kiểm tra cập nhật:

Arrange: 
    New object as NewObject
    Set properties of NewObject
    Save the NewObject
    Read the object as ReadObject

Act: 
    Change the ReadObject
    Save the ReadObject

Assemble: 
    Read the object as ReadUpdated

Assert: 
    Compare ReadUpdated with ReadObject properties

Lý do là ACT không chứa phần đọc của ReadUpdated là vì nó không phải là một phần của hành động. Hành động chỉ là thay đổi và tiết kiệm. Vì vậy, thực sự, ARRANGE ReadUpdated cho khẳng định, tôi đang gọi ASSEMBLE để khẳng định. Điều này là để tránh nhầm lẫn phần ARRANGE

ASSERT chỉ nên chứa các xác nhận. Điều đó khiến ASSEMBLE giữa ACT và ASSERT thiết lập xác nhận.

Cuối cùng, nếu bạn không đạt trong Sắp xếp, các bài kiểm tra của bạn không đúng vì bạn nên có các bài kiểm tra khác để ngăn chặn / tìm ra những lỗi nhỏ này . Bởi vì đối với tình huống tôi trình bày, nên đã có các bài kiểm tra khác kiểm tra ĐỌC và TẠO. Nếu bạn tạo một "Xác nhận bảo vệ", bạn có thể đang phá vỡ DRY và tạo bảo trì.


1

Đưa ra xác nhận "kiểm tra độ tỉnh táo" để xác minh trạng thái trước khi bạn thực hiện hành động mà bạn đang kiểm tra là một kỹ thuật cũ. Tôi thường viết chúng dưới dạng giàn giáo thử nghiệm để chứng minh với bản thân rằng bài kiểm tra thực hiện những gì tôi mong đợi và loại bỏ chúng sau đó để tránh các bài kiểm tra lộn xộn với giàn giáo thử nghiệm. Đôi khi, việc để nguyên phần đầu bài giúp bài kiểm tra giống như một bài tường thuật.


1

Tôi đã đọc về kỹ thuật này - có thể từ bạn btw - nhưng tôi không sử dụng nó; chủ yếu là vì tôi đã quen với dạng ba A cho các bài kiểm tra đơn vị của mình.

Bây giờ, tôi đang rất tò mò và có một số câu hỏi: bạn viết bài kiểm tra của mình như thế nào, liệu bạn có khiến khẳng định này không thành công, theo chu trình tái cấu trúc màu đỏ-xanh-đỏ-xanh lá cây hay bạn thêm nó sau đó?

Đôi khi bạn bị lỗi, có lẽ sau khi bạn cấu trúc lại mã? Thứ này nói lên điều gì ? Có lẽ bạn có thể chia sẻ một ví dụ về nơi nó hữu ích. Cảm ơn.


Tôi thường không ép buộc xác nhận ban đầu thất bại - sau tất cả, nó sẽ không thất bại, theo cách mà một khẳng định TDD nên, trước khi phương thức của nó được viết. Tôi làm viết nó, khi tôi viết nó, trước khi , chỉ cần trong quá trình bình thường của văn bản kiểm tra, không sau đó. Thành thật mà nói, tôi không thể nhớ nó đã thất bại - có lẽ điều đó cho thấy đó là một sự lãng phí thời gian. Tôi sẽ cố gắng đưa ra một ví dụ, nhưng hiện tại tôi không có trong đầu. Cảm ơn vì những câu hỏi; chúng hữu ích.
Carl Manaster

1

Tôi đã làm điều này trước đây khi điều tra một thử nghiệm không thành công.

Sau khi vò đầu bứt tai, tôi xác định rằng nguyên nhân là do các phương thức được gọi trong quá trình "Sắp xếp" không hoạt động chính xác. Việc thử nghiệm thất bại đã gây hiểu nhầm. Tôi đã thêm một Khẳng định sau khi sắp xếp. Điều này đã làm cho thử nghiệm không thành công ở một nơi làm nổi bật vấn đề thực tế.

Tôi nghĩ rằng cũng có một mùi mã ở đây nếu phần Sắp xếp của bài kiểm tra quá dài và phức tạp.


Một điểm nhỏ: Tôi sẽ xem xét sắp xếp quá phức tạp hơn mùi thiết kế hơn là mùi mã - đôi khi thiết kế đến mức chỉ một Sắp xếp phức tạp mới cho phép bạn kiểm tra thiết bị. Tôi đề cập đến nó bởi vì tình huống đó muốn được khắc phục sâu hơn là một mùi mã đơn giản.
Carl Manaster

1

Nói chung, tôi rất thích "Sắp xếp, Hành động, Khẳng định" và sử dụng nó như một tiêu chuẩn cá nhân của tôi. Tuy nhiên, một điều mà nó không nhắc tôi làm là sắp xếp lại những gì tôi đã sắp xếp khi các xác nhận được thực hiện. Trong hầu hết các trường hợp, điều này không gây nhiều phiền toái, vì hầu hết mọi thứ đều tự động biến mất một cách kỳ diệu thông qua thu gom rác, v.v. Tuy nhiên, nếu bạn đã thiết lập kết nối với các tài nguyên bên ngoài, có thể bạn sẽ muốn đóng các kết nối đó khi hoàn tất. với sự khẳng định của bạn hoặc nhiều người có một máy chủ hoặc tài nguyên đắt tiền ở đâu đó giữ các kết nối hoặc tài nguyên quan trọng mà nó có thể cho người khác. Điều này đặc biệt quan trọng nếu bạn là một trong những nhà phát triển không sử dụng TearDown hoặc TestFixtureTearDownđể làm sạch sau một hoặc nhiều thử nghiệm. Tất nhiên, "Sắp xếp, Hành động, Khẳng định" không chịu trách nhiệm về việc tôi không đóng những gì tôi mở; Tôi chỉ đề cập đến "gotcha" này vì tôi vẫn chưa tìm được từ đồng nghĩa "A-word" cho "vứt bỏ" để giới thiệu! Bất kỳ đề xuất?


1
@carlmanaster, bạn thực sự đủ thân với tôi! Tôi đang gắn điều đó trong TestFixture tiếp theo của mình để thử kích thước. Nó giống như một lời nhắc nhở nhỏ để làm những gì mẹ bạn nên dạy bạn: "Nếu bạn mở nó ra, hãy đóng nó lại! Nếu bạn làm nó lộn xộn, hãy dọn dẹp nó!" Có thể người khác có thể cải thiện nó nhưng ít nhất nó bắt đầu bằng "a!" Cảm ơn đề nghị của bạn!
John Tobler

1
@carlmanaster, tôi đã dùng thử "Annul". Nó tốt hơn "teardown", và nó khá hiệu quả, nhưng tôi vẫn đang tìm kiếm một từ "A" khác mà nó xuất hiện trong đầu tôi một cách hoàn hảo như "Sắp xếp, Hành động, Khẳng định". Có thể là "Hủy diệt ?!"
John Tobler

1
Vì vậy, bây giờ, tôi có "Sắp xếp, Giả định, Hành động, Khẳng định, Hủy bỏ." Hừ! Tôi đang phức tạp hóa mọi thứ, hả? Có lẽ tốt hơn tôi nên BỎ LỠ và quay lại "Sắp xếp, Hành động và Khẳng định!"
John Tobler

1
Có thể sử dụng một R để Đặt lại? Tôi biết nó không phải là A, nhưng nó giống như một tên cướp biển đang nói: Aaargh! và Reset vần với Khẳng định: o
Marcel Valdez Orozco

1

Hãy xem mục nhập của Wikipedia về Thiết kế theo Hợp đồng . Bộ ba thánh Sắp xếp-Hành động-Khẳng định là một nỗ lực để mã hoá một số khái niệm giống nhau và nhằm chứng minh tính đúng đắn của chương trình. Từ bài báo:

The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:

    Acceptable and unacceptable input values or types, and their meanings
    Return values or types, and their meanings
    Error and exception condition values or types that can occur, and their meanings
    Side effects
    Preconditions
    Postconditions
    Invariants
    (more rarely) Performance guarantees, e.g. for time or space used

Có một sự cân bằng giữa số lượng nỗ lực bỏ ra để thiết lập điều này và giá trị mà nó thêm vào. AAA là một lời nhắc hữu ích cho các bước tối thiểu cần thiết nhưng sẽ không ngăn cản bất kỳ ai tạo các bước bổ sung.


0

Phụ thuộc vào môi trường / ngôn ngữ thử nghiệm của bạn, nhưng thông thường nếu một cái gì đó trong phần Sắp xếp không thành công, một ngoại lệ sẽ được đưa ra và thử nghiệm không hiển thị nó thay vì bắt đầu phần Hành động. Vì vậy, không, tôi thường không sử dụng phần Assert thứ hai.

Ngoài ra, trong trường hợp phần Sắp xếp của bạn khá phức tạp và không phải lúc nào cũng có ngoại lệ, bạn có thể cân nhắc gói nó bên trong một số phương pháp và viết một bài kiểm tra riêng cho phần đó, vì vậy bạn có thể chắc chắn rằng nó sẽ không thất bại (nếu không ném một ngoại lệ).


0

Tôi không sử dụng mẫu đó, bởi vì tôi nghĩ làm một cái gì đó như:

Arrange
Assert-Not
Act
Assert

Có thể là vô nghĩa, vì được cho là bạn biết phần Sắp xếp của mình hoạt động chính xác, có nghĩa là bất cứ thứ gì trong phần Sắp xếp đều phải được kiểm tra tốt hoặc đủ đơn giản để không cần kiểm tra.

Sử dụng ví dụ về câu trả lời của bạn:

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7)); // <-- Pointless and against DRY if there 
                                    // are unit tests for Range(int, int)
    range.encompass(7);
    assertTrue(range.includes(7));
}

Tôi e rằng bạn không thực sự hiểu câu hỏi của tôi. Khẳng định ban đầu không phải là về thử nghiệm Sắp xếp; nó chỉ đơn giản là đảm bảo rằng Đạo luật là thứ mang lại trạng thái được khẳng định ở cuối.
Carl Manaster

Và quan điểm của tôi là, bất cứ điều gì bạn đặt trong phần Assert-Not, đều đã được ngụ ý trong phần Sắp xếp, bởi vì mã trong phần Sắp xếp đã được kiểm tra kỹ lưỡng và bạn đã biết nó làm gì.
Marcel Valdez Orozco

Nhưng tôi tin rằng có giá trị trong phần Khẳng định-Không, bởi vì bạn đang nói: Cho rằng phần Sắp xếp để "thế giới" ở "trạng thái này" thì "Hành động" của tôi sẽ để "thế giới" ở "trạng thái mới" này ; và nếu việc thực thi mã mà phần Sắp xếp phụ thuộc vào, thay đổi, thì bài kiểm tra cũng sẽ bị hỏng. Nhưng một lần nữa, điều đó có thể chống lại DRY, bởi vì bạn (nên) cũng có các bài kiểm tra cho bất kỳ mã nào bạn đang tùy thuộc trong phần Sắp xếp.
Marcel Valdez Orozco

Có thể trong các dự án có một số đội (hoặc một nhóm lớn) làm việc trong cùng một dự án, một điều khoản như vậy sẽ khá hữu ích, nếu không thì tôi thấy nó là không cần thiết và thừa.
Marcel Valdez Orozco

Có lẽ mệnh đề như vậy sẽ tốt hơn trong các bài kiểm tra Tích hợp, bài kiểm tra hệ thống hoặc bài kiểm tra chấp nhận, trong đó phần Sắp xếp thường phụ thuộc vào nhiều hơn một thành phần và có nhiều yếu tố hơn có thể khiến trạng thái ban đầu của 'thế giới' thay đổi bất ngờ. Nhưng tôi không thấy có chỗ cho nó trong các bài kiểm tra Đơn vị.
Marcel Valdez Orozco

0

Nếu bạn thực sự muốn kiểm tra mọi thứ trong ví dụ, hãy thử thêm các bài kiểm tra khác ... như:

public void testIncludes7() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
}

public void testIncludes5() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(5));
}

public void testIncludes0() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(0));
}

public void testEncompassInc7() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(7));
}

public void testEncompassInc5() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(5));
}

public void testEncompassInc0() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(0));
}

Bởi vì nếu không, bạn đang thiếu rất nhiều khả năng xảy ra lỗi ... ví dụ sau khi bao gồm, phạm vi chỉ bao gồm 7, v.v. một tập hợp các bài kiểm tra khác hoàn toàn để cố gắng bao gồm 5 trong phạm vi ... chúng ta sẽ mong đợi điều gì - một ngoại lệ trong phạm vi, hoặc phạm vi không thay đổi?

Dù sao, vấn đề là nếu có bất kỳ giả định nào trong hành động mà bạn muốn kiểm tra, hãy đặt chúng vào bài kiểm tra của riêng họ, phải không?


0

Tôi sử dụng:

1. Setup
2. Act
3. Assert 
4. Teardown

Bởi vì một thiết lập sạch là rất quan trọng.

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.