Làm cách nào để kiểm tra một hệ thống mà các đối tượng khó chế giễu?


34

Tôi đang làm việc với hệ thống sau:

Network Data Feed -> Third Party Nio Library -> My Objects via adapter pattern

Gần đây, chúng tôi đã gặp sự cố khi tôi cập nhật phiên bản thư viện tôi đang sử dụng, trong số những thứ khác, khiến dấu thời gian (mà thư viện bên thứ ba trả về là long), bị thay đổi từ mili giây sau epoch thành nano giây sau epoch.

Vấn đề:

Nếu tôi viết các bài kiểm tra giả định các đối tượng của thư viện bên thứ ba, bài kiểm tra của tôi sẽ sai nếu tôi mắc lỗi về các đối tượng của thư viện bên thứ ba. Ví dụ, tôi đã không nhận ra rằng dấu thời gian thay đổi độ chính xác, dẫn đến nhu cầu thay đổi trong bài kiểm tra đơn vị, vì giả của tôi trả về dữ liệu sai. Đây không phải là một lỗi trong thư viện , nó đã xảy ra vì tôi đã bỏ lỡ một cái gì đó trong tài liệu.

Vấn đề là, tôi không thể chắc chắn về dữ liệu chứa trong các cấu trúc dữ liệu này bởi vì tôi không thể tạo dữ liệu thực mà không có nguồn cấp dữ liệu thực. Những đối tượng này lớn và phức tạp và có rất nhiều phần dữ liệu khác nhau trong đó. Các tài liệu cho thư viện bên thứ ba là người nghèo.

Câu hỏi:

Làm cách nào tôi có thể thiết lập các thử nghiệm của mình để kiểm tra hành vi này? Tôi không chắc mình có thể giải quyết vấn đề này trong một bài kiểm tra đơn vị, vì bản thân bài kiểm tra có thể dễ dàng sai. Ngoài ra, hệ thống tích hợp lớn và phức tạp và thật dễ dàng bỏ lỡ điều gì đó. Ví dụ, trong tình huống trên, tôi đã điều chỉnh chính xác việc xử lý dấu thời gian ở một số nơi, nhưng tôi đã bỏ lỡ một trong số chúng. Hệ thống dường như đang thực hiện hầu hết các điều đúng trong thử nghiệm tích hợp của tôi, nhưng khi tôi triển khai nó vào sản xuất (có nhiều dữ liệu hơn), vấn đề trở nên rõ ràng.

Tôi không có một quy trình cho các bài kiểm tra tích hợp của mình ngay bây giờ. Kiểm tra về cơ bản là: cố gắng giữ cho đơn vị kiểm tra tốt, thêm nhiều thử nghiệm khi mọi thứ bị hỏng, sau đó triển khai đến máy chủ thử nghiệm của tôi và đảm bảo mọi thứ có vẻ lành mạnh, sau đó triển khai vào sản xuất. Vấn đề dấu thời gian này đã vượt qua các bài kiểm tra đơn vị vì các giả được tạo sai, sau đó nó đã vượt qua bài kiểm tra tích hợp vì nó không gây ra bất kỳ vấn đề rõ ràng, tức thời nào. Tôi không có bộ phận QA.


3
Bạn có thể "ghi lại" một nguồn cấp dữ liệu thực và "phát" nó sau đó vào thư viện của bên thứ ba không?
Idan Arye

2
Ai đó có thể viết một cuốn sách về các vấn đề như thế này. Trên thực tế, Michael Feathers đã viết cuốn sách đó: c2.com/cgi/wiki?WorkingEffectivelyWithLegacyCode Trong đó, ông mô tả một số kỹ thuật để phá vỡ các phụ thuộc khó khăn để mã có thể trở nên dễ kiểm chứng hơn.
cbojar

