Có thể có nhiều khẳng định trong một bài kiểm tra đơn vị không?


397

Trong bình luận cho bài đăng tuyệt vời này , Roy Osherove đã đề cập đến dự án OAPT được thiết kế để chạy từng khẳng định trong một thử nghiệm duy nhất.

Sau đây được viết trên trang chủ của dự án:

Các bài kiểm tra đơn vị thích hợp sẽ thất bại vì chính xác một lý do, đó là lý do tại sao bạn nên sử dụng một xác nhận cho mỗi bài kiểm tra đơn vị.

Và, cũng, Roy đã viết trong các bình luận:

Hướng dẫn của tôi thường là bạn kiểm tra một Ý TƯỞNG logic cho mỗi bài kiểm tra. bạn có thể có nhiều khẳng định trên cùng một đối tượng . chúng thường sẽ là cùng một khái niệm đang được thử nghiệm.

Tôi nghĩ rằng, có một số trường hợp cần nhiều xác nhận (ví dụ: Xác nhận bảo vệ ), nhưng nói chung tôi cố gắng tránh điều này. Ý kiến ​​của bạn là gì? Vui lòng cung cấp một ví dụ thực tế trong đó nhiều xác nhận thực sự cần thiết .


2
Làm thế nào để bạn chế nhạo mà không có nhiều xác nhận? Mỗi kỳ vọng vào bản giả là một sự khẳng định trong chính nó, bao gồm mọi lệnh gọi bạn áp đặt.
Christopher Creutzig

15
Tôi đã từng thấy triết lý một phương thức khẳng định từng bị lạm dụng trong quá khứ. Một đồng nghiệp cũ đã sử dụng một cơ chế thừa kế thú vị để thực hiện điều này. Nó đã dẫn đến rất nhiều lớp con (một lớp trên mỗi nhánh) và rất nhiều bài kiểm tra đã thực hiện cùng một quy trình thiết lập / xé chỉ để kiểm tra các kết quả khác nhau. Nó chậm, khó đọc và là một vấn đề bảo trì nghiêm trọng. Tôi không bao giờ thuyết phục anh ta chuyển trở lại một cách tiếp cận cổ điển hơn. Cuốn sách Gerard Meszaros nói về chủ đề này một cách chi tiết.
Công viên Travis

3
Tôi nghĩ như một quy tắc chung, bạn nên cố gắng giảm thiểu số lượng khẳng định cho mỗi bài kiểm tra. Tuy nhiên, miễn là thử nghiệm thu hẹp đủ vấn đề đến một vị trí cụ thể trong mã, thì đó là một thử nghiệm hữu ích.
Điều

2
Tôi đã thấy các trường hợp sử dụng nhiều xác nhận thay vì RowTest(MbUnit) / TestCase(NUnit) để kiểm tra một loạt các hành vi trường hợp cạnh. Sử dụng các công cụ thích hợp cho công việc! (Thật không may, MSTest dường như chưa có khả năng kiểm tra hàng.)
GalacticCowboy

@GalacticCowboy Bạn có thể có chức năng tương tự RowTestTestCasesử dụng các nguồn dữ liệu thử nghiệm . Tôi đang sử dụng một tệp CSV đơn giản rất thành công.
julealgon

Câu trả lời:


236

Tôi không nghĩ rằng đó thực sự là một điều xấu , nhưng tôi nghĩ chúng ta nên cố gắng chỉ hướng tới những khẳng định duy nhất trong các thử nghiệm của mình. Điều này có nghĩa là bạn viết nhiều bài kiểm tra hơn và các bài kiểm tra của chúng tôi sẽ chỉ kiểm tra một điều duy nhất tại một thời điểm.

Có nói rằng, tôi sẽ nói có thể một nửa các bài kiểm tra của tôi thực sự chỉ có một khẳng định. Tôi nghĩ rằng nó chỉ trở thành một mã (thử nghiệm?) Khi bạn có khoảng năm khẳng định trở lên trong thử nghiệm của mình.

Làm thế nào để bạn giải quyết nhiều khẳng định?


