Kiểm thử đơn vị C ++: Kiểm tra gì?


20

TL; DR

Viết các bài kiểm tra tốt, hữu ích là khó, và có chi phí cao trong C ++. Bạn có thể phát triển kinh nghiệm chia sẻ lý do của bạn về những gì và khi nào cần kiểm tra?

Câu chuyện dài

Tôi đã từng thực hiện phát triển dựa trên thử nghiệm, trên thực tế, toàn bộ nhóm của tôi, nhưng nó không hoạt động tốt cho chúng tôi. Chúng tôi có nhiều thử nghiệm, nhưng dường như chúng không bao giờ bao gồm các trường hợp chúng tôi có lỗi và hồi quy thực tế - thường xảy ra khi các đơn vị tương tác, không phải từ hành vi biệt lập của chúng.

Điều này thường rất khó kiểm tra ở cấp độ đơn vị đến nỗi chúng tôi đã ngừng thực hiện TDD (ngoại trừ các thành phần thực sự tăng tốc độ phát triển), và thay vào đó đầu tư nhiều thời gian hơn để tăng phạm vi kiểm tra tích hợp. Trong khi các thử nghiệm đơn vị nhỏ không bao giờ bắt gặp bất kỳ lỗi thực sự nào và về cơ bản chỉ là bảo trì trên không, các thử nghiệm tích hợp đã thực sự đáng nỗ lực.

Bây giờ tôi đã kế thừa một dự án mới và đang tự hỏi làm thế nào để thử nghiệm nó. Đây là một ứng dụng C ++ / OpenGL riêng, vì vậy các bài kiểm tra tích hợp không thực sự là một lựa chọn. Nhưng kiểm thử đơn vị trong C ++ khó hơn một chút so với Java (bạn phải tạo ra công cụ một cách rõ ràng virtual) và chương trình không hướng đối tượng nhiều, vì vậy tôi không thể chế giễu / bỏ đi một số thứ.

Tôi không muốn tách ra và OO-ize toàn bộ chỉ để viết một số bài kiểm tra vì mục đích viết bài kiểm tra. Vì vậy, tôi đang hỏi bạn: Tôi nên viết bài kiểm tra để làm gì? ví dụ:

  • Chức năng / Lớp học mà tôi mong đợi sẽ thay đổi thường xuyên?
  • Chức năng / Lớp khó kiểm tra thủ công hơn?
  • Chức năng / Lớp dễ kiểm tra chưa?

Tôi bắt đầu điều tra một số cơ sở mã C ++ đáng kính để xem họ tiến hành thử nghiệm như thế nào. Ngay bây giờ tôi đang xem xét mã nguồn Chromium, nhưng tôi thấy thật khó để trích xuất lý do thử nghiệm của họ từ mã. Nếu bất cứ ai có một ví dụ hay bài đăng về cách người dùng C ++ phổ biến (những người trong ủy ban, tác giả sách, Google, Facebook, Microsoft, ...) tiếp cận điều này, điều đó sẽ rất hữu ích.

Cập nhật

Tôi đã tìm kiếm theo cách của tôi xung quanh trang web này và web kể từ khi viết này. Tìm thấy một số thứ tốt:

Đáng buồn thay, tất cả những thứ này khá là Java / C # centric. Viết nhiều bài kiểm tra bằng Java / C # không phải là một vấn đề lớn, vì vậy lợi ích thường vượt xa chi phí.

Nhưng như tôi đã viết ở trên, nó khó hơn trong C ++. Đặc biệt nếu cơ sở mã của bạn không phải là OO, bạn phải làm rối tung mọi thứ để có được phạm vi kiểm tra đơn vị tốt. Ví dụ: Ứng dụng tôi được kế thừa có một Graphicskhông gian tên là một lớp mỏng phía trên OpenGL. Để kiểm tra bất kỳ thực thể nào - tất cả đều sử dụng trực tiếp các chức năng của nó - tôi phải biến nó thành một giao diện và một lớp và đưa nó vào tất cả các thực thể. Đó chỉ là một ví dụ.

Vì vậy, khi trả lời câu hỏi này, xin lưu ý rằng tôi phải đầu tư khá lớn cho bài kiểm tra viết.


3
+1 cho độ khó khi kiểm tra đơn vị C ++. Nếu bài kiểm tra đơn vị của bạn yêu cầu bạn thay đổi mã, thì không.
DPD

