Kiểm tra đơn vị giòn do cần chế nhạo quá mức


21

Tôi đã phải vật lộn với một vấn đề ngày càng khó chịu liên quan đến các bài kiểm tra đơn vị của chúng tôi mà chúng tôi đang thực hiện trong nhóm của mình. Chúng tôi đang cố gắng thêm các bài kiểm tra đơn vị vào mã kế thừa không được thiết kế tốt và trong khi chúng tôi không gặp khó khăn gì với việc bổ sung thực tế các bài kiểm tra mà chúng tôi đang bắt đầu đấu tranh với cách các bài kiểm tra được thực hiện.

Như một ví dụ về vấn đề, giả sử bạn có một phương thức gọi 5 phương thức khác là một phần của việc thực thi. Một thử nghiệm cho phương pháp này có thể là để xác nhận rằng một hành vi xảy ra là kết quả của một trong 5 phương thức khác được gọi này. Vì vậy, vì một bài kiểm tra đơn vị nên thất bại chỉ vì một lý do và một lý do duy nhất, bạn muốn loại bỏ các vấn đề tiềm ẩn gây ra bằng cách gọi 4 phương thức khác này và chế nhạo chúng. Tuyệt quá! Thử nghiệm đơn vị thực thi, các phương thức giả định bị bỏ qua (và hành vi của chúng có thể được xác nhận là một phần của các thử nghiệm đơn vị khác) và xác minh hoạt động.

Nhưng có một vấn đề mới - bài kiểm tra đơn vị có kiến ​​thức sâu sắc về cách bạn xác nhận hành vi đó và bất kỳ chữ ký nào thay đổi đối với bất kỳ phương thức nào trong 4 phương pháp khác trong tương lai hoặc bất kỳ phương pháp mới nào cần được thêm vào 'phương thức cha mẹ', sẽ dẫn đến việc phải thay đổi bài kiểm tra đơn vị để tránh những thất bại có thể xảy ra.

Đương nhiên, vấn đề có thể được giảm bớt phần nào bằng cách đơn giản là có nhiều phương pháp thực hiện ít hành vi hơn nhưng tôi đã hy vọng có lẽ có một giải pháp thanh lịch hơn.

Đây là một bài kiểm tra đơn vị mẫu để nắm bắt vấn đề.

Như một lưu ý nhanh 'MergeTests' là lớp kiểm tra đơn vị kế thừa từ lớp chúng tôi đang kiểm tra và ghi đè hành vi khi cần. Đây là một "mẫu" mà chúng tôi sử dụng trong các thử nghiệm của mình để cho phép chúng tôi ghi đè các cuộc gọi đến các lớp / phụ thuộc bên ngoài.

[TestMethod]
public void VerifyMergeStopsSpinner()
{
    var mockViewModel = new Mock<MergeTests> { CallBase = true };
    var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());

    mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
    mockViewModel.Setup(
        m =>
        m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
                         It.IsAny<bool>()));
    mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
    mockViewModel.Setup(m => m.SwitchToOverviewTab());
    mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
    mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
    mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));

    mockViewModel.Object.OnMerge(It.IsAny<MergeState>());    

    mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}

Làm thế nào phần còn lại của bạn xử lý vấn đề này hoặc không có cách xử lý 'đơn giản' tuyệt vời?

Cập nhật - Tôi đánh giá cao phản hồi của mọi người. Thật không may, và thực sự không có gì đáng ngạc nhiên, dường như không có một giải pháp, mô hình hay thực tiễn tuyệt vời nào mà người ta có thể làm theo trong kiểm thử đơn vị nếu mã được kiểm tra kém. Tôi đã đánh dấu câu trả lời tốt nhất nắm bắt sự thật đơn giản này.


Ồ, tôi chỉ thấy thiết lập giả, không có SUT tức thời hay bất cứ điều gì, bạn có đang thử nghiệm bất kỳ triển khai thực tế nào ở đây không? Ai có nghĩa vụ gọi StopSpinner? OnMerge? Bạn nên chế giễu bất kỳ sự phụ thuộc nào mà nó có thể gọi ra nhưng không phải là chính nó ..
Joppe

