Có phải thử nghiệm đơn vị dẫn đến khái quát hóa sớm (cụ thể là trong bối cảnh của C ++)?


20

Ghi chú sơ bộ

Tôi sẽ không phân biệt các loại thử nghiệm khác nhau, đã có một vài câu hỏi trên các trang web này liên quan đến điều đó.

Tôi sẽ lấy những gì ở đó và nói rằng: kiểm thử đơn vị theo nghĩa "kiểm tra đơn vị cách ly nhỏ nhất của ứng dụng" từ đó câu hỏi này thực sự xuất phát

Vấn đề cách ly

Là những gì đơn vị isolatable nhỏ nhất của một chương trình. Vâng, như tôi thấy, nó (rất cao?) Phụ thuộc vào ngôn ngữ bạn đang mã hóa.

Micheal Feathers nói về khái niệm đường may : [WEwLC, p31]

Đường may là nơi bạn có thể thay đổi hành vi trong chương trình của mình mà không cần chỉnh sửa ở nơi đó.

Và không đi sâu vào chi tiết, tôi hiểu một đường nối - trong bối cảnh thử nghiệm đơn vị - là một vị trí trong chương trình nơi "thử nghiệm" của bạn có thể giao tiếp với "đơn vị" của bạn.

Ví dụ

Kiểm thử đơn vị - đặc biệt là trong C ++ - yêu cầu từ mã được kiểm tra để thêm nhiều đường nối sẽ được gọi đúng cho một vấn đề nhất định.

Thí dụ:

  • Thêm một giao diện ảo trong đó việc triển khai phi ảo là đủ
  • Chia tách - khái quát hóa (?) - một lớp (nhỏ) tiếp tục "chỉ" để tạo điều kiện thêm một bài kiểm tra.
  • Chia một dự án có thể thực hiện được thành các lib dường như "độc lập", "chỉ" để tạo điều kiện biên dịch chúng một cách độc lập cho các thử nghiệm.

Câu hỏi

Tôi sẽ thử một vài phiên bản hy vọng sẽ hỏi về cùng một điểm:

  • Là cách mà các Thử nghiệm đơn vị yêu cầu một để cấu trúc mã của ứng dụng "chỉ" có lợi cho các thử nghiệm đơn vị hay nó thực sự có lợi cho cấu trúc ứng dụng.
  • Là khái quát hóa mã cần thiết để làm cho nó có thể kiểm tra đơn vị hữu ích cho bất cứ điều gì ngoại trừ các thử nghiệm đơn vị?
  • Có thêm các bài kiểm tra đơn vị buộc người ta phải khái quát hóa một cách không cần thiết?
  • Có phải các đơn vị hình dạng kiểm tra lực trên mã "luôn luôn" cũng là một hình dạng tốt cho mã nói chung như nhìn thấy từ miền vấn đề?

Tôi nhớ một quy tắc ngón tay cái nói rằng không khái quát cho đến khi bạn cần / cho đến khi có một nơi thứ hai sử dụng mã. Với Bài kiểm tra đơn vị, luôn có một vị trí thứ hai sử dụng mã - cụ thể là bài kiểm tra đơn vị. Vậy lý do này có đủ để khái quát?


8
Một meme phổ biến là bất kỳ mẫu nào cũng có thể bị lạm dụng để trở thành một mẫu chống. Điều tương tự cũng đúng với TDD. Người ta có thể thêm các giao diện có thể kiểm tra qua điểm lợi nhuận giảm dần, trong đó mã được kiểm tra ít hơn các giao diện kiểm tra tổng quát được thêm vào, cũng như vào khu vực lợi ích chi phí quá thấp. Một trò chơi thông thường có thêm giao diện để thử nghiệm như hệ điều hành không gian sâu hoàn toàn có thể bỏ lỡ cửa sổ thị trường của nó. Hãy chắc chắn rằng thử nghiệm được thêm vào trước các điểm uốn đó.
hotpaw2

@ hotpaw2 báng bổ! :)
maple_shaft

Câu trả lời:


23

Kiểm thử đơn vị - đặc biệt là trong C ++ - yêu cầu từ mã được kiểm tra để thêm nhiều đường nối sẽ được gọi đúng cho một vấn đề nhất định.

Chỉ khi bạn không xem xét việc kiểm tra một phần không thể thiếu trong việc giải quyết vấn đề. Đối với bất kỳ vấn đề không cần thiết nào, nó phải là, không chỉ trong thế giới phần mềm.