2
@DPD: Tôi không chắc lắm, nếu cái gì đó thực sự đáng để thử nghiệm thì sao? Trong cơ sở mã hiện tại, tôi khó có thể kiểm tra bất cứ điều gì trong mã mô phỏng bởi vì tất cả đều gọi trực tiếp các chức năng đồ họa và tôi không thể giả định / khai thác chúng. Tất cả tôi có thể kiểm tra ngay bây giờ là các chức năng tiện ích. Nhưng tôi đồng ý, việc thay đổi mã để làm cho nó "có thể kiểm tra" cảm thấy ... sai. Những người đề xuất TDD thường nói rằng điều này sẽ làm cho tất cả mã của bạn tốt hơn theo mọi cách có thể tưởng tượng được, nhưng tôi hoàn toàn không đồng ý. Không phải tất cả mọi thứ cần một giao diện và một số triển khai.
futlib

Để tôi cho bạn một ví dụ gần đây: Tôi đã dành cả ngày để thử kiểm tra một chức năng singe (được viết bằng C ++ / CLI) và công cụ kiểm tra MS Test sẽ luôn bị sập cho bài kiểm tra này. Nó dường như có một số vấn đề với các tài liệu tham khảo CPP đơn giản. Thay vào đó tôi chỉ kiểm tra ouptput của chức năng gọi của nó và nó hoạt động tốt. Tôi đã lãng phí cả một ngày để UT một chức năng. Đó là một mất thời gian quý giá. Ngoài ra, tôi không thể có bất kỳ công cụ khai thác phù hợp với nhu cầu của tôi. Tôi đã làm thủ công bất cứ nơi nào có thể.
DPD

Đó chỉ là loại công cụ tôi muốn tránh: DI đoán chúng tôi các nhà phát triển C ++ phải đặc biệt thực tế trong việc thử nghiệm. Bạn đã kết thúc thử nghiệm nó, vì vậy tôi đoán rằng nó ổn.
futlib

@DPD: Tôi đã nghĩ thêm về điều này và tôi nghĩ bạn đúng, câu hỏi là tôi muốn thực hiện loại đánh đổi nào. Có đáng để tái cấu trúc toàn bộ hệ thống đồ họa để kiểm tra một vài thực thể không? Có bất kỳ lỗi nào trong đó tôi biết, vì vậy có lẽ: Không. Nếu nó bắt đầu cảm thấy có lỗi, tôi sẽ viết bài kiểm tra. Quá tệ Tôi không thể chấp nhận câu trả lời của bạn vì đó là một nhận xét :)
futlib

Câu trả lời:


5

Vâng, Đơn vị kiểm tra chỉ là một phần. Kiểm tra tích hợp giúp bạn với vấn đề của nhóm của bạn. Kiểm tra tích hợp có thể được viết cho tất cả các loại ứng dụng, cũng như cho các ứng dụng gốc và ứng dụng OpenGL. Bạn nên xem "Phần mềm hướng đối tượng phát triển được hướng dẫn bởi các thử nghiệm" của Steve Freemann và Nat Pryce (ví dụ: http://www.amazon.com/Growing-Object-Orients-Software-Guided-Signature/dp/0321503627 ). Nó dẫn bạn từng bước thông qua việc phát triển một ứng dụng với GUI và giao tiếp mạng.

Phần mềm kiểm tra không được kiểm tra là một câu chuyện khác. Kiểm tra Michael Feathers "Làm việc hiệu quả với Mã kế thừa" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).


Tôi biết cả hai cuốn sách. Vấn đề là: 1. Chúng tôi không muốn đi với TDD, vì nó không hoạt động tốt với chúng tôi. Chúng tôi muốn thử nghiệm, nhưng không tôn giáo. 2. Tôi chắc chắn rằng có thể kiểm tra tích hợp cho các ứng dụng OpenGL bằng cách nào đó, nhưng phải mất quá nhiều nỗ lực. Tôi muốn tiếp tục cải thiện ứng dụng, không bắt đầu một dự án nghiên cứu.
futlib

Xin lưu ý rằng kiểm tra đơn vị "sau thực tế" luôn khó hơn "kiểm tra trước", bởi vì mã không được thiết kế để có thể kiểm tra được (có thể sử dụng lại, có thể duy trì, v.v.). Nếu bạn vẫn muốn làm điều đó, hãy cố gắng tuân theo các thủ thuật của Michael Feathers (ví dụ như các đường nối) ngay cả khi bạn tránh TDD. Ví dụ: nếu bạn muốn mở rộng một hàm, hãy thử một cái gì đó như "phương pháp mọc" và cố gắng giữ phương thức mới có thể kiểm tra được. Có thể làm, nhưng IMHO khó hơn.
EricSchaefer