Hơi khó nhìn một chút, nhưng Mock <MergeTests> là SUT. Chúng tôi đặt cờ CallBase để đảm bảo phương thức 'OnMerge' thực thi trên đối tượng thực tế, nhưng giả định các phương thức được gọi bởi 'OnMerge' có thể khiến thử nghiệm thất bại do các vấn đề phụ thuộc, v.v. Mục tiêu của thử nghiệm là dòng cuối cùng - để xác minh chúng tôi đã dừng spinner trong trường hợp này.
PremiumTier

MergeTests nghe giống như một lớp nhạc cụ khác, không phải là thứ sống trong sản xuất do đó gây nhầm lẫn.
Joppe


1
Hoàn toàn ngoài các vấn đề khác của bạn, có vẻ sai đối với tôi rằng SUT của bạn là Mock <MergeTests>. Tại sao bạn sẽ kiểm tra một Mock? Tại sao bạn không thử nghiệm lớp MergeTests?
Eric King

Câu trả lời:


18
  1. Sửa mã để được thiết kế tốt hơn. Nếu các bài kiểm tra của bạn có những vấn đề này, thì mã của bạn sẽ có vấn đề tồi tệ hơn khi bạn cố gắng thay đổi mọi thứ.

  2. Nếu bạn không thể, thì có lẽ bạn cần ít lý tưởng hơn. Kiểm tra đối với các điều kiện trước và sau của phương pháp. Ai quan tâm nếu bạn đang sử dụng 5 phương pháp khác? Họ có lẽ có các bài kiểm tra đơn vị riêng của họ làm cho nó rõ ràng (er) những gì gây ra sự thất bại khi các bài kiểm tra thất bại.

"kiểm tra đơn vị chỉ nên có một lý do để thất bại" là một hướng dẫn tốt, nhưng theo kinh nghiệm của tôi, không thực tế. Bài kiểm tra khó viết không được viết. Các bài kiểm tra mong manh không được tin tưởng.


Tôi hoàn toàn đồng ý với việc sửa thiết kế mã nhưng trong thế giới phát triển ít lý tưởng hơn cho một công ty lớn với các mốc thời gian chặt chẽ, thật khó để đưa ra trường hợp 'trả hết' nợ kỹ thuật do các đội trong quá khứ hoặc các quyết định kém Một lần. Đối với điểm thứ hai của bạn, phần lớn việc chế giễu không chỉ vì chúng tôi muốn thử nghiệm chỉ thất bại vì một lý do - đó là vì mã được thực thi không được phép thực thi mà không xử lý trước một số lượng lớn các phụ thuộc được tạo bên trong mã đó . Xin lỗi vì đã chuyển mục tiêu bài viết trên đó.
PremiumTier

Nếu một thiết kế tốt hơn là không thực tế, tôi đồng ý với 'Ai quan tâm nếu bạn đang sử dụng 5 phương pháp khác?' Xác minh phương thức thực hiện chức năng được yêu cầu, chứ không phải cách thức thực hiện.
Kwebble

@Kwebble - Tuy nhiên, mục tiêu của câu hỏi là xác định xem có cách nào đơn giản để xác minh hành vi cho một phương thức hay không khi bạn cũng phải chế nhạo các hành vi khác được gọi trong phương thức để chạy thử nghiệm. Tôi muốn xóa 'làm thế nào', nhưng tôi không biết làm thế nào :)
PremiumTier

Không có viên đạn bạc ma thuật. Không có "cách đơn giản" để kiểm tra mã kém. Hoặc mã kiểm tra cần phải được cấu trúc lại, hoặc chính mã kiểm tra, cũng sẽ kém. Thử nghiệm sẽ kém vì nó quá cụ thể với các chi tiết bên trong, như bạn đã chạy vào, hoặc như btilly đề xuất, bạn có thể chạy thử nghiệm đối với môi trường làm việc, nhưng sau đó các thử nghiệm sẽ chậm hơn và phức tạp hơn nhiều. Dù bằng cách nào, các bài kiểm tra sẽ khó viết hơn, khó duy trì hơn và dễ bị âm tính giả.
Steven Dog hành

