Làm thế nào để bạn có hiệu quả giữ cho các bài kiểm tra của bạn làm việc khi bạn thiết kế lại?


14

Một cơ sở mã được kiểm tra tốt có một số lợi ích, nhưng việc kiểm tra các khía cạnh nhất định của hệ thống dẫn đến một cơ sở mã có khả năng chống lại một số loại thay đổi.

Một ví dụ là kiểm tra đầu ra cụ thể - ví dụ: văn bản hoặc HTML. Các thử nghiệm thường được viết (ngây thơ?) Để mong đợi một khối văn bản cụ thể làm đầu ra cho một số tham số đầu vào hoặc để tìm kiếm các phần cụ thể trong một khối.

Thay đổi hành vi của mã, để đáp ứng các yêu cầu mới hoặc do kiểm tra khả năng sử dụng đã dẫn đến thay đổi giao diện, cũng yêu cầu thay đổi các thử nghiệm - thậm chí có thể các thử nghiệm không phải là thử nghiệm đơn vị cụ thể cho mã bị thay đổi.

  • Làm thế nào để bạn quản lý công việc tìm và viết lại các bài kiểm tra này? Điều gì sẽ xảy ra nếu bạn không thể "chạy hết tất cả và để khung sắp xếp chúng ra"?

  • Những loại khác của kết quả kiểm tra mã trong các bài kiểm tra dễ vỡ thường xuyên?


Điều này khác biệt đáng kể như thế nào so với lập trình viên.stackexchange.com/questions/5898/ trên ?
HỎI

4
Câu hỏi đó đã hỏi nhầm về tái cấu trúc - các bài kiểm tra đơn vị nên bất biến khi tái cấu trúc.
Alex Feinman

Câu trả lời:


9

Tôi biết những người TDD sẽ ghét câu trả lời này, nhưng một phần lớn trong số đó đối với tôi là chọn cẩn thận nơi để kiểm tra một cái gì đó.

Nếu tôi phát điên với các bài kiểm tra đơn vị ở các tầng thấp hơn thì không thể thay đổi có ý nghĩa mà không làm thay đổi các bài kiểm tra đơn vị. Nếu giao diện không bao giờ bị lộ và không có ý định sử dụng lại bên ngoài ứng dụng thì đây chỉ là chi phí không cần thiết cho những gì có thể là một sự thay đổi nhanh chóng.

Ngược lại, nếu những gì bạn đang cố gắng thay đổi được phơi bày hoặc sử dụng lại mỗi một trong những thử nghiệm mà bạn sẽ phải thay đổi là bằng chứng về điều gì đó bạn có thể phá vỡ ở nơi khác.

Trong một số dự án, điều này có thể giúp thiết kế các bài kiểm tra của bạn từ cấp độ chấp nhận xuống thay vì từ đơn vị kiểm tra trở lên. và có ít bài kiểm tra đơn vị hơn và nhiều bài kiểm tra phong cách tích hợp hơn.

Điều đó không có nghĩa là bạn vẫn không thể xác định một tính năng và mã cho đến khi tính năng đó đáp ứng các tiêu chí chấp nhận của nó. Điều đó đơn giản có nghĩa là trong một số trường hợp, bạn không kết thúc việc đo các tiêu chí chấp nhận bằng các bài kiểm tra đơn vị.


Tôi nghĩ bạn có nghĩa là viết "bên ngoài mô-đun", chứ không phải "bên ngoài ứng dụng".
SamB

SamB, nó phụ thuộc. Nếu giao diện là nội bộ ở một vài nơi với một ứng dụng, nhưng không công khai, tôi sẽ xem xét thử nghiệm ở cấp cao hơn nếu tôi nghĩ giao diện có thể không ổn định.
Hóa đơn

Tôi đã tìm thấy phương pháp này rất tương thích với TDD. Tôi thích bắt đầu ở các lớp trên của ứng dụng gần hơn với người dùng cuối để tôi có thể thiết kế các lớp thấp hơn để biết các lớp trên cần sử dụng các lớp thấp hơn như thế nào. Về cơ bản, việc xây dựng từ trên xuống cho phép bạn thiết kế chính xác hơn giao diện giữa lớp này và lớp khác.
Greg Burghardt

4