9
Giống như câu trả lời này - nghĩa là nó ổn, nhưng trong trường hợp chung thì không tốt (-:
Murph

5
Ơ Tại sao bạn lại làm vậy? việc thực hiện phương thức hoàn toàn giống nhau?
jgauffin

197
Một xác nhận duy nhất cho mỗi bài kiểm tra đơn vị là một cách tuyệt vời để kiểm tra khả năng cuộn lên xuống của người đọc.
Tom

3
Bất kỳ lý do đằng sau này? Như là, câu trả lời hiện tại này chỉ nêu nó nên là gì, nhưng không phải tại sao.
Steven Jeuris

37
Mạnh mẽ phủ quyết. Câu trả lời không liệt kê bất kỳ lợi thế nào của việc có một xác nhận duy nhất và nhược điểm rõ ràng là bạn cần sao chép-dán chỉ vì một câu trả lời trên internet đã nói như vậy. Nếu bạn cần kiểm tra nhiều trường của một kết quả hoặc nhiều kết quả của một thao tác, bạn hoàn toàn nên khẳng định tất cả chúng trong các xác nhận độc lập, bởi vì nó cung cấp thông tin hữu ích hơn nhiều so với kiểm tra chúng trong một khẳng định blob lớn. Trong một bài kiểm tra tốt, bạn kiểm tra một thao tác, không phải là kết quả của một thao tác.
Peter

296

Các thử nghiệm chỉ thất bại vì một lý do duy nhất, nhưng điều đó không phải lúc nào cũng có nghĩa là chỉ nên có một Asserttuyên bố. IMHO điều quan trọng hơn là phải giữ mẫu " Sắp xếp, Hành động, Khẳng định ".

Điều quan trọng là bạn chỉ có một hành động, và sau đó bạn kiểm tra kết quả của hành động đó bằng cách sử dụng các xác nhận. Nhưng đó là "Sắp xếp, Hành động, Khẳng định, Kết thúc thử nghiệm ". Nếu bạn muốn tiếp tục thử nghiệm bằng cách thực hiện một hành động khác và nhiều xác nhận sau đó, hãy thực hiện thử nghiệm riêng thay thế.

Tôi rất vui khi thấy nhiều tuyên bố khẳng định tạo thành các phần của thử nghiệm cùng một hành động. ví dụ

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

hoặc là

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

Bạn có thể kết hợp những điều này thành một khẳng định, nhưng đó là một điều khác với khẳng định rằng bạn nên hoặc phải . Không có cải tiến từ việc kết hợp chúng.

ví dụ: cái đầu tiên có thể

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

Nhưng điều này không tốt hơn - thông báo lỗi từ nó ít cụ thể hơn và nó không có lợi thế nào khác. Tôi chắc rằng bạn có thể nghĩ về các ví dụ khác khi kết hợp hai hoặc ba (hoặc nhiều hơn) khẳng định vào một điều kiện boolean lớn làm cho nó khó đọc hơn, khó thay đổi hơn và khó tìm ra lý do tại sao nó thất bại. Tại sao làm điều này chỉ vì lợi ích của một quy tắc?

NB : Mã mà tôi đang viết ở đây là C # với NUnit, nhưng các nguyên tắc sẽ tuân theo các ngôn ngữ và khung khác. Cú pháp có thể rất giống nhau quá.


32
Điều quan trọng là bạn chỉ có một hành động, và sau đó bạn kiểm tra kết quả của hành động đó bằng cách sử dụng các xác nhận.
Amitābha

1
Tôi nghĩ nó cũng thú vị, để có nhiều hơn một Assert, nếu Arrange tốn thời gian.
Rekshino

1
@Rekshino, nếu sắp xếp tốn kém thời gian, chúng ta có thể chia sẻ mã sắp xếp, bằng cách đặt mã sắp xếp vào thói quen khởi tạo thử nghiệm.
Shaun Luttin

2
Vì vậy, nếu tôi so sánh với câu trả lời của Jaco, "chỉ một người khẳng định" trở thành "chỉ một nhóm khẳng định" có ý nghĩa hơn đối với tôi.
Walfrat

2
Đây là một câu trả lời tốt, nhưng tôi không đồng ý rằng một khẳng định duy nhất là không tốt hơn. Ví dụ khẳng định xấu không tốt hơn, nhưng điều đó không có nghĩa là một khẳng định duy nhất sẽ không tốt hơn nếu được thực hiện đúng. Nhiều thư viện cho phép xác nhận / đối sánh tùy chỉnh để có thể tạo một cái gì đó nếu chưa có. Ví dụ Assert.IsBetween(10, 100, value)mà in Expected 8 to be between 10 and 100 tốt hơn so với hai riêng biệt khẳng định theo ý kiến của tôi. Bạn chắc chắn có thể lập luận rằng điều đó là không cần thiết, nhưng thường đáng để xem xét liệu có dễ dàng giảm xuống một xác nhận hay không trước khi thực hiện toàn bộ chúng.
Thor84no

85

Tôi chưa bao giờ nghĩ rằng nhiều hơn một khẳng định là một điều xấu.

Tôi làm nó suốt:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Ở đây tôi sử dụng nhiều xác nhận để đảm bảo các điều kiện phức tạp có thể được chuyển thành vị từ dự kiến.

Tôi chỉ thử nghiệm một đơn vị ( ToPredicatephương pháp), nhưng tôi bao quát mọi thứ tôi có thể nghĩ ra trong thử nghiệm.


46
Nhiều khẳng định là xấu vì phát hiện lỗi. Nếu bạn đã thất bại Assert.IsTrue đầu tiên của mình, các xác nhận khác sẽ không được thực thi và bạn sẽ không nhận được bất kỳ thông tin nào từ họ. Mặt khác, nếu bạn có 5 bài kiểm tra thay vì 1 với 5 lần xác nhận, bạn có thể nhận được một cái gì đó hữu ích
Sly

6
Bạn có cho rằng nó vẫn tệ nếu tất cả các xác nhận kiểm tra cùng loại chức năng không? Giống như ở trên, ví dụ kiểm tra các điều kiện và nếu bất kỳ điều này không thành công, bạn nên sửa nó. Có vấn đề gì với bạn rằng bạn có thể bỏ lỡ 2 lần xác nhận cuối cùng nếu một lần trước đó không thành công?
co rúm

106
Tôi sửa từng vấn đề của mình. Vì vậy, thực tế là bài kiểm tra có thể thất bại hơn một lần không làm phiền tôi. Nếu tôi tách chúng ra, tôi sẽ có cùng một lỗi, nhưng tất cả cùng một lúc. Tôi thấy dễ dàng hơn để sửa chữa mọi thứ một bước. Tôi thừa nhận rằng trong trường hợp này, hai khẳng định cuối cùng có thể được tái cấu trúc vào thử nghiệm của riêng họ.
Matt Ellen

19
Trường hợp của bạn rất tiêu biểu, đó là lý do tại sao NUnit có thêm thuộc tính TestCase - nunit.org/?p=testCase&r=2.5
Restuta

10
khung thử nghiệm google C ++ có ASSERT () và EXPECT (). ASSERT () dừng khi thất bại trong khi EXPECT () tiếp tục. Điều đó rất tiện lợi khi bạn muốn xác nhận nhiều hơn một điều trong bài kiểm tra.
ratkok

21

Khi tôi đang sử dụng thử nghiệm đơn vị để xác thực hành vi cấp cao, tôi hoàn toàn đặt nhiều xác nhận vào một thử nghiệm. Đây là một thử nghiệm tôi thực sự đang sử dụng cho một số mã thông báo khẩn cấp. Mã chạy trước khi kiểm tra đặt hệ thống vào trạng thái nếu bộ xử lý chính được chạy, một cảnh báo sẽ được gửi.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Nó đại diện cho các điều kiện cần tồn tại ở mọi bước trong quy trình để tôi tự tin rằng mã đang hành xử theo cách tôi mong đợi. Nếu một xác nhận duy nhất thất bại, tôi không quan tâm rằng những cái còn lại thậm chí sẽ không được chạy; bởi vì trạng thái của hệ thống không còn hiệu lực, những xác nhận sau đó sẽ không cho tôi biết điều gì có giá trị. * Nếu assertAllUnitsAlerting()thất bại, tôi sẽ không biết phải làm gì để assertAllNotificationSent()thành công HOẶC thất bại cho đến khi tôi xác định được nguyên nhân gây ra lỗi trước đó và sửa nó.

(* - Được rồi, chúng có thể có thể hiểu là hữu ích trong việc gỡ lỗi vấn đề. Nhưng thông tin quan trọng nhất, rằng thử nghiệm thất bại, đã được nhận.)


Khi bạn làm điều đó, bạn nên sử dụng các khung kiểm tra với các bài kiểm tra phụ thuộc tốt hơn, tốt hơn (ví dụ: testng hỗ trợ tính năng này)
Kemoda

8
Tôi cũng viết các bài kiểm tra như thế này để bạn có thể tự tin về những gì mã đang làm và thay đổi trạng thái, tôi không nghĩ đây là một bài kiểm tra đơn vị, mà là một bài kiểm tra tích hợp.
mdma

Ý kiến ​​của bạn về việc tái cấu trúc nó thành một assertAlarmStatus (int numberOfAlarmMessages);?
Borjab

1
Khẳng định của bạn sẽ làm cho tên thử nghiệm rất tốt đẹp.
Bjorn Tipling

Tốt hơn là để thử nghiệm chạy, ngay cả trên đầu vào không hợp lệ. Nó cung cấp cho bạn thêm thông tin theo cách đó (và đặc biệt là nếu nó vẫn vượt qua khi bạn không mong đợi nó).
RèmDog

8

Một lý do khác khiến tôi nghĩ rằng, nhiều khẳng định trong một phương thức không phải là một điều xấu được mô tả trong đoạn mã sau:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

Trong thử nghiệm của tôi, tôi chỉ muốn kiểm tra service.process()trả về số chính xác trong Innercác thể hiện của lớp.

Thay vì thử nghiệm ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

tôi đang làm

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}