2
Các bộ chuyển đổi xung quanh thư viện bên thứ ba? Vâng, đó chính xác là những gì tôi khuyên bạn nên. Những bài kiểm tra đơn vị sẽ không cải thiện mã của bạn. Họ sẽ không làm cho nó đáng tin cậy hơn hoặc dễ bảo trì hơn. Bạn chỉ sao chép một phần mã của người khác tại thời điểm đó; trong trường hợp này, bạn đang sao chép một số mã được viết kém từ âm thanh của nó. Đó là một mất mát ròng. Một số câu trả lời đề nghị thực hiện một số thử nghiệm tích hợp; đó là một ý tưởng tốt nếu bạn chỉ muốn một "Cái này có hiệu quả không?" kiểm tra sự tỉnh táo. Kiểm tra tốt là khó, và nó chỉ cần nhiều kỹ năng và trực giác như mã tốt.
jpmc26

4
Một minh họa hoàn hảo về sự xấu xa của tích hợp. Tại sao không thư viện trả về một Timestamplớp (có chứa bất kỳ đại diện họ muốn) và cung cấp phương pháp đặt tên ( .seconds(), .milliseconds(), .microseconds(), .nanoseconds()) và các nhà xây dựng quá trình đặt tên. Sau đó sẽ không có vấn đề.
Matthieu M.

2
Câu nói "tất cả các vấn đề trong mã hóa có thể được giải quyết bằng một lớp không xác định (tất nhiên, ngoại trừ vấn đề có quá nhiều lớp không xác định)" xuất hiện ở đây ..
Dan Pantry

Câu trả lời:


27

Có vẻ như bạn đã làm việc siêng năng. Nhưng ...

Ở mức thực tế nhất, luôn luôn bao gồm một số ít các bài kiểm tra tích hợp "toàn vòng lặp" trong bộ phần mềm của bạn cho mã của riêng bạn và viết nhiều xác nhận hơn bạn nghĩ. Cụ thể, bạn nên có một số ít các bài kiểm tra thực hiện chu trình tạo-đọc- [do_ ware] -validate đầy đủ.

[TestMethod]
public void MyFormatter_FormatsTimesCorrectly() {

  // this test isn't necessarily about the stream or the external interpreter.
  // but ... we depend on them working how we think they work:
  var stream = new StreamThingy();
  var interpreter = new InterpreterThingy(stream);
  stream.Write("id-123, some description, 12345");

  // this is what you're actually testing. but, it'll also hiccup
  // if your 3rd party dependencies introduce a breaking change.
  var formatter = new MyFormatter(interpreter);
  var line = formatter.getLine();
  Assert.equal(
    "some description took 123.45 seconds to complete (id-123)", line
  );
}

Và có vẻ như bạn đã làm điều này. Bạn chỉ đang đối phó với một thư viện dễ vỡ và / hoặc phức tạp. Và trong trường hợp đó, thật tốt khi đưa vào một số loại thử nghiệm "đây là cách thư viện hoạt động", vừa xác minh sự hiểu biết của bạn về thư viện vừa là ví dụ về cách sử dụng thư viện.

Giả sử bạn cần hiểu và phụ thuộc vào cách trình phân tích cú pháp JSON diễn giải từng "loại" trong chuỗi JSON. Thật hữu ích và tầm thường khi bao gồm một cái gì đó như thế này trong bộ của bạn:

[TestMethod]
public void JSONParser_InterpretsTypesAsExpected() {
  String datastream = "{nbr:11,str:"22",nll:null,udf:undefined}";
  var o = (new JSONParser()).parse(datastream);

  Assert.equal(11, o.nbr);
  Assert.equal(Int32.getType(), o.nbr.getType());
  Assert.equal("22", o.str);
  Assert.equal(null, o.nll);
  Assert.equal(Object.getType(), o.nll.getType());
  Assert.isFalse(o.KeyExists(udf));
}

Nhưng thứ hai, hãy nhớ rằng thử nghiệm tự động dưới bất kỳ hình thức nào, và ở hầu hết mọi mức độ nghiêm ngặt, vẫn sẽ không bảo vệ bạn trước tất cả các lỗi. Nó là hoàn toàn phổ biến để thêm các bài kiểm tra khi bạn phát hiện ra vấn đề. Không có bộ phận QA, điều này có nghĩa là rất nhiều vấn đề sẽ được phát hiện bởi người dùng cuối.