Trong thế giới phần cứng, điều này đã được học từ lâu - một cách khó khăn. Các nhà sản xuất các thiết bị khác nhau đã học được qua nhiều thế kỷ từ vô số cây cầu rơi, xe nổ, CPU hút thuốc, v.v. những gì chúng ta đang học trong thế giới phần mềm. Tất cả họ xây dựng "các đường nối thêm" vào các sản phẩm của mình để làm cho chúng có thể kiểm tra được. Hầu hết các xe mới hiện nay đều có cổng chẩn đoán cho thợ sửa chữa để lấy dữ liệu về những gì đang diễn ra bên trong động cơ. Một phần đáng kể của các bóng bán dẫn trên mỗi CPU phục vụ mục đích chẩn đoán. Trong thế giới phần cứng, mỗi bit chi phí "thêm" và khi một sản phẩm được sản xuất bởi hàng triệu người, những chi phí này chắc chắn sẽ cộng thêm một khoản tiền lớn. Tuy nhiên, các nhà sản xuất sẵn sàng chi tất cả số tiền này cho khả năng kiểm tra.

Quay trở lại thế giới phần mềm, C ++ thực sự khó kiểm tra đơn vị hơn các ngôn ngữ sau này có tính năng tải lớp động, phản xạ, v.v. Tuy nhiên, hầu hết các vấn đề có thể được giảm thiểu ít nhất. Trong một dự án C ++ mà tôi đã sử dụng các bài kiểm tra đơn vị cho đến nay, chúng tôi đã không chạy các bài kiểm tra thường xuyên như trong một dự án Java - nhưng chúng vẫn là một phần của bản dựng CI của chúng tôi và chúng tôi thấy chúng hữu ích.

Là cách mà các Thử nghiệm đơn vị yêu cầu một để cấu trúc mã của ứng dụng "chỉ" có lợi cho các thử nghiệm đơn vị hay nó thực sự có lợi cho cấu trúc ứng dụng?

Theo kinh nghiệm của tôi, một thiết kế có thể kiểm tra là có lợi về tổng thể, không phải "chỉ" cho bản thân các bài kiểm tra đơn vị. Những lợi ích này đến ở các cấp độ khác nhau:

  • Làm cho thiết kế của bạn có thể kiểm tra được sẽ buộc bạn chia nhỏ ứng dụng của mình thành các phần nhỏ, ít nhiều độc lập, chỉ có thể ảnh hưởng lẫn nhau theo những cách hạn chế và được xác định rõ - điều này rất quan trọng đối với tính ổn định và khả năng duy trì lâu dài của chương trình của bạn. Không có điều này, mã có xu hướng xấu đi thành mã spaghetti trong đó bất kỳ thay đổi nào được thực hiện trong bất kỳ phần nào của cơ sở mã có thể gây ra hiệu ứng không mong muốn trong các phần dường như không liên quan của chương trình. Không cần phải nói, là cơn ác mộng của mọi lập trình viên.
  • Tự viết các bài kiểm tra theo kiểu TDD thực sự thực hiện các API, lớp và phương thức của bạn và đóng vai trò là một bài kiểm tra rất hiệu quả để phát hiện xem thiết kế của bạn có hợp lý hay không - nếu viết bài kiểm tra và giao diện cảm thấy khó xử hoặc khó khăn, bạn sẽ nhận được phản hồi sớm có giá trị vẫn dễ dàng định hình API. Nói cách khác, điều này bảo vệ bạn khỏi việc xuất bản API sớm.
  • Mô hình phát triển được thực thi bởi TDD giúp bạn tập trung vào (các) nhiệm vụ cụ thể cần thực hiện và giúp bạn đạt mục tiêu, giảm thiểu khả năng bạn đi lang thang giải quyết các vấn đề khác ngoài nhiệm vụ bạn cần, thêm các tính năng bổ sung không cần thiết và phức tạp v.v.
  • Phản hồi nhanh của các bài kiểm tra đơn vị cho phép bạn mạnh dạn trong việc tái cấu trúc mã, cho phép bạn liên tục điều chỉnh và phát triển thiết kế trong suốt vòng đời của mã, do đó ngăn chặn entropy mã một cách hiệu quả.