2
Và đó là một điều tốt, bạn không nên có bất kỳ logic điều kiện nào trong các bài kiểm tra của mình. Nó làm cho bài kiểm tra phức tạp hơn và ít đọc hơn. Và tôi đoán Roy đã phác thảo nó ngay trong bài đăng trên blog của mình rằng nhiều khẳng định trên một đối tượng hầu hết đều ổn. Vì vậy, những người mà bạn có chỉ là người bảo vệ khẳng định và không sao khi có họ.
Restuta

2
Của bạn Assert.notNulllà dư thừa, thử nghiệm của bạn sẽ thất bại với NPE nếu chúng là null.
sara

1
Ngoài ra, ví dụ đầu tiên của bạn (với if) sẽ vượt qua nếu resnull
sara

1
@kai notNull's là dư thừa, tôi đồng ý, nhưng tôi cảm thấy rằng sẽ tốt hơn để có sự khẳng định (và nếu tôi không lười biếng cũng với thông điệp thích hợp) thay vì ngoại lệ ...
Betlista

1
Một xác nhận thất bại cũng đưa ra một ngoại lệ, và trong cả hai trường hợp, bạn nhận được một liên kết trực tiếp đến hàng chính xác đã ném nó với dấu vết ngăn xếp đi kèm, vì vậy cá nhân tôi không muốn làm lộn xộn bài kiểm tra với các điều kiện tiên quyết sẽ được kiểm tra. Tôi thích một oneliner à la Assert.assertEquals(..., service.process().getInner());, có thể với các biến được trích xuất nếu dòng này "quá dài"
sara