Và ở một mức độ đáng kể, đó chỉ là bình thường.

Và thứ ba, khi một thư viện thay đổi ý nghĩa của giá trị trả về hoặc trường mà không đổi tên trường hoặc phương thức hoặc "phá vỡ" mã phụ thuộc (có thể bằng cách thay đổi loại của nó), tôi sẽ không hài lòng với nhà xuất bản đó. Và tôi tranh luận rằng, mặc dù bạn có lẽ nên đọc thay đổi nếu có, nhưng có lẽ bạn cũng nên chuyển một số căng thẳng của mình cho nhà xuất bản. Tôi cho rằng họ cần những lời chỉ trích hy vọng mang tính xây dựng ...


Ugh, tôi ước nó đơn giản như cho một chuỗi json vào thư viện. Không phải vậy. Tôi không thể thực hiện tương đương (new JSONParser()).parse(datastream), vì họ lấy dữ liệu trực tiếp từ một NetworkInterfacevà tất cả các lớp thực hiện phân tích cú pháp thực tế là gói riêng tư và được bảo vệ.
durron597

Ngoài ra, thay đổi không bao gồm thực tế là họ đã thay đổi dấu thời gian từ ms sang ns, trong số những vấn đề đau đầu khác mà họ không ghi nhận. Vâng, tôi rất không hài lòng với họ, và tôi đã bày tỏ điều này với họ.
durron597

@ durron597 Ồ, nó gần như không bao giờ. Nhưng, bạn thường có thể giả mạo nguồn dữ liệu cơ bản - như trong ví dụ mã đầu tiên. ... Quan điểm là: thực hiện các bài kiểm tra tích hợp toàn vòng khi có thể, kiểm tra sự hiểu biết của bạn về thư viện khi có thể và chỉ cần lưu ý rằng bạn vẫn sẽ để lỗi vào tự nhiên. Và các nhà cung cấp bên thứ 3 của bạn phải chịu trách nhiệm cho việc thực hiện các thay đổi vô hình, phá vỡ.
Svidgen

@ durron597 Tôi không quen với NetworkInterface... đó có phải là thứ bạn có thể cung cấp dữ liệu bằng cách kết nối giao diện với một cổng trên localhost hoặc một cái gì đó không?
Svidgen

NetworkInterface. Đây là một đối tượng cấp thấp để làm việc trực tiếp với card mạng và mở các ổ cắm trên nó, v.v.
durron597 6/10/2015

11

Câu trả lời ngắn gọn: Thật khó. Bạn có thể cảm thấy như không có câu trả lời hay, và đó là vì không có câu trả lời dễ dàng.

Câu trả lời dài: Giống như @ptyx nói , bạn cần kiểm tra hệ thống và kiểm tra tích hợp cũng như kiểm tra đơn vị:

  • Kiểm tra đơn vị là nhanh chóng và dễ dàng để chạy. Họ bắt lỗi trong các phần riêng biệt của mã và sử dụng giả để làm cho chúng có thể chạy được. Do sự cần thiết, họ không thể bắt được sự không khớp giữa các đoạn mã (như mili giây so với nano giây).
  • Kiểm thử tích hợp và kiểm tra hệ thống là chậm (er) và khó (er) để chạy nhưng bắt lỗi nhiều hơn.

