2014-10-25 10 views
5

Đối với những người không chắc chắn ý nghĩa của 'ràng buộc không xác định' tôi khuyên bạn nên đánh dấu post của Mark Seeman.Phát hiện các kiểm tra 'đã chết' và dữ liệu mã cứng so với giới hạn không xác định

Bản chất của ý tưởng là thử nghiệm có giá trị xác định chỉ dành cho dữ liệu ảnh hưởng đến hành vi SUT. Không phải dữ liệu 'có liên quan' có thể ở mức độ nào đó 'ngẫu nhiên'.

Tôi thích phương pháp này. Càng có nhiều dữ liệu thì trừu tượng càng trở nên rõ ràng và mang tính kỳ vọng hơn và thực sự nó trở nên khó khăn hơn để vô tình phù hợp với dữ liệu để thử nghiệm.

Tôi đang cố gắng để 'bán' phương pháp này (cùng với AutoFixture) cho các đồng nghiệp của tôi và ngày hôm qua chúng tôi đã có một cuộc tranh luận dài về nó.
Họ đề xuất đối số thú vị về không ổn định khó khăn để gỡ lỗi kiểm tra do dữ liệu ngẫu nhiên.
Lúc đầu, có vẻ hơi lạ vì tất cả chúng ta đều đồng ý rằng luồng dữ liệu ảnh hưởng không được ngẫu nhiên và hành vi đó là không thể. Tuy nhiên tôi đã nghỉ ngơi để suy nghĩ kỹ về mối quan tâm đó. Và cuối cùng tôi đã đến vấn đề sau:

Nhưng một số các giả định của tôi đầu tiên:

  1. mã kiểm tra phải được xử lý như mã sản xuất.
  2. Mã kiểm tra PHẢI thể hiện đúng kỳ vọng và thông số kỹ thuật của hành vi hệ thống.
  3. Không có gì cảnh báo bạn về những mâu thuẫn tốt hơn so với bản dựng bị hỏng (hoặc không được biên soạn hoặc thử nghiệm không thành công - kiểm tra có cổng).

xem xét hai biến thể này của bài kiểm tra tương tự:

[TestMethod] 
public void DoSomethig_RetunrsValueIncreasedByTen() 
{ 
    // Arrange 
    ver input = 1; 
    ver expectedOutput = input+10; 

    var sut = new MyClass(); 

    // Act 
    var actualOuptut = sut.DoeSomething(input); 

    // Assert 
    Assert.AreEqual(expectedOutput,actualOutput,"Unexpected return value."); 
} 

/// Here nothing is changed besides input now is random. 
[TestMethod] 
public void DoSomethig_RetunrsValueIncreasedByTen() 
{ 
    // Arrange 
    var fixture = new Fixture(); 
    ver input = fixture.Create<int>(); 
    ver expectedOutput = input+10; 

    var sut = new MyClass(); 

    // Act 
    var actualOuptut = sut.DoeSomething(input); 

    // Assert 
    Assert.AreEqual(expectedOutput,actualOutput,"Unexpected return value."); 
} 

Cho đến nay rất thần, mọi thứ hoạt động và cuộc sống là đẹp nhưng sau đó yêu cầu thay đổi và DoSomething thay đổi hành vi của nó: bây giờ nó chỉ làm tăng đầu vào nếu nó 'thấp hơn 10 và nhân với 10 cách khác. Điều gì xảy ra ở đây? Các thử nghiệm với dữ liệu hardcoded đi (thực sự vô tình), trong khi thử nghiệm thứ hai không thành công đôi khi. Và cả hai đều là những thử nghiệm lừa dối sai: họ kiểm tra hành vi không tồn tại.

Có vẻ như không quan trọng dữ liệu được mã hóa cứng hoặc ngẫu nhiên: chỉ là không liên quan. Tuy nhiên, chúng tôi không có cách nào mạnh mẽ để phát hiện các thử nghiệm 'chết' đó.

Vì vậy, câu hỏi là:

Có ai lời khuyên tốt làm thế nào để viết bài kiểm tra theo cách tình huống như vậy không xuất hiện?

Trả lời

5

"sau đó yêu cầu thay đổi và DoSomething thay đổi hành vi của nó"

Liệu nó, thực sự? Nếu DoSomething hành vi thay đổi, vi phạm Open/Closed Principle (OCP). Bạn có thể quyết định không quan tâm đến điều này, nhưng nó liên quan chặt chẽ đến why we trust tests.

Mỗi khi bạn thay đổi các kiểm tra hiện tại, bạn sẽ giảm độ tin cậy của chúng. Mỗi khi bạn thay đổi hành vi sản xuất hiện tại, bạn cần phải xem lại tất cả các thử nghiệm liên lạc với mã sản xuất đó. Lý tưởng nhất, bạn cần phải truy cập vào mỗi bài kiểm tra như vậy, và một thời gian ngắn thay đổi việc thực hiện để thấy rằng nó vẫn thất bại nếu việc thực hiện là sai.