Tôi vừa hoàn thành một cuộc đại tu lớn của ngăn xếp SIP của mình, viết lại toàn bộ vận chuyển TCP. (Đây là một công cụ tái cấu trúc gần, ở quy mô khá lớn, so với hầu hết các công cụ tái cấu trúc.)

Tóm lại, có TIdSipTcpTransport, lớp con của TIdSipTransport. Tất cả TIdSipTransports chia sẻ một bộ thử nghiệm chung. Nội bộ cho TIdSipTcpTransport là một số lớp - bản đồ chứa các cặp thông báo kết nối / khởi tạo, máy khách TCP luồng, máy chủ TCP luồng, v.v.

Đây là những gì tôi đã làm:

  • Đã xóa các lớp tôi sẽ thay thế.
  • Đã xóa các bộ kiểm tra cho các lớp.
  • Còn lại bộ thử nghiệm dành riêng cho TIdSipTcpTransport (và vẫn còn bộ thử nghiệm chung cho tất cả TIdSipTransports).
  • Chạy thử nghiệm TIdSipTransport / TIdSipTcpTransport, để đảm bảo tất cả đều thất bại.
  • Đã nhận xét tất cả trừ một bài kiểm tra TIdSipTransport / TIdSipTcpTransport.
  • Nếu tôi cần thêm một lớp, tôi sẽ thêm nó viết các bài kiểm tra để xây dựng đủ chức năng mà bài kiểm tra không bị lỗi duy nhất vượt qua.
  • Lót, rửa sạch, lặp lại.

Do đó, tôi biết những gì tôi vẫn cần phải làm, dưới dạng các bài kiểm tra nhận xét (*) và biết rằng mã mới đang hoạt động như mong đợi, nhờ các bài kiểm tra mới tôi đã viết.

(*) Thực sự, bạn không cần phải nhận xét chúng. Đừng chạy chúng; 100 bài kiểm tra thất bại không đáng khích lệ. Ngoài ra, trong thiết lập cụ thể của tôi, biên dịch ít bài kiểm tra hơn có nghĩa là vòng lặp kiểm tra-ghi-tái cấu trúc nhanh hơn.


Tôi đã làm điều này quá nhiều tháng trước và nó hoạt động khá tốt đối với tôi. Tuy nhiên, tôi hoàn toàn không thể áp dụng phương pháp này khi ghép nối với một đồng nghiệp trong việc thiết kế lại mô-đun mô hình miền của chúng tôi (điều này đã kích hoạt việc thiết kế lại tất cả các mô-đun khác trong dự án).
Marco Ciambrone

3

Khi các bài kiểm tra rất mong manh, tôi thường thấy vì tôi đang kiểm tra sai. Lấy ví dụ, đầu ra HTML. Nếu bạn kiểm tra đầu ra HTML thực tế, bài kiểm tra của bạn sẽ rất mong manh. Nhưng bạn không quan tâm đến đầu ra thực tế, bạn quan tâm đến việc liệu nó có truyền đạt thông tin cần thiết hay không. Thật không may, làm điều đó đòi hỏi phải xác nhận về nội dung trong bộ não của người dùng và do đó không thể được thực hiện tự động.

Bạn có thể:

  • Tạo HTML dưới dạng thử nghiệm khói để đảm bảo nó thực sự chạy
  • Sử dụng một hệ thống mẫu, do đó bạn có thể kiểm tra bộ xử lý mẫu và dữ liệu được gửi đến mẫu mà không thực sự kiểm tra chính xác mẫu.

Điều tương tự xảy ra với SQL. Nếu bạn xác nhận SQL thực tế, các lớp của bạn sẽ cố gắng khiến bạn gặp rắc rối. Bạn thực sự muốn khẳng định kết quả. Do đó, tôi sử dụng cơ sở dữ liệu bộ nhớ SQLITE trong các bài kiểm tra đơn vị của mình để đảm bảo rằng SQL của tôi thực sự làm những gì nó được yêu cầu.


Nó cũng có thể giúp sử dụng HTML cấu trúc.
SamB

@SamB chắc chắn điều đó sẽ giúp ích, nhưng tôi không nghĩ nó sẽ giải quyết vấn đề hoàn toàn
Winston Ewert

tất nhiên là không, không có gì có thể :-)
SamB