Một số gợi ý cụ thể:

  • Có một số lợi ích khi chỉ cần kiểm tra hệ thống để chạy càng nhiều hệ thống càng tốt. Ngay cả khi nó không thể xác nhận rất nhiều hành vi hoặc làm rất tốt trong việc xác định chính xác vấn đề. (Feathers Micheal thảo luận này nhiều hơn trong công tác hiệu quả với Legacy Mã .)
  • Đầu tư vào khả năng kiểm tra giúp. Có một số lượng lớn các kỹ thuật bạn có thể sử dụng ở đây: tích hợp liên tục, tập lệnh, VM, công cụ để phát lại, proxy hoặc chuyển hướng lưu lượng mạng.
  • Một trong những lợi thế (đối với tôi, ít nhất là) đầu tư vào khả năng kiểm tra có thể không rõ ràng: nếu các bài kiểm tra tẻ nhạt, khó chịu hoặc cồng kềnh để viết hoặc chạy, thì tôi quá dễ dàng bỏ qua chúng nếu tôi bị áp lực hay mệt mỏi. Giữ các bài kiểm tra của bạn dưới ngưỡng "Thật dễ dàng đến nỗi không có lý do gì để không làm điều này" là điều quan trọng.
  • Phần mềm hoàn hảo là không khả thi. Giống như mọi thứ khác, nỗ lực dành cho thử nghiệm là một sự đánh đổi và đôi khi nó không đáng để nỗ lực. Các ràng buộc (chẳng hạn như bạn thiếu bộ phận QA) tồn tại. Chấp nhận rằng các lỗi sẽ xảy ra, phục hồi và học hỏi.

Tôi đã thấy lập trình được mô tả là hoạt động tìm hiểu về một vấn đề và không gian giải pháp. Có được mọi thứ hoàn hảo trước thời hạn có thể không khả thi, nhưng bạn có thể học hỏi sau thực tế. ("Tôi đã sửa lỗi xử lý dấu thời gian ở một số nơi nhưng đã bỏ lỡ một. Tôi có thể thay đổi loại dữ liệu hoặc lớp của mình để xử lý dấu thời gian rõ ràng hơn và khó bỏ sót hơn hay để làm cho nó tập trung hơn để tôi chỉ có một nơi để thay đổi? Tôi có thể đơn giản hóa môi trường thử nghiệm của mình để làm cho việc này dễ dàng hơn trong tương lai không? Tôi có thể tưởng tượng một số công cụ sẽ giúp việc này dễ dàng hơn không và nếu có, tôi có thể tìm một công cụ như vậy trên Google không? "V.v.)


7

Tôi đã cập nhật phiên bản của thư viện, thứ mà cảm biến thời gian (mà thư viện bên thứ ba trả về là long), được thay đổi từ mili giây sau epoch thành nano giây sau epoch.

Giáo dục

Đây không phải là một lỗi trong thư viện

Tôi hoàn toàn không đồng ý với bạn ở đây. Đó là một lỗi trong thư viện , một điều khá ngấm ngầm trong thực tế. Họ đã thay đổi loại ngữ nghĩa của giá trị trả về, nhưng không thay đổi loại lập trình của giá trị trả về. Điều này có thể tàn phá tất cả các loại tàn phá, đặc biệt nếu đây là một phiên bản nhỏ, nhưng thậm chí nếu nó là một phiên bản lớn.

Giả sử thay vào đó, thư viện trả về một kiểu MillisecondsSinceEpoch, một trình bao bọc đơn giản chứa a long. Khi họ thay đổi nó thành một NanosecondsSinceEpochgiá trị, mã của bạn sẽ không thể biên dịch và rõ ràng sẽ chỉ bạn đến những nơi bạn cần thực hiện thay đổi. Sự thay đổi không thể âm thầm làm hỏng chương trình của bạn.

Tốt hơn nữa sẽ là một TimeSinceEpochđối tượng có thể điều chỉnh giao diện của nó khi độ chính xác được thêm vào, chẳng hạn như thêm một #toLongNanosecondsphương thức dọc theo #toLongMillisecondsphương thức, không yêu cầu thay đổi mã của bạn.