Đối với những thay đổi nhỏ, điều này có thể vẫn còn thực tế, nhưng đối với những thay đổi vừa phải, sẽ càng khôn ngoan hơn để tuân thủ OCP: không thay đổi hành vi hiện tại; thêm hành vi mới cạnh nhau và để cho hành vi teo cũ. Trong ví dụ trên, có thể rõ ràng rằng kiểm tra AutoFixture có thể không xác định sai, nhưng ở một mức độ khái niệm hơn, hoàn toàn có thể là, nếu bạn thay đổi hành vi sản xuất mà không xem xét lại, một số xét nghiệm có thể âm thầm biến vào false negatives. Đây là một vấn đề chung liên quan đến thử nghiệm đơn vị, và không cụ thể đối với AutoFixture.

+0

+1 (Giả sử mã là RẮN.) –

+0

Đồng ý một phần. Vẫn khó có thể tưởng tượng được có bao nhiêu thành phần (hoặc mức độ thừa kế) sẽ có nếu các yêu cầu tự nhiên không ổn định (ví dụ pháp luật) nhưng không thay đổi triệt để mỗi lần và do đó cho phép tái sử dụng. –

+2

@voroninp Điều về các miền không ổn định vốn có là những miền này đặc biệt là các miền mà OCP có giá trị, bởi vì OCP cho phép bạn giữ nguyên logic cũ tại chỗ. Điều này một lần nữa cho phép bạn tính toán lại những thứ * đã * trong quá khứ. Nó cũng cho phép bạn khôi phục lại một quy tắc cũ, trong trường hợp bất cứ ai thay đổi suy nghĩ của họ. –

6

Câu trả lời là thực sự ẩn trong câu này:

[..] sau đó yêu cầu thay đổi và DoSomething thay đổi hành vi của nó [..]

Nó sẽ không được dễ dàng hơn nếu bạn làm điều đó theo cách này:

  • thay đổi Đầu tiên expectedOutput, để đáp ứng yêu cầu mới.
  • Tuân thủ thử nghiệm không thành công — điều quan trọng là phải thấy nó không thành công.
  • Chỉ sau đó sửa đổi DoSomething theo yêu cầu mới — hãy thực hiện lại kiểm tra.

Phương pháp này không liên quan đến một công cụ cụ thể như AutoFixture, nó chỉ là Phát triển theo hướng thử nghiệm.


Trường hợp AutoFixture là thực sự hữu ích sau đó? Sử dụng AutoFixture, bạn có thể giảm thiểu phần Sắp xếp của thử nghiệm.

Đây là thử nghiệm ban đầu, được viết idiomatically sử dụng AutoFixture.Xunit:

[Theory, InlineAutoData] 
public void DoSomethingWhenInputIsLowerThan10ReturnsCorrectResult(
    MyClass sut, 
    [Range(int.MinValue, 9)]int input) 
{ 
    Assert.True(input < 10); 
    var expected = input + 1; 

    var actual = sut.DoSomething(input); 

    Assert.Equal(expected, actual); 
} 

[Theory, InlineAutoData] 
public void DoSomethingWhenInputIsEqualsOrGreaterThan10ReturnsCorrectResult(
    MyClass sut, 
    [Range(10, int.MaxValue)]int input) 
{ 
    Assert.True(input >= 10); 
    var expected = input * 10; 

    var actual = sut.DoSomething(input); 

    Assert.Equal(expected, actual); 
} 

Ngoài ra, bên cạnh xUnit.net, đó cũng là support for NUnit 2.

HTH

+0

Vâng, có một vấn đề ở đây. Khi điều kiện phân nhánh trở nên khá phức tạp, bạn thường tạo ra một số kiểm tra - mỗi kiểm tra trên mỗi nhánh điều kiện. Bạn có thể viết các bài kiểm tra cho các chi nhánh mới nhưng quên về các phương pháp cũ –

+0

Chủ yếu là tôi đồng ý với quan điểm của bạn, nhưng tôi nghĩ tốt hơn là thể hiện mong đợi luồng công việc trong trình tạo dữ liệu thử nghiệm. Về cơ bản nó sẽ trả về dữ liệu theo phương thức 'GetSomeInput'. Sau đó, khi thay đổi hành vi, chúng tôi xóa phương thức đó và thêm hai phương thức khác 'GetLessThan10' và 'GetEqualOrGreaterThan10'. Và loại bỏ phương pháp đầu tiên sẽ phá vỡ thử nghiệm ban đầu: nó sẽ không biên dịch. –

+5

Giới thiệu logic phân nhánh trong thử nghiệm hầu như không phải là ý tưởng hay ... –

Các vấn đề liên quan