6

Tôi nghĩ rằng có rất nhiều trường hợp viết nhiều khẳng định là hợp lệ trong quy tắc rằng một bài kiểm tra chỉ nên thất bại vì một lý do.

Ví dụ, hãy tưởng tượng một hàm phân tích chuỗi ngày:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

Nếu thử nghiệm thất bại là vì một lý do, phân tích cú pháp là không chính xác. Nếu bạn cho rằng thử nghiệm này có thể thất bại vì ba lý do khác nhau, thì IMHO sẽ quá tốt trong định nghĩa của bạn về "một lý do".


3

Tôi không biết về bất kỳ tình huống nào sẽ là một ý tưởng tốt khi có nhiều xác nhận bên trong phương thức [Thử nghiệm]. Lý do chính khiến mọi người muốn có nhiều Xác nhận là vì họ đang cố gắng có một lớp [TestFixture] cho mỗi lớp được kiểm tra. Thay vào đó, bạn có thể chia các bài kiểm tra của mình thành nhiều lớp [TestFixture] hơn. Điều này cho phép bạn xem nhiều cách mà mã có thể không phản ứng theo cách bạn mong đợi, thay vì chỉ một cách mà xác nhận đầu tiên thất bại. Cách bạn đạt được điều này là bạn có ít nhất một thư mục cho mỗi lớp được kiểm tra với rất nhiều lớp [TestFixture] bên trong. Mỗi lớp [TestFixture] sẽ được đặt tên theo trạng thái cụ thể của một đối tượng bạn sẽ kiểm tra. Phương thức [SetUp] sẽ đưa đối tượng vào trạng thái được mô tả bằng tên lớp. Sau đó, bạn có nhiều phương thức [Kiểm tra], mỗi phương thức khẳng định những điều khác nhau mà bạn mong đợi là đúng, với trạng thái hiện tại của đối tượng. Mỗi phương thức [Kiểm tra] được đặt tên theo điều mà nó đang khẳng định, ngoại trừ có lẽ nó có thể được đặt tên theo khái niệm thay vì chỉ đọc mã tiếng Anh. Sau đó, mỗi triển khai phương thức [Kiểm tra] chỉ cần một dòng mã duy nhất trong đó nó đang xác nhận một cái gì đó. Một ưu điểm khác của phương pháp này là nó làm cho các bài kiểm tra rất dễ đọc vì nó trở nên khá rõ ràng những gì bạn đang kiểm tra và những gì bạn mong đợi chỉ bằng cách nhìn vào tên lớp và phương thức. Điều này cũng sẽ mở rộng quy mô tốt hơn khi bạn bắt đầu nhận ra tất cả các trường hợp cạnh nhỏ mà bạn muốn kiểm tra và khi bạn tìm thấy lỗi. ngoại trừ có lẽ nó được đặt tên theo khái niệm thay vì chỉ đọc mã tiếng Anh. Sau đó, mỗi triển khai phương thức [Kiểm tra] chỉ cần một dòng mã duy nhất trong đó nó đang xác nhận một cái gì đó. Một ưu điểm khác của phương pháp này là nó làm cho các bài kiểm tra rất dễ đọc vì nó trở nên khá rõ ràng những gì bạn đang kiểm tra và những gì bạn mong đợi chỉ bằng cách nhìn vào tên lớp và phương thức. Điều này cũng sẽ mở rộng quy mô tốt hơn khi bạn bắt đầu nhận ra tất cả các trường hợp cạnh nhỏ mà bạn muốn kiểm tra và khi bạn tìm thấy lỗi. ngoại trừ có lẽ nó được đặt tên theo khái niệm thay vì chỉ đọc mã tiếng Anh. Sau đó, mỗi triển khai phương thức [Kiểm tra] chỉ cần một dòng mã duy nhất trong đó nó đang xác nhận một cái gì đó. Một ưu điểm khác của phương pháp này là nó làm cho các bài kiểm tra rất dễ đọc vì nó trở nên khá rõ ràng những gì bạn đang kiểm tra và những gì bạn mong đợi chỉ bằng cách nhìn vào tên lớp và phương thức. Điều này cũng sẽ mở rộng quy mô tốt hơn khi bạn bắt đầu nhận ra tất cả các trường hợp cạnh nhỏ mà bạn muốn kiểm tra và khi bạn tìm thấy lỗi. và những gì bạn mong đợi chỉ bằng cách nhìn vào tên lớp và phương thức. Điều này cũng sẽ mở rộng quy mô tốt hơn khi bạn bắt đầu nhận ra tất cả các trường hợp cạnh nhỏ mà bạn muốn kiểm tra và khi bạn tìm thấy lỗi. và những gì bạn mong đợi chỉ bằng cách nhìn vào tên lớp và phương thức. Điều này cũng sẽ mở rộng quy mô tốt hơn khi bạn bắt đầu nhận ra tất cả các trường hợp cạnh nhỏ mà bạn muốn kiểm tra và khi bạn tìm thấy lỗi.