Vấn đề tiếp theo là bạn không có một bộ kiểm tra tích hợp đáng tin cậy cho thư viện. Bạn nên viết những cái đó. Tốt hơn là tạo một giao diện xung quanh thư viện đó để gói nó khỏi phần còn lại của ứng dụng của bạn. Một số câu trả lời khác giải quyết vấn đề này (và nhiều hơn nữa tiếp tục bật lên khi tôi gõ). Kiểm tra tích hợp nên được chạy ít thường xuyên hơn kiểm tra đơn vị của bạn. Đó là lý do tại sao có một lớp đệm giúp. Phân tách các bài kiểm tra tích hợp của bạn vào một khu vực riêng (hoặc đặt tên chúng theo cách khác) để bạn có thể chạy chúng khi cần, nhưng không phải mỗi khi bạn chạy bài kiểm tra đơn vị của mình.


2
@ durron597 Tôi vẫn sẽ cho rằng đó là một lỗi. Ngoài việc thiếu tài liệu, tại sao lại thay đổi hành vi dự kiến? Tại sao không phải là một phương thức mới cung cấp độ chính xác mới và để phương thức cũ vẫn cung cấp millis? Và tại sao không cung cấp một cách để trình biên dịch cảnh báo bạn thông qua thay đổi trong kiểu trả về? Nó không mất nhiều để làm cho điều này rõ ràng hơn nhiều, không chỉ trong tài liệu, mà còn trong chính mã.
cbojar

1
@gbjbaanb, mà họ có các hoạt động phát hành kém thì dường như là một lỗi với tôi
Arturo Torres Sánchez

2
@gbjbaanb Thư viện của bên thứ 3 [nên] tạo "hợp đồng" với người dùng. Phá vỡ hợp đồng đó - cho dù đó là tài liệu hay không - có thể / nên coi là một lỗi. Như những người khác đã nói, nếu bạn phải thay đổi một cái gì đó, hãy thêm vào hợp đồng với một chức năng / phương thức mới (xem tất cả các ...Ex()phương thức trong Win32API). Nếu điều này không khả thi, "phá vỡ" hợp đồng bằng cách đổi tên chức năng (hoặc loại trả lại của nó) sẽ tốt hơn thay đổi hành vi.
TripeHound 7/10/2015

1
Đây là một lỗi trong thư viện. Sử dụng nano giây trong một thời gian dài đang đẩy nó.
Joshua

1
@gbjbaanb Bạn nói rằng đó không phải là lỗi do hành vi có chủ đích của nó, ngay cả khi không mong muốn. Theo nghĩa đó, nó không phải là một lỗi thực thi , nhưng nó là một lỗi giống nhau. Nó có thể được gọi là lỗi thiết kế hoặc lỗi giao thoa . Các lỗi nằm ở chỗ nó phơi bày một nỗi ám ảnh nguyên thủy với các đơn vị dài thay vì các đơn vị rõ ràng, sự trừu tượng của nó bị rò rỉ khi nó xuất chi tiết về việc thực hiện bên trong của nó (rằng dữ liệu được lưu trữ dưới dạng một đơn vị nhất định) và nó vi phạm nguyên tắc ít ngạc nhiên nhất với sự thay đổi đơn vị tinh tế.
cbojar

5

Bạn cần tích hợp và kiểm tra hệ thống.

Các bài kiểm tra đơn vị là rất tốt về việc xác minh rằng mã của bạn hoạt động như bạn mong đợi. Như bạn nhận ra, không có gì để thách thức các giả định của bạn hoặc đảm bảo kỳ vọng của bạn là lành mạnh.

Trừ khi sản phẩm của bạn có ít tương tác với các hệ thống bên ngoài hoặc tương tác với các hệ thống nổi tiếng, ổn định và được ghi lại rằng chúng có thể bị chế giễu một cách tự tin (điều này hiếm khi xảy ra trong thế giới thực) - các thử nghiệm đơn vị là không đủ.

Các bài kiểm tra của bạn càng ở cấp độ cao, chúng sẽ càng bảo vệ bạn trước những điều không mong muốn. Điều đó phải trả giá (sự tiện lợi, tốc độ, độ giòn ...), vì vậy các bài kiểm tra đơn vị sẽ vẫn là nền tảng của thử nghiệm của bạn, nhưng bạn cần các lớp khác, bao gồm - cuối cùng - một chút thử nghiệm của con người đi một chặng đường dài để nắm bắt những điều ngu ngốc mà không ai nghĩ tới.