Tôi nhớ một quy tắc ngón tay cái nói rằng không khái quát cho đến khi bạn cần / cho đến khi có một vị trí thứ hai sử dụng mã. Với Bài kiểm tra đơn vị, luôn có một vị trí thứ hai sử dụng mã - cụ thể là bài kiểm tra đơn vị. Vậy lý do này có đủ để khái quát?

Nếu bạn có thể chứng minh rằng phần mềm của bạn thực hiện chính xác những gì nó phải làm - và chứng minh phần mềm đó một cách nhanh chóng, có thể lặp lại, rẻ tiền và đủ tính quyết định để làm hài lòng khách hàng của bạn - mà không cần khái quát hóa "thêm" bởi các bài kiểm tra đơn vị, hãy thử (và cho chúng tôi biết làm thế nào bạn làm điều đó, bởi vì tôi chắc chắn rất nhiều người trên diễn đàn này sẽ quan tâm như tôi :-)

Btw Tôi giả sử bằng cách "khái quát hóa", ý bạn là những thứ như giới thiệu một giao diện (lớp trừu tượng) và đa hình thay vì một lớp cụ thể - nếu không, xin vui lòng làm rõ.


Thưa ông, tôi chào bạn.
GordonM

Một lưu ý ngắn gọn nhưng mang tính mô phạm: "cổng chẩn đoán" chủ yếu là ở đó bởi vì các chính phủ đã bắt buộc họ là một phần của kế hoạch kiểm soát khí thải. Do đó, nó có những hạn chế nghiêm trọng; có nhiều thứ có khả năng được chẩn đoán với cổng này mà không phải (tức là bất cứ điều gì không liên quan đến kiểm soát khí thải).
Robert Harvey

4

Tôi sẽ ném Con đường của Testivus cho bạn, nhưng để tóm tắt:

Nếu bạn đang dành nhiều thời gian và năng lượng làm cho mã của bạn trở nên phức tạp hơn để kiểm tra một phần của hệ thống, thì có thể cấu trúc của bạn sai hoặc cách tiếp cận thử nghiệm của bạn sai.

Hướng dẫn đơn giản nhất là: Điều bạn đang kiểm tra là giao diện công khai mã của bạn theo cách nó được sử dụng cho các phần khác của hệ thống.

Nếu các bài kiểm tra của bạn trở nên dài và phức tạp, thì đó là một dấu hiệu cho thấy việc sử dụng giao diện chung sẽ khó khăn.

Nếu bạn phải sử dụng tính kế thừa để cho phép lớp của bạn được sử dụng bởi bất kỳ thứ gì khác ngoài trường hợp duy nhất mà nó hiện đang được sử dụng, thì rất có thể lớp của bạn bị ràng buộc quá nhiều vào môi trường sử dụng. Bạn có thể đưa ra một ví dụ về một tình huống mà điều này là đúng?

Tuy nhiên, hãy cẩn thận với giáo điều kiểm tra đơn vị. Viết bài kiểm tra cho phép bạn phát hiện vấn đề sẽ khiến khách hàng hét vào mặt bạn .


Tôi sẽ thêm tương tự: tạo một api, kiểm tra api, từ bên ngoài.
Christopher Mahan

2

TDD và Kiểm thử đơn vị, tốt cho toàn bộ chương trình, và không chỉ cho các bài kiểm tra đơn vị. Lý do cho điều này là vì nó tốt cho não.

Đây là phần trình bày về một khung ActionScript cụ thể có tên RobotLegs. Tuy nhiên, nếu bạn lướt qua 10 slide đầu tiên hoặc lâu hơn, nó bắt đầu đi đến những phần tốt về não.

Kiểm tra TDD và Đơn vị, buộc bạn phải hành xử theo cách tốt hơn cho não để xử lý và ghi nhớ thông tin. Vì vậy, trong khi nhiệm vụ chính xác của bạn trước mặt bạn chỉ là thực hiện một bài kiểm tra đơn vị tốt hơn hoặc làm cho mã dễ kiểm tra hơn ... những gì nó thực sự làm cho mã của bạn dễ đọc hơn và do đó làm cho mã của bạn dễ bảo trì hơn. Điều này giúp bạn mã hóa các thói quen nhanh hơn và giúp bạn có thể hiểu mã của mình nhanh hơn khi bạn cần thêm / xóa các tính năng, sửa lỗi hoặc nói chung mở tệp nguồn.


1

thử nghiệm đơn vị cách ly nhỏ nhất của ứng dụng