Thông thường, điều này có nghĩa là dòng mã cuối cùng bên trong phương thức [SetUp] sẽ lưu trữ giá trị thuộc tính hoặc giá trị trả về trong một biến đối tượng riêng của [TestFixture]. Sau đó, bạn có thể xác nhận nhiều điều khác nhau về biến thể hiện này từ các phương thức [Kiểm tra] khác nhau. Bạn cũng có thể đưa ra các xác nhận về các thuộc tính khác nhau của đối tượng được thử nghiệm được đặt thành bây giờ nó ở trạng thái mong muốn.

Đôi khi bạn cần phải xác nhận trên đường đi khi bạn đang đưa đối tượng được kiểm tra vào trạng thái mong muốn để đảm bảo bạn không làm phiền trước khi đưa đối tượng vào trạng thái mong muốn. Trong trường hợp đó, các xác nhận bổ sung đó sẽ xuất hiện bên trong phương thức [SetUp]. Nếu có lỗi xảy ra bên trong phương thức [SetUp], thì rõ ràng có điều gì đó không ổn với thử nghiệm trước khi đối tượng rơi vào trạng thái mong muốn mà bạn dự định kiểm tra.

Một vấn đề khác bạn có thể gặp phải là bạn có thể đang kiểm tra Ngoại lệ mà bạn dự kiến ​​sẽ bị ném. Điều này có thể cám dỗ bạn không theo mô hình trên. Tuy nhiên, vẫn có thể đạt được bằng cách bắt ngoại lệ bên trong phương thức [SetUp] và lưu trữ nó vào một biến thể hiện. Điều này sẽ cho phép bạn khẳng định những điều khác nhau về ngoại lệ, mỗi phương thức [Thử nghiệm] của riêng nó. Sau đó, bạn cũng có thể xác nhận những điều khác về đối tượng được thử nghiệm để đảm bảo không có tác dụng phụ ngoài ý muốn từ ngoại lệ được ném ra.