2

Điều tốt nhất sẽ là tạo ra một nguyên mẫu tối thiểu và hiểu cách thư viện hoạt động chính xác. Bằng cách đó, bạn sẽ có được một số kiến ​​thức về thư viện với tài liệu kém. Một nguyên mẫu có thể là một chương trình tối giản sử dụng thư viện đó và thực hiện chức năng.

Mặt khác, không có nghĩa gì khi viết các bài kiểm tra đơn vị, với các yêu cầu được xác định một nửa và hiểu biết yếu về hệ thống.

Đối với vấn đề cụ thể của bạn - về việc sử dụng các số liệu sai: Tôi sẽ coi đó là thay đổi yêu cầu. Khi bạn nhận ra vấn đề, thay đổi các bài kiểm tra đơn vị và mã.


1

Nếu bạn đang sử dụng một thư viện ổn định, phổ biến, thì có lẽ bạn có thể cho rằng nó sẽ không giở trò đồi bại với bạn. Nhưng nếu những thứ như những gì bạn mô tả xảy ra với thư viện này, thì rõ ràng, đây không phải là một. Sau trải nghiệm tồi tệ này, mỗi khi có sự cố xảy ra trong tương tác của bạn với thư viện này, bạn sẽ cần kiểm tra không chỉ khả năng bạn đã mắc lỗi mà còn có khả năng thư viện có thể mắc lỗi. Vì vậy, hãy nói rằng đây là một thư viện mà bạn "không chắc chắn".

Một trong những kỹ thuật được sử dụng với các thư viện mà chúng tôi "không chắc chắn" là xây dựng một lớp trung gian giữa hệ thống của chúng tôi và các thư viện nói, tóm tắt chức năng được cung cấp bởi các thư viện, khẳng định rằng những kỳ vọng của chúng tôi về thư viện là đúng, và cũng đơn giản hóa rất nhiều cuộc sống của chúng ta trong tương lai, chúng ta có nên quyết định cho thư viện đó khởi động và thay thế nó bằng một thư viện khác hoạt động tốt hơn.


Điều này không thực sự trả lời câu hỏi. Tôi đã có một lớp ngăn cách thư viện với hệ thống của tôi, nhưng vấn đề là lớp trừu tượng của tôi có thể có "lỗi" khi thư viện thay đổi trên tôi mà không có cảnh báo.
durron597

1
@ durron597 Sau đó, có lẽ lớp không đủ cách ly thư viện với phần còn lại của ứng dụng của bạn. Nếu bạn thấy rằng bạn đang gặp khó khăn khi kiểm tra lớp đó, có lẽ bạn cần đơn giản hóa hành vi và cách ly mạnh mẽ hơn các dữ liệu cơ bản.
cbojar

Những gì @cbojar nói. Ngoài ra, hãy để tôi nhắc lại một cái gì đó có thể không được chú ý trong văn bản trên: assertTừ khóa (hoặc chức năng hoặc cơ sở, tùy thuộc vào ngôn ngữ bạn đang sử dụng,) là bạn của bạn. Tôi không nói về các xác nhận trong các bài kiểm tra đơn vị / tích hợp, tôi đang nói rằng lớp cách ly phải rất nặng nề với các xác nhận, khẳng định mọi thứ có thể khẳng định về hành vi của thư viện.
Mike Nakis

Các xác nhận này không nhất thiết phải thực thi trong quá trình sản xuất, nhưng chúng thực thi trong khi kiểm tra, có chế độ xem hộp trắng của lớp cách ly của bạn và do đó có thể đảm bảo (càng nhiều càng tốt) thông tin mà lớp của bạn nhận được từ thư viện Là âm thanh.
Mike Nakis
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.