Điều này là đúng, nhưng nếu bạn đưa nó đi quá xa thì nó không mang lại cho bạn nhiều và nó tốn kém rất nhiều, và tôi tin rằng chính khía cạnh này đang thúc đẩy việc sử dụng thuật ngữ BDD là điều mà TDD nên có cùng - đơn vị cách ly nhỏ nhất là những gì bạn muốn nó được.

Ví dụ, tôi đã từng gỡ lỗi một lớp mạng có (trong số các bit khác) 2 phương thức: 1 để đặt địa chỉ IP, một phương thức khác để đặt số cổng. Đương nhiên, đây là những phương pháp rất đơn giản và sẽ vượt qua thử nghiệm tầm thường nhất một cách dễ dàng, nhưng nếu bạn đặt số cổng và sau đó đặt địa chỉ IP, nó sẽ không hoạt động - bộ cài đặt ip đã ghi đè số cổng theo mặc định. Vì vậy, bạn phải kiểm tra toàn bộ lớp để đảm bảo hành vi chính xác, một điều tôi nghĩ rằng khái niệm về TDD bị thiếu nhưng BDD cung cấp cho bạn. Bạn thực sự không cần phải kiểm tra từng phương thức nhỏ, khi bạn có thể kiểm tra khu vực nhỏ nhất và hợp lý nhất của ứng dụng tổng thể - trong trường hợp này là lớp kết nối mạng.

Cuối cùng, không có viên đạn ma thuật nào để thử nghiệm, bạn phải đưa ra quyết định hợp lý về mức độ và mức độ chi tiết để áp dụng các tài nguyên thử nghiệm hạn chế của bạn. Cách tiếp cận dựa trên công cụ tự động tạo ra sơ khai cho bạn không làm điều này, đó là cách tiếp cận lực lượng cùn.

Vì vậy, bạn không cần phải cấu trúc mã theo một cách nhất định để đạt được TDD, nhưng mức độ thử nghiệm bạn đạt được sẽ phụ thuộc vào cấu trúc mã của bạn - nếu bạn có GUI nguyên khối có tất cả logic của nó bị ràng buộc chặt chẽ với cấu trúc GUI, sau đó bạn sẽ thấy khó khăn hơn trong việc cô lập các phần đó, nhưng bạn vẫn có thể viết một bài kiểm tra đơn vị trong đó 'đơn vị' đề cập đến GUI và tất cả các công việc DB phía sau bị chế giễu. Đây là một ví dụ cực đoan, nhưng nó cho thấy bạn vẫn có thể thực hiện kiểm tra tự động trên nó.

Hiệu ứng phụ của việc cấu trúc mã của bạn để giúp kiểm tra các đơn vị nhỏ hơn dễ dàng hơn giúp bạn xác định ứng dụng tốt hơn và điều đó cho phép bạn thay thế các bộ phận dễ dàng hơn. Nó cũng giúp khi mã hóa vì ít có khả năng 2 nhà phát triển sẽ làm việc trên cùng một thành phần tại bất kỳ thời điểm nào - không giống như một ứng dụng nguyên khối có phụ thuộc xen kẽ, phá vỡ thời gian làm việc của mọi người khác.


0

Bạn đã đạt được một nhận thức tốt về sự đánh đổi trong thiết kế ngôn ngữ. Một số quyết định thiết kế cốt lõi trong C ++ (cơ chế chức năng ảo trộn với cơ chế gọi hàm tĩnh) làm cho TDD trở nên khó khăn. Ngôn ngữ không thực sự hỗ trợ những gì bạn cần để làm cho nó dễ dàng. Thật dễ dàng để viết C ++ mà không thể kiểm tra đơn vị.

Chúng tôi đã may mắn hơn khi thực hiện mã TDD C ++ của mình từ một hàm tư duy chức năng gần như không phải là thủ tục (một hàm không có đối số và trả về khoảng trống) và sử dụng bố cục bất cứ khi nào có thể. Vì rất khó để thay thế các lớp thành viên này, chúng tôi tập trung vào việc thử nghiệm các lớp đó để xây dựng một cơ sở đáng tin cậy và sau đó biết rằng các chức năng đơn vị cơ bản khi chúng tôi thêm nó vào một thứ khác.

Điều quan trọng là cách tiếp cận gần như chức năng. Hãy suy nghĩ về điều này, nếu tất cả mã C ++ của bạn là các hàm miễn phí không truy cập toàn cầu, đó sẽ là một thử nghiệm đơn vị nhanh chóng :)

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.