-1

Trước tiên, hãy tạo API MỚI, đó là những gì bạn muốn hành vi API MỚI của mình. Nếu điều đó xảy ra là API mới này có cùng tên với API OLTER, thì tôi sẽ thêm tên _NEW vào tên API mới.

int DoS SomethingInterestingAPI ();

trở thành:

int DoS SomethingInterestingAPI_NEW (int takes_more_argument); int DoS SomethingInterestingAPI_OLD (); int DoS SomethingInterestingAPI () {DoS SomethingInterestingAPI_NEW (anything_default_mimics_the_old_API); OK - ở giai đoạn này - tất cả các bài kiểm tra hồi quy của bạn đều vượt qua - sử dụng tên DoS SomethingInterestingAPI ().

TIẾP THEO, xem qua mã của bạn và thay đổi tất cả các cuộc gọi thành DoS SomethingInterestingAPI () thành biến thể thích hợp của DoS SomethingInterestingAPI_NEW (). Điều này bao gồm cập nhật / viết lại bất kỳ phần nào trong các bài kiểm tra hồi quy của bạn cần được thay đổi để sử dụng API mới.

TIẾP THEO, đánh dấu DoS SomethingInterestingAPI_OLD () là [[deprecated ()]]. Giữ xung quanh API không dùng nữa miễn là bạn muốn (cho đến khi bạn cập nhật an toàn tất cả các mã có thể phụ thuộc vào nó).

Với phương pháp này, mọi thất bại trong kiểm tra hồi quy của bạn chỉ đơn giản là các lỗi trong kiểm tra hồi quy đó hoặc xác định các lỗi trong mã của bạn - chính xác như bạn muốn. Quá trình dàn dựng này để sửa đổi API bằng cách tạo rõ ràng các phiên bản _NEW và _OLD của API cho phép bạn có các bit của mã mới và mã cũ cùng tồn tại trong một thời gian.

Dưới đây là một ví dụ tốt (khó) của phương pháp này trong thực tế. Tôi đã có chức năng BitSub chuỗi () - trong đó tôi đã sử dụng cách tiếp cận để có tham số thứ ba là COUNT bit trong chuỗi con. Để phù hợp với các API và mẫu khác trong C ++, tôi muốn chuyển sang bắt đầu / kết thúc làm đối số cho hàm.

https://github.com/SophistSolutions/Stroika/commit/003dd8707405c43e735ca71116c773b108c217c0

Tôi đã tạo một hàm BitSubopes_NEW với API mới và cập nhật tất cả mã của mình để sử dụng hàm đó (không để lại NHIỀU CUỘC GỌI THÊM nào cho BitSubString). Nhưng tôi đã để lại việc triển khai trong một vài bản phát hành (tháng) - và đánh dấu nó không được chấp nhận - vì vậy mọi người có thể chuyển sang BitSubString_NEW (và tại thời điểm đó, thay đổi đối số từ kiểu đếm sang kiểu bắt đầu / kết thúc).

THEN - khi quá trình chuyển đổi đó được hoàn thành, tôi đã thực hiện một cam kết khác xóa BitSubString () và đổi tên BitSubString_NEW-> BitSubString () (và không dùng tên BitSubString_NEW).


Không bao giờ thêm các hậu tố không có ý nghĩa hoặc tự ti về tên. Luôn phấn đấu để đưa ra những cái tên ý nghĩa.
Basilevs

Bạn hoàn toàn bỏ lỡ điểm. Đầu tiên - đây không phải là hậu tố "không có nghĩa". Chúng mang ý nghĩa rằng API đang chuyển từ một cái cũ sang một cái mới hơn. Trên thực tế, đó là toàn bộ quan điểm của CÂU HỎI mà tôi đang trả lời, và toàn bộ quan điểm của câu trả lời. Các tên rõ ràng giao tiếp là API OLD, là API MỚI và là tên đích cuối cùng của API sau khi quá trình chuyển đổi hoàn tất. VÀ - hậu tố _OLD / _NEW là tạm thời - CHỈ trong quá trình chuyển đổi thay đổi API.
Lewis Pringle

Chúc may mắn với phiên bản API NEW_NEW_3 ba năm sau.
Basilevs 17/07/18
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.