Ví dụ (điều này sẽ được chia thành nhiều tệp):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}

Làm thế nào để bạn xử lý đầu vào TestCase trong trường hợp này?
Simon Gillbee

Vì vậy, tại tổ chức hiện tại của tôi, chúng tôi có hơn 20.000 bài kiểm tra đơn vị rất giống với những gì bạn thể hiện. Đây là một cơn ác mộng. Phần lớn mã thiết lập thử nghiệm được sao chép / dán, dẫn đến thiết lập thử nghiệm không chính xác và các thử nghiệm không hợp lệ vượt qua. Đối với mỗi [Test]phương thức, lớp đó được khởi tạo lại và [SetUp]phương thức được thực hiện lại. Điều này giết chết .NET Garbage Collector và khiến các bài kiểm tra chạy rất chậm: 5+ phút cục bộ, hơn 20 phút trên máy chủ xây dựng. Các bài kiểm tra 20K sẽ chạy trong khoảng 2 - 3 phút. Tôi hoàn toàn không đề xuất phong cách thử nghiệm này, đặc biệt là đối với bộ thử nghiệm lớn.
Fourpastmidnight

@fourpastmidnight hầu hết những gì bạn nói có vẻ như chỉ trích hợp lệ, nhưng quan điểm về sao chép và dán mã thiết lập và do đó là sai, đó không phải là vấn đề về cấu trúc mà là các lập trình viên vô trách nhiệm (có thể là kết quả của người quản lý vô trách nhiệm hoặc môi trường tiêu cực nhiều hơn so với lập trình viên xấu). Nếu mọi người chỉ sao chép và dán mã và hy vọng nó là chính xác và không bận tâm để hiểu mã, trong bất kỳ bối cảnh nào, vì bất kỳ lý do gì, họ cần được đào tạo để không làm điều này hoặc bị bỏ qua nếu họ không thể đào tạo. Điều đó đi ngược lại mọi hiệu trưởng tốt của lập trình.
still_dreaming_1

Nhưng nói chung, tôi đồng ý, đây là sự quá mức điên rồ sẽ dẫn đến rất nhiều sự phình to / hành lý / trùng lặp sẽ dẫn đến tất cả các loại vấn đề. Tôi đã từng bị điên và đề nghị những thứ như thế này. Đó là những gì tôi hy vọng có thể nói mỗi ngày về bản thân mình vào ngày hôm trước bởi vì điều đó có nghĩa là tôi không bao giờ ngừng tìm cách làm việc tốt hơn.
still_dreaming_1

@ still_dreaming_1 wrt "lập trình viên vô trách nhiệm": Tôi đồng ý rằng hành vi này là một vấn đề lớn. Tuy nhiên, loại cấu trúc kiểm tra này thực sự mời loại hành vi này, tốt hơn hoặc xấu hơn. Thực tiễn phát triển xấu sang một bên, sự phản đối chính của tôi đối với hình thức này là nó thực sự giết chết hiệu suất thử nghiệm. Không có gì tệ hơn một bộ thử nghiệm chạy chậm. Bộ kiểm tra chạy chậm có nghĩa là mọi người sẽ không chạy thử nghiệm cục bộ và thậm chí muốn bỏ qua chúng trên các bản dựng trung gian - một lần nữa, vấn đề về con người, nhưng nó xảy ra - tất cả đều có thể tránh được bằng cách đảm bảo bạn có các bài kiểm tra chạy nhanh để bắt đầu với.
bốnpastmidnight

2

Có nhiều xác nhận trong cùng một bài kiểm tra chỉ là vấn đề khi bài kiểm tra thất bại. Sau đó, bạn có thể phải gỡ lỗi bài kiểm tra hoặc phân tích ngoại lệ để tìm ra khẳng định nào không thành công. Với một khẳng định trong mỗi bài kiểm tra, thường dễ dàng xác định những gì sai.

Tôi không thể nghĩ ra một kịch bản trong đó nhiều xác nhận thực sự cần thiết , vì bạn luôn có thể viết lại chúng dưới dạng nhiều điều kiện trong cùng một xác nhận. Tuy nhiên, có thể tốt hơn nếu bạn có một số bước để xác minh dữ liệu trung gian giữa các bước thay vì mạo hiểm rằng các bước sau đó bị sập vì đầu vào xấu.