Tôi đồng ý rằng mã không TDD không được thiết kế để có thể kiểm tra được, nhưng tôi sẽ không nói rằng nó không thể duy trì hoặc có thể tái sử dụng mỗi lần - như tôi đã nhận xét ở trên, một số thứ không cần giao diện và nhiều triển khai. Không phải là một vấn đề với Mockito, nhưng trong C ++, tôi cần phải thực hiện tất cả các chức năng mà tôi muốn khai thác / giả ảo. Dù sao, mã không thể kiểm tra là vấn đề lớn nhất của tôi ngay bây giờ: tôi phải thay đổi một số điều rất cơ bản để làm cho một số phần có thể kiểm tra được, và do đó tôi muốn có một lý do chính đáng để kiểm tra, để đảm bảo rằng nó đáng giá.
futlib

Tất nhiên, bạn đúng, tôi sẽ cẩn thận để tạo bất kỳ mã mới nào tôi viết có thể kiểm tra được. Nhưng nó sẽ không dễ dàng, với cách mọi thứ hoạt động trong cơ sở mã này ngay bây giờ.
futlib

Khi bạn thêm một tính năng / chức năng, chỉ cần nghĩ về cách bạn có thể kiểm tra nó. Bạn có thể tiêm bất kỳ phụ thuộc xấu xí? Làm thế nào bạn biết rằng chức năng làm những gì nó phải làm. Bạn có thể quan sát bất kỳ hành vi? Có kết quả nào bạn có thể kiểm tra tính chính xác không? Có bất kỳ bất biến nào bạn có thể kiểm tra?
EricSchaefer

2

Thật đáng tiếc TDD "không làm việc tốt cho bạn." Tôi nghĩ đó là chìa khóa để hiểu nơi cần rẽ. Xem lại và hiểu cách TDD không hoạt động, bạn có thể làm gì tốt hơn, tại sao lại gặp khó khăn.

Vì vậy, tất nhiên các bài kiểm tra đơn vị của bạn đã không bắt được các lỗi bạn tìm thấy. Đó là loại điểm. :-) Bạn đã không tìm thấy những lỗi đó vì bạn đã ngăn chúng xảy ra ngay từ đầu bằng cách suy nghĩ về cách các giao diện nên hoạt động và làm thế nào để đảm bảo chúng được kiểm tra đúng cách.

Để trả lời, bạn đặt câu hỏi, như bạn đã kết luận, mã kiểm thử đơn vị không được thiết kế để kiểm tra là khó khăn. Đối với mã hiện tại, có thể hiệu quả hơn khi sử dụng môi trường thử nghiệm chức năng hoặc tích hợp thay vì môi trường thử nghiệm đơn vị. Kiểm tra tổng thể hệ thống tập trung vào các lĩnh vực cụ thể.

Tất nhiên sự phát triển mới sẽ được hưởng lợi từ TDD. Khi các tính năng mới được thêm vào, tái cấu trúc cho TDD có thể giúp kiểm tra sự phát triển mới, đồng thời cho phép phát triển thử nghiệm đơn vị mới cho các chức năng cũ.


4
Chúng tôi đã làm TDD trong khoảng một năm rưỡi, tất cả đều khá đam mê về nó. Tuy nhiên, so sánh các dự án TDD với các dự án trước đó được thực hiện mà không có TDD (nhưng không phải không có kiểm tra), tôi sẽ không nói rằng chúng thực sự ổn định hơn hoặc có mã được thiết kế tốt hơn. Có lẽ đó là nhóm của chúng tôi: Chúng tôi ghép nối và đánh giá rất nhiều, chất lượng mã của chúng tôi luôn ở mức khá tốt.
futlib

1
Càng nghĩ về nó, tôi càng nghĩ rằng TDD không phù hợp lắm với công nghệ của dự án cụ thể đó: Flex / Swiz. Có rất nhiều sự kiện và ràng buộc và tiêm xảy ra khiến cho sự tương tác giữa các đối tượng trở nên phức tạp và gần như không thể kiểm tra đơn vị. Việc tách các đối tượng đó không làm cho nó tốt hơn, vì chúng hoạt động chính xác ngay từ đầu.
futlib

2

Tôi chưa thực hiện TDD trong C ++ vì vậy tôi không thể nhận xét về điều đó, nhưng bạn phải kiểm tra hành vi dự kiến ​​của mã của bạn. Trong khi việc thực hiện có thể thay đổi, hành vi nên (thường là?) Vẫn giữ nguyên. Trong thế giới trung tâm của Java \ C #, điều đó có nghĩa là bạn chỉ kiểm tra các phương thức công khai, viết các bài kiểm tra cho hành vi dự kiến ​​và thực hiện điều đó trước khi thực hiện (thường được nói tốt hơn thực hiện :)).

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.