8

Chia các phương pháp lớn thành các phương pháp nhỏ tập trung hơn chắc chắn là một cách thực hành tốt nhất. Bạn thấy đó là nỗi đau khi xác minh hành vi kiểm tra đơn vị, nhưng bạn cũng đang trải qua nỗi đau theo những cách khác.

Điều đó nói rằng, đó là một dị giáo nhưng cá nhân tôi là một fan hâm mộ của việc tạo ra các môi trường thử nghiệm tạm thời thực tế. Đó là, thay vì chế nhạo mọi thứ ẩn giấu bên trong các phương thức khác đó, hãy đảm bảo rằng có một môi trường tạm thời dễ dàng (hoàn thành với các cơ sở dữ liệu và lược đồ riêng - SQLite có thể giúp bạn ở đây) cho phép bạn chạy tất cả những thứ đó. Trách nhiệm biết cách xây dựng / phá bỏ môi trường thử nghiệm đó với mã yêu cầu, để khi nó thay đổi, bạn không phải thay đổi tất cả mã kiểm tra đơn vị phụ thuộc vào sự tồn tại của nó.

Nhưng tôi lưu ý rằng đây là một dị giáo về phía tôi. Những người tập trung vào thử nghiệm đơn vị ủng hộ các thử nghiệm đơn vị "thuần túy" và gọi những gì tôi mô tả là "thử nghiệm tích hợp". Cá nhân tôi không lo lắng về sự khác biệt đó.


3

Tôi sẽ xem xét nới lỏng các giả và chỉ xây dựng các bài kiểm tra có thể bao gồm các phương pháp mà nó gọi ra.

Đừng kiểm tra làm thế nào , kiểm tra những gì . Đó là kết quả quan trọng, bao gồm các phương thức phụ nếu cần.

Từ một góc độ khác, bạn có thể xây dựng một bài kiểm tra, làm cho nó vượt qua với một phương thức lớn, tái cấu trúc và kết thúc với một cây phương thức sau khi tái cấu trúc. Bạn không cần phải kiểm tra từng người một cách cô lập. Đó là kết quả cuối cùng mà tính.

Nếu các phương thức phụ làm cho việc kiểm tra một số khía cạnh trở nên khó khăn, hãy cân nhắc việc tách chúng ra để tách các lớp để bạn có thể chế nhạo chúng ở đó sạch hơn mà không cần lớp của bạn được kiểm tra có nhiều dụng cụ / đường may. Thật khó để biết liệu bạn có thực sự đang thử nghiệm bất kỳ triển khai cụ thể nào trong thử nghiệm mẫu của bạn hay không.


Vấn đề là chúng ta phải chế giễu 'làm thế nào' để kiểm tra 'cái gì'. Đó là một giới hạn được áp đặt bởi thiết kế của mã. Tôi chắc chắn không muốn 'chế giễu' làm thế nào vì đó là điều làm cho bài kiểm tra trở nên dễ vỡ.
PremiumTier

Nhìn vào tên phương thức tôi nghĩ rằng lớp thử nghiệm của bạn chỉ đơn giản là đảm nhận quá nhiều trách nhiệm. Đọc lên trên nguyên tắc trách nhiệm duy nhất. Mượn từ MVC có thể giúp một chút, lớp của bạn dường như xử lý cả các vấn đề về UI, cơ sở hạ tầng và kinh doanh.
Joppe

Yeah :( Đó sẽ là mã kế thừa được thiết kế kém mà tôi đã đề cập. Chúng tôi đang nghiên cứu thiết kế lại và tái cấu trúc nhưng chúng tôi cảm thấy tốt nhất nên đặt nguồn thử nghiệm trước.
PremiumTier
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.