1
Nếu bạn đang kết hợp nhiều điều kiện thành một khẳng định duy nhất, thì thất bại, tất cả những gì bạn biết là một điều kiện đã thất bại. Với nhiều khẳng định, bạn biết cụ thể về một số trong số chúng (những quyết định đến và bao gồm cả sự thất bại). Xem xét việc kiểm tra một mảng trả về chứa một giá trị duy nhất: kiểm tra nó không phải là null, sau đó nó có chính xác một phần tử và sau đó là giá trị của phần tử đó. (Tùy thuộc vào nền tảng) chỉ cần kiểm tra giá trị ngay lập tức có thể đưa ra một quy định null (ít hữu ích hơn so với xác nhận null không thành công) và không kiểm tra độ dài mảng.
Richard

@Richard: Nhận kết quả và sau đó trích xuất một cái gì đó từ kết quả đó sẽ là một quá trình trong một số bước, vì vậy tôi đã đề cập đến điều đó trong đoạn thứ hai trong câu trả lời.
Guffa

2
Nguyên tắc chung: nếu bạn có nhiều xác nhận trong một bài kiểm tra, mỗi bài sẽ có một thông điệp khác nhau. Sau đó, bạn không có vấn đề này.
Anthony

Và sử dụng một trình chạy thử chất lượng như NCrunch sẽ cho bạn thấy chính xác dòng thử nghiệm thất bại, cả trong mã kiểm tra và mã được kiểm tra.
bốnpastmidnight

2

Nếu thử nghiệm của bạn thất bại, bạn sẽ không biết liệu các xác nhận sau đây cũng sẽ bị hỏng hay không. Thông thường, điều đó có nghĩa là bạn sẽ thiếu thông tin có giá trị để tìm ra nguồn gốc của vấn đề. Giải pháp của tôi là sử dụng một khẳng định nhưng với một vài giá trị:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

Điều đó cho phép tôi thấy tất cả các xác nhận thất bại cùng một lúc. Tôi sử dụng một số dòng vì hầu hết các IDE sẽ hiển thị các chuỗi khác nhau trong một hộp thoại so sánh cạnh nhau.


2

Nếu bạn có nhiều xác nhận trong một chức năng kiểm tra, tôi hy vọng chúng có liên quan trực tiếp đến thử nghiệm bạn đang tiến hành. Ví dụ,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

Có rất nhiều bài kiểm tra (ngay cả khi bạn cảm thấy rằng nó có thể là quá mức cần thiết) không phải là một điều xấu. Bạn có thể lập luận rằng có các bài kiểm tra quan trọng và thiết yếu nhất là quan trọng hơn. Vì vậy, khi bạn đang khẳng định, hãy chắc chắn rằng các câu khẳng định của bạn được đặt chính xác thay vì lo lắng về nhiều khẳng định quá nhiều. Nếu bạn cần nhiều hơn một, sử dụng nhiều hơn một.


1

Mục tiêu của bài kiểm tra đơn vị là cung cấp cho bạn càng nhiều thông tin càng tốt về những gì đang thất bại nhưng cũng để giúp xác định chính xác các vấn đề cơ bản nhất trước tiên. Khi bạn biết một cách hợp lý rằng một xác nhận sẽ thất bại do một xác nhận khác thất bại hoặc nói cách khác, có một mối quan hệ phụ thuộc giữa thử nghiệm, thì sẽ rất hợp lý khi đưa các xác nhận này thành nhiều xác nhận trong một thử nghiệm. Điều này có lợi ích của việc không xả rác các kết quả thử nghiệm với những thất bại rõ ràng có thể bị loại bỏ nếu chúng tôi bảo lãnh cho khẳng định đầu tiên trong một thử nghiệm. Trong trường hợp mối quan hệ này không tồn tại, thì đương nhiên sẽ tách các xác nhận này thành các thử nghiệm riêng lẻ bởi vì nếu không tìm thấy những thất bại này sẽ yêu cầu nhiều lần thử nghiệm để giải quyết tất cả các vấn đề.

Nếu sau đó bạn cũng thiết kế các đơn vị / lớp theo cách mà các bài kiểm tra quá phức tạp sẽ cần phải được viết thì nó sẽ giảm bớt gánh nặng trong quá trình thử nghiệm và có thể thúc đẩy một thiết kế tốt hơn.


1

Có, bạn có thể có nhiều xác nhận miễn là thử nghiệm không cung cấp cho bạn đủ thông tin để có thể chẩn đoán lỗi. Điều này sẽ phụ thuộc vào những gì bạn đang thử nghiệm và các chế độ thất bại là gì.

Các bài kiểm tra đơn vị thích hợp sẽ thất bại vì chính xác một lý do, đó là lý do tại sao bạn nên sử dụng một xác nhận cho mỗi bài kiểm tra đơn vị.

Tôi chưa bao giờ thấy các công thức như vậy là hữu ích (rằng một lớp nên có một lý do để thay đổi là một ví dụ về một câu ngạn ngữ không có ích như vậy). Hãy xem xét một khẳng định rằng hai chuỗi bằng nhau, điều này tương đương về mặt ngữ nghĩa với việc khẳng định rằng độ dài của hai chuỗi là như nhau và mỗi ký tự ở chỉ số tương ứng là bằng nhau.

Chúng ta có thể khái quát và nói rằng bất kỳ hệ thống xác nhận nào cũng có thể được viết lại thành một xác nhận duy nhất và bất kỳ xác nhận đơn lẻ nào cũng có thể được phân tách thành một tập hợp các xác nhận nhỏ hơn.

Vì vậy, chỉ cần tập trung vào sự rõ ràng của mã và sự rõ ràng của kết quả kiểm tra và để điều đó hướng dẫn số lượng xác nhận bạn sử dụng thay vì ngược lại.


0

Câu trả lời rất đơn giản - nếu bạn kiểm tra một hàm thay đổi nhiều hơn một thuộc tính, của cùng một đối tượng hoặc thậm chí hai đối tượng khác nhau và tính chính xác của hàm phụ thuộc vào kết quả của tất cả các thay đổi đó, thì bạn muốn khẳng định rằng mỗi một trong những thay đổi đó đã được thực hiện đúng!

Tôi có ý tưởng về một khái niệm logic, nhưng kết luận ngược lại sẽ nói rằng không có chức năng nào phải thay đổi nhiều hơn một đối tượng. Nhưng đó là điều không thể thực hiện trong mọi trường hợp, theo kinh nghiệm của tôi.

Lấy khái niệm logic về giao dịch ngân hàng - rút một số tiền từ một tài khoản ngân hàng trong hầu hết các trường hợp ĐÃ bao gồm việc thêm số tiền đó vào tài khoản khác. Bạn KHÔNG BAO GIỜ muốn tách hai thứ đó ra, chúng tạo thành một đơn vị nguyên tử. Bạn có thể muốn thực hiện hai chức năng (rút / addMoney) và do đó viết hai bài kiểm tra đơn vị khác nhau - ngoài ra. Nhưng hai hành động đó phải diễn ra trong một giao dịch và bạn cũng muốn đảm bảo rằng giao dịch đó hoạt động. Trong trường hợp đó, đơn giản là không đủ để đảm bảo các bước riêng lẻ đã thành công. Bạn phải kiểm tra cả hai tài khoản ngân hàng, trong bài kiểm tra của bạn.

Có thể có những ví dụ phức tạp hơn mà bạn sẽ không kiểm tra trong bài kiểm tra đơn vị, ngay từ đầu, nhưng thay vào đó là bài kiểm tra tích hợp hoặc chấp nhận. Nhưng những ranh giới đó là trôi chảy, IMHO! Không dễ để quyết định, đó là vấn đề hoàn cảnh và có thể là sở thích cá nhân. Rút tiền từ một và thêm nó vào một tài khoản khác vẫn là một chức năng rất đơn giản và chắc chắn là một ứng cử viên để thử nghiệm đơn vị.


-1

Câu hỏi này liên quan đến vấn đề kinh điển về sự cân bằng giữa các vấn đề mã spaghetti và lasagna.

Có nhiều khẳng định có thể dễ dàng gặp phải vấn đề spaghetti khi bạn không biết bài kiểm tra đó là gì, nhưng việc xác nhận một lần cho mỗi bài kiểm tra có thể khiến bài kiểm tra của bạn không thể đọc được bằng một bài kiểm tra lớn trong một lasagna lớn .

Có một số trường hợp ngoại lệ, nhưng trong trường hợp này giữ con lắc ở giữa là câu trả lời.


-3

Tôi thậm chí không đồng ý với "thất bại chỉ vì một lý do" nói chung. Điều quan trọng hơn là các bài kiểm tra ngắn và đọc rõ ràng imo.

Điều này không phải lúc nào cũng có thể đạt được và khi một bài kiểm tra phức tạp, một tên mô tả (dài) và thử nghiệm ít thứ có ý nghĩa hơn.

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.