TDD Red-Green-Refactor và nếu / cách kiểm tra các phương thức trở thành riêng tư


91

Theo như tôi hiểu, hầu hết mọi người dường như đồng ý rằng các phương thức riêng tư không nên được kiểm tra trực tiếp, mà là thông qua bất kỳ phương thức công khai nào gọi chúng. Tôi có thể thấy quan điểm của họ, nhưng tôi gặp một số vấn đề với điều này khi tôi cố gắng tuân theo "Ba định luật TDD" và sử dụng chu trình "Đỏ - xanh - tái cấu trúc". Tôi nghĩ rằng nó được giải thích tốt nhất bằng một ví dụ:

Ngay bây giờ, tôi cần một chương trình có thể đọc một tệp (chứa dữ liệu được phân tách bằng tab) và lọc ra tất cả các cột có chứa dữ liệu không phải là số. Tôi đoán có lẽ đã có sẵn một số công cụ đơn giản để làm điều này, nhưng tôi đã quyết định tự mình thực hiện nó, chủ yếu là vì tôi nghĩ rằng nó có thể là một dự án tốt và sạch sẽ để tôi thực hành với TDD.

Vì vậy, trước tiên, tôi "đội mũ đỏ", nghĩa là tôi cần một bài kiểm tra thất bại. Tôi hình dung, tôi sẽ cần một phương pháp tìm tất cả các trường không phải là số trong một dòng. Vì vậy, tôi viết một bài kiểm tra đơn giản, tất nhiên là nó không biên dịch ngay lập tức, vì vậy tôi bắt đầu tự viết hàm và sau một vài chu kỳ qua lại (đỏ / xanh) tôi có một hàm làm việc và một bài kiểm tra hoàn chỉnh.

Tiếp theo, tôi tiếp tục với một hàm, "tập hợpNonNumericColumns" để đọc tệp, từng dòng một và gọi hàm "findNonNumericFields" của tôi trên mỗi dòng để thu thập tất cả các cột cuối cùng phải được xóa. Một vài chu kỳ đỏ-xanh, và tôi đã hoàn thành, có một lần nữa, một chức năng làm việc và một bài kiểm tra hoàn chỉnh.

Bây giờ, tôi hình tôi nên tái cấu trúc. Vì phương thức "findNonNumericFields" của tôi chỉ được thiết kế vì tôi cho rằng tôi sẽ cần nó khi triển khai "tập hợpNonNumericColumns", nên có vẻ hợp lý khi để "findNonNumericFields" trở nên riêng tư. Tuy nhiên, điều đó sẽ phá vỡ các thử nghiệm đầu tiên của tôi, vì họ sẽ không còn có quyền truy cập vào phương pháp mà họ đang thử nghiệm.

Vì vậy, tôi kết thúc với một phương thức riêng tư và một bộ thử nghiệm kiểm tra nó. Vì rất nhiều người khuyên rằng không nên thử nghiệm các phương pháp riêng tư, nên có cảm giác như tôi đã vẽ mình vào một góc ở đây. Nhưng chính xác thì tôi đã thất bại ở đâu?

Tôi tập hợp tôi có thể đã bắt đầu ở cấp độ cao hơn, viết một bài kiểm tra để kiểm tra xem cái gì cuối cùng sẽ trở thành phương thức công khai của tôi : Rằng bạn nên chuyển đổi liên tục giữa việc viết thử nghiệm và mã sản xuất, và tại bất kỳ thời điểm nào, tất cả các thử nghiệm của bạn đều hoạt động trong vòng một phút cuối hoặc lâu hơn. Bởi vì nếu tôi bắt đầu bằng cách viết một bài kiểm tra cho một phương thức công khai, sẽ có vài phút (hoặc vài giờ hoặc thậm chí vài ngày trong các trường hợp rất phức tạp) trước khi tôi nhận được tất cả các chi tiết trong các phương thức riêng tư để thử nghiệm công khai phương pháp thông qua.

Vậy lam gi? Có phải TDD (với chu trình tái cấu trúc đỏ-xanh nhanh chóng) đơn giản là không tương thích với các phương thức riêng tư? Hoặc có một lỗi trong thiết kế của tôi?



2
Hai phần chức năng này đủ khác nhau để trở thành các đơn vị khác nhau - trong trường hợp đó, các phương thức riêng có lẽ nên ở trên các lớp riêng của chúng - hoặc chúng là cùng một đơn vị trong trường hợp tôi không hiểu tại sao bạn viết bài kiểm tra cho hành vi nội bộ cho đơn vị. Về đoạn áp chót, tôi không thấy xung đột. Tại sao bạn cần phải viết toàn bộ một phương thức riêng phức tạp để vượt qua một trường hợp thử nghiệm? Tại sao không lái nó ra dần dần thông qua phương thức công khai, hoặc bắt đầu với nó nội tuyến sau đó giải nén nó ra?
Ben Aaronson

26
Tại sao mọi người lấy thành ngữ và sáo rỗng từ sách lập trình và blog làm hướng dẫn thực tế về cách lập trình nằm ngoài tôi.
AK_

7
Tôi không thích TDD vì lý do chính xác này: nếu bạn ở một khu vực mới thì bạn sẽ phải làm thêm rất nhiều việc trong khi cố gắng tìm hiểu kiến ​​trúc nên như thế nào và một số thứ nhất định hoạt động như thế nào. Mặt khác: nếu bạn đang ở trong khu vực mà bạn đã có kinh nghiệm thì sẽ có ích khi viết bài kiểm tra trước khi làm phiền bạn vì intellisense không hiểu tại sao bạn viết mã không thể biên dịch được. Tôi là một fan hâm mộ lớn hơn nhiều về suy nghĩ về thiết kế, viết nó và sau đó đơn vị thử nghiệm nó.
Jeroen Vannevel

1
"Hầu hết mọi người dường như đồng ý rằng các phương pháp riêng tư không nên được kiểm tra trực tiếp" - không, hãy kiểm tra một phương pháp trực tiếp nếu nó hợp lý để làm như vậy. Ẩn nó như privatethể nó có ý nghĩa để làm như vậy.
osa

Câu trả lời:


44

Các đơn vị

Tôi nghĩ rằng tôi có thể xác định chính xác nơi vấn đề bắt đầu:

Tôi hình dung, tôi sẽ cần một phương pháp tìm tất cả các trường không phải là số trong một dòng.

Điều này cần được thực hiện ngay lập tức với việc tự hỏi "Liệu đó có phải là một đơn vị thử nghiệm riêng biệt với gatherNonNumericColumnshoặc một phần của cùng một đơn vị không?"

Nếu câu trả lời là " có, riêng biệt ", thì quá trình hành động của bạn rất đơn giản: phương pháp đó cần được công khai trên một lớp thích hợp, vì vậy nó có thể được kiểm tra như một đơn vị. Tâm lý của bạn là một cái gì đó như "Tôi cần thử lái một phương thức và tôi cũng cần thử lái ra một phương pháp khác"

Từ những gì bạn nói, bạn nhận ra rằng câu trả lời là " không, một phần giống nhau ". Tại thời điểm này, kế hoạch của bạn không còn là viết đầy đủ và kiểm tra findNonNumericFields sau đó viết gatherNonNumericColumns. Thay vào đó, nó chỉ đơn giản là để viết gatherNonNumericColumns. Hiện tại, findNonNumericFieldschỉ nên là một phần của điểm đến mà bạn có trong tâm trí khi bạn chọn trường hợp thử nghiệm màu đỏ tiếp theo và thực hiện tái cấu trúc. Lần này tâm lý của bạn là "Tôi cần thử lái một phương thức, và trong khi tôi làm như vậy tôi nên nhớ rằng việc thực hiện đã hoàn thành của tôi có thể sẽ bao gồm phương pháp khác này".


Giữ một chu kỳ ngắn

Làm những điều trên không nên dẫn đến những vấn đề bạn mô tả trong đoạn áp chót của bạn:

Bởi vì nếu tôi bắt đầu bằng cách viết một bài kiểm tra cho một phương thức công khai, sẽ có vài phút (hoặc vài giờ hoặc thậm chí vài ngày trong các trường hợp rất phức tạp) trước khi tôi nhận được tất cả các chi tiết trong các phương thức riêng tư để thử nghiệm công khai phương pháp thông qua.

Kỹ thuật này không đòi hỏi bạn phải viết một bài kiểm tra màu đỏ, nó sẽ chỉ chuyển sang màu xanh khi bạn thực hiện toàn bộ findNonNumericFieldstừ đầu. Nhiều khả năng, findNonNumericFieldssẽ bắt đầu khi một số mã nội tuyến trong phương thức công khai mà bạn đang thử nghiệm, sẽ được xây dựng trong quá trình của một số chu kỳ và cuối cùng được trích xuất trong quá trình tái cấu trúc.


Lộ trình

Để đưa ra một lộ trình gần đúng cho ví dụ cụ thể này, tôi không biết các trường hợp kiểm tra chính xác mà bạn đã sử dụng, nhưng nói rằng bạn đang viết gatherNonNumericColumnsnhư một phương pháp công khai. Sau đó, rất có thể các trường hợp kiểm tra sẽ giống như các trường hợp bạn đã viết findNonNumericFields, mỗi trường hợp sử dụng một bảng chỉ có một hàng. Khi kịch bản một hàng đó được thực hiện đầy đủ và bạn muốn viết một bài kiểm tra để buộc bạn trích xuất phương thức, bạn sẽ viết một trường hợp hai hàng sẽ yêu cầu bạn thêm phép lặp.


2
Tôi nghĩ rằng đây là câu trả lời ngay tại đây. Chấp nhận TDD trong môi trường OOP, tôi thường thấy mình gặp khó khăn trong việc vượt qua bản năng từ dưới lên của chính mình. Có, các chức năng nên nhỏ, nhưng đó là sau khi tái cấu trúc. Trước đây, chúng có thể là những tảng đá nguyên khối khổng lồ. +1
João Mendes

2
@ JoãoMendes Chà, tôi không chắc bạn nên đến trạng thái của một tảng đá nguyên khối khổng lồ trước khi tái cấu trúc, đặc biệt là trong các chu kỳ RGR rất ngắn. Nhưng vâng, trong một đơn vị có thể kiểm tra, làm việc từ dưới lên có thể dẫn đến các vấn đề mà OP mô tả.
Ben Aaronson

1
OK, tôi nghĩ rằng tôi hiểu nó đã đi sai ở đâu. Cảm ơn tất cả các bạn (đánh dấu câu này là câu trả lời, nhưng hầu hết các câu trả lời khác cũng hữu ích không kém)
Henrik Berg

66

Rất nhiều người nghĩ rằng thử nghiệm đơn vị là dựa trên phương pháp; không phải vậy Nó nên được dựa trên đơn vị nhỏ nhất có ý nghĩa. Đối với hầu hết mọi thứ, điều này có nghĩa là lớp là những gì bạn nên kiểm tra với tư cách là một thực thể. Không phải phương pháp cá nhân trên đó.

Bây giờ rõ ràng bạn sẽ gọi các phương thức trên lớp, nhưng bạn nên nghĩ về các thử nghiệm như áp dụng cho đối tượng hộp đen mà bạn có, vì vậy bạn sẽ có thể thấy rằng bất kỳ hoạt động logic nào mà lớp của bạn cung cấp; đây là những điều bạn cần kiểm tra Nếu lớp của bạn lớn đến mức hoạt động logic quá phức tạp, thì bạn có một vấn đề thiết kế cần được khắc phục trước.

Một lớp có hàng ngàn phương thức có thể xuất hiện có thể kiểm tra được, nhưng nếu bạn chỉ kiểm tra từng phương thức riêng lẻ thì bạn không thực sự kiểm tra lớp đó. Một số lớp có thể yêu cầu ở trạng thái nhất định trước khi phương thức được gọi, ví dụ: lớp mạng cần kết nối được thiết lập trước khi gửi dữ liệu. Phương thức gửi dữ liệu không thể được xem xét độc lập với cả lớp.

Vì vậy, bạn nên thấy rằng các phương thức riêng tư không liên quan đến thử nghiệm. Nếu bạn không thể thực hiện các phương thức riêng tư của mình bằng cách gọi giao diện chung của lớp, thì các phương thức riêng đó là vô dụng và dù sao cũng sẽ không được sử dụng.

Tôi nghĩ rằng nhiều người cố gắng biến các phương thức riêng tư thành các đơn vị có thể kiểm tra được vì có vẻ dễ dàng chạy thử nghiệm cho họ, nhưng điều này đưa độ chi tiết thử nghiệm đi quá xa. Martin Fowler nói

Mặc dù tôi bắt đầu với khái niệm đơn vị là một lớp, tôi thường lấy một nhóm các lớp liên quan chặt chẽ và coi chúng như một đơn vị

điều này rất có ý nghĩa đối với một hệ thống hướng đối tượng, các đối tượng được thiết kế thành các đơn vị. Nếu bạn muốn kiểm tra các phương thức riêng lẻ, có lẽ bạn nên tạo một hệ thống thủ tục như C hoặc một lớp bao gồm toàn bộ các hàm tĩnh thay thế.


14
Đối với tôi có vẻ như câu trả lời này hoàn toàn bỏ qua cách tiếp cận TDD trong câu hỏi của OP. Nó chỉ là một sự lặp lại của "không thử nghiệm các phương pháp riêng" thần chú, nhưng nó không giải thích cách TDD - đó thực tế phương pháp dựa - có thể làm việc với một đơn vị có trụ sở cách tiếp cận kiểm tra phi pháp.
Doc Brown

6
@DocBrown không, nó trả lời anh ta hoàn toàn bằng cách nói "đừng quá cố chấp" các đơn vị của bạn và làm cho cuộc sống khó khăn cho chính bạn. TDD không dựa trên phương pháp, nó là đơn vị dựa trên mà một đơn vị là bất cứ điều gì có ý nghĩa. Nếu bạn có một thư viện C, thì có, mỗi đơn vị sẽ là một hàm. Nếu bạn có một lớp, đơn vị là một đối tượng. Như Fowler nói, đôi khi một đơn vị là một số lớp liên quan chặt chẽ. Tôi nghĩ rằng nhiều người coi thử nghiệm đơn vị là phương pháp theo phương pháp đơn giản chỉ vì một số công cụ câm tạo ra sơ khai dựa trên phương pháp.
gbjbaanb

3
@gbjbaanb: cố gắng đề xuất một bài kiểm tra cho phép OP thực hiện "thu thập các trường không số trong một dòng" của mình bằng cách sử dụng TDD thuần túy trước, mà không có giao diện chung của lớp mà anh ta dự định viết.
Doc Brown

8
Tôi đã đồng ý với @DocBrown tại đây. Vấn đề của người hỏi không phải là anh ta muốn thử nghiệm độ chi tiết nhiều hơn mức anh ta có thể đạt được mà không cần thử nghiệm các phương pháp riêng tư. Đó là anh ta đã cố gắng thực hiện một cách tiếp cận TDD nghiêm ngặt và - không có kế hoạch như vậy - điều đó đã khiến anh ta đâm vào một bức tường nơi anh ta đột nhiên thấy anh ta có một loạt các bài kiểm tra xem đâu là phương pháp riêng tư. Câu trả lời này không giúp được gì với điều đó. Đó là một câu trả lời tốt cho một số câu hỏi, chỉ không phải câu hỏi này.
Ben Aaronson

7
@Matthew: Sai lầm của anh ấy là anh ấy đã viết chức năng này ngay từ đầu. Lý tưởng nhất là anh ta nên viết phương thức công khai dưới dạng mã spaghetti sau đó tái cấu trúc nó thành một hàm riêng trong chu trình tái cấu trúc - không đánh dấu nó là riêng tư trong chu trình tái cấu trúc.
slebetman

51

Thực tế là các phương thức thu thập dữ liệu của bạn đủ phức tạp để xứng đáng với các bài kiểm tra tách biệt khỏi mục tiêu chính của bạn thành các phương thức của riêng chúng chứ không phải là một phần của một số điểm vòng lặp cho giải pháp: làm cho các phương thức này không riêng tư, nhưng là thành viên của một số lớp khác cung cấp chức năng thu thập / lọc / lập bảng.

Sau đó, bạn viết các bài kiểm tra cho các khía cạnh phân tích dữ liệu câm của lớp người trợ giúp (ví dụ: "phân biệt số với các ký tự") ở một nơi và kiểm tra mục tiêu chính của bạn (ví dụ: "lấy số liệu bán hàng") ở một nơi khác và bạn không Không phải lặp lại các bài kiểm tra lọc cơ bản trong các bài kiểm tra cho logic kinh doanh thông thường của bạn.

Nói chung, nếu lớp của bạn làm một thứ có chứa mã mở rộng để thực hiện một thứ khác được yêu cầu, nhưng tách biệt với mục đích chính của nó, mã đó sẽ sống trong một lớp khác và được gọi thông qua các phương thức công khai. Nó không nên được ẩn trong các góc riêng tư của một lớp chỉ vô tình chứa mã đó. Điều này cải thiện khả năng kiểm tra và dễ hiểu cùng một lúc.


Vâng tôi đồng ý với bạn. Nhưng tôi có một vấn đề với tuyên bố đầu tiên của bạn, cả phần "đủ phức tạp" và phần "đủ riêng biệt". Về "đủ phức tạp": Tôi đang cố gắng thực hiện một chu kỳ đỏ-xanh nhanh chóng, điều đó có nghĩa là tôi chỉ có thể viết mã ít nhất một phút hoặc lâu hơn trước khi chuyển sang thử nghiệm (hoặc ngược lại). Điều đó có nghĩa là các bài kiểm tra của tôi sẽ rất tốt. Tôi cho rằng đó là một trong những lợi thế của TDD, nhưng có lẽ tôi đã sử dụng nó quá mức, để nó trở thành một bất lợi.
Henrik Berg

Về "đủ riêng biệt": Tôi đã học (một lần nữa từ chú) rằng các hàm nên nhỏ và chúng phải nhỏ hơn thế. Vì vậy, về cơ bản tôi cố gắng thực hiện 3-4 chức năng dòng. Vì vậy, ít nhiều tất cả các chức năng được tách thành các phương thức của riêng chúng, bất kể nhỏ và đơn giản.
Henrik Berg

Dù sao, tôi cảm thấy như các khía cạnh trộn dữ liệu (ví dụ: findNonNumericFields) nên thực sự riêng tư. Và nếu tôi tách nó thành một lớp khác, dù sao tôi cũng sẽ phải công khai nó, vì vậy tôi không thấy rõ điểm đó.
Henrik Berg

6
@HenrikBerg nghĩ tại sao bạn có các đối tượng ở nơi đầu tiên - chúng không phải là cách thuận tiện để nhóm các chức năng, nhưng là các đơn vị độc lập giúp các hệ thống phức tạp dễ dàng hoạt động hơn. Do đó, bạn nên nghĩ đến việc kiểm tra lớp học như một điều.
gbjbaanb

@gbjbaanb Tôi sẽ tranh luận rằng cả hai đều giống nhau.
RubberDuck

29

Cá nhân, tôi cảm thấy bạn đã đi sâu vào tư duy thực hiện khi bạn viết các bài kiểm tra. Bạn cho rằng bạn sẽ cần một số phương pháp nhất định. Nhưng bạn có thực sự cần họ làm những gì mà lớp học phải làm không? Lớp học sẽ thất bại nếu ai đó đi cùng và tái cấu trúc chúng trong nội bộ? Nếu bạn đang sử dụng lớp (và đó phải là suy nghĩ của người kiểm tra theo ý kiến ​​của tôi), bạn thực sự có thể quan tâm ít hơn nếu có một phương pháp rõ ràng để kiểm tra các con số.

Bạn nên kiểm tra giao diện chung của một lớp. Việc thực hiện riêng tư là riêng tư vì một lý do. Nó không phải là một phần của giao diện công cộng vì nó không cần thiết và có thể thay đổi. Đó là một chi tiết thực hiện.

Nếu bạn viết các bài kiểm tra đối với giao diện công cộng, bạn sẽ không bao giờ thực sự gặp phải vấn đề mà bạn gặp phải. Hoặc bạn có thể tạo các trường hợp thử nghiệm cho giao diện chung bao gồm các phương thức riêng tư của bạn (tuyệt vời) hoặc bạn không thể. Trong trường hợp đó, có lẽ đã đến lúc phải suy nghĩ kỹ về các phương thức riêng tư và có thể loại bỏ chúng hoàn toàn nếu chúng không thể đạt được.


1
"Chi tiết triển khai" là những thứ như "tôi đã sử dụng XOR hoặc biến tạm thời để trao đổi int giữa các biến". Phương pháp bảo vệ / riêng tư có hợp đồng, giống như bất cứ điều gì khác. Họ nhận đầu vào, làm việc với nó và tạo ra một số đầu ra, dưới những ràng buộc nhất định. Bất cứ điều gì với một hợp đồng cuối cùng nên được kiểm tra - không nhất thiết cho những người tiêu thụ thư viện của bạn, nhưng cho những người duy trì nó và sửa đổi nó sau bạn. Chỉ vì nó không phải là "công cộng" không có nghĩa là nó không phải là một phần của một API.
K từ

11

Bạn không làm TDD dựa trên những gì bạn mong đợi lớp sẽ làm trong nội bộ.

Các trường hợp thử nghiệm của bạn nên dựa trên những gì lớp / chức năng / chương trình phải làm với thế giới bên ngoài. Trong ví dụ của bạn, người dùng sẽ gọi lớp người đọc của bạn bằngfind all the non-numerical fields in a line?

Nếu câu trả lời là "không" thì đó là một bài kiểm tra tồi để viết ngay từ đầu. Bạn muốn viết bài kiểm tra về chức năng ở cấp độ lớp / giao diện - không phải là mức "phương thức lớp sẽ phải thực hiện để làm cho điều này hoạt động", đây là bài kiểm tra của bạn.

Luồng của TDD là:

  • màu đỏ (lớp / đối tượng / hàm / etc đang làm gì với thế giới bên ngoài)
  • màu xanh lá cây (viết mã tối thiểu để làm cho chức năng thế giới bên ngoài này hoạt động)
  • refactor (mã nào tốt hơn để làm cho công việc này)

Điều này KHÔNG phải làm "bởi vì tôi sẽ cần X trong tương lai như một phương thức riêng tư, hãy để tôi thực hiện nó và thử nghiệm nó trước." Nếu bạn thấy mình làm điều này, bạn đang thực hiện giai đoạn "đỏ" không chính xác. Đây dường như là vấn đề của bạn ở đây.

Nếu bạn thấy mình thường xuyên viết bài kiểm tra cho các phương thức trở thành phương thức riêng tư, bạn đang thực hiện một trong một số điều sau:

  • Không hiểu chính xác các trường hợp sử dụng giao diện / mức công khai của bạn đủ tốt để viết bài kiểm tra cho chúng
  • Thay đổi đáng kể thiết kế của bạn và tái cấu trúc lại một số thử nghiệm (có thể là một điều tốt, tùy thuộc vào việc chức năng đó có được thử nghiệm trong các thử nghiệm mới hơn không)

9

Bạn đang gặp phải một quan niệm sai lầm phổ biến với thử nghiệm nói chung.

Hầu hết những người mới thử nghiệm bắt đầu nghĩ theo cách này:

  • viết bài kiểm tra cho hàm F
  • thực hiện F
  • viết bài kiểm tra cho hàm G
  • thực hiện G bằng cách gọi F
  • viết bài kiểm tra cho hàm H
  • thực hiện H bằng cách sử dụng lệnh gọi đến G

vân vân

Vấn đề ở đây là trên thực tế bạn không có bài kiểm tra đơn vị nào cho chức năng H. Bài kiểm tra được cho là kiểm tra H thực sự đang kiểm tra H, G và F cùng một lúc.

Để giải quyết điều này, bạn phải nhận ra rằng các đơn vị có thể kiểm tra phải không bao giờ phụ thuộc vào nhau, mà phụ thuộc vào giao diện của chúng . Trong trường hợp của bạn, trong đó các đơn vị là các chức năng đơn giản, các giao diện chỉ là chữ ký cuộc gọi của chúng. Do đó, bạn phải triển khai G theo cách như vậy, nó có thể được sử dụng với bất kỳ hàm nào có cùng chữ ký với F.

Làm thế nào chính xác điều này có thể được thực hiện phụ thuộc vào ngôn ngữ lập trình của bạn. Trong nhiều ngôn ngữ, bạn có thể truyền các hàm (hoặc con trỏ cho chúng) làm đối số cho các hàm khác. Điều này sẽ cho phép bạn kiểm tra từng chức năng một cách cô lập.


3
Tôi ước tôi có thể bỏ phiếu này nhiều lần nữa. Tôi sẽ chỉ tóm tắt như là, bạn chưa kiến ​​trúc giải pháp của bạn một cách chính xác.
Justin Ohms

Trong một ngôn ngữ như C, điều này có ý nghĩa, nhưng đối với các ngôn ngữ OO nơi đơn vị thường là một lớp (với các phương thức công khai và riêng tư) thì bạn nên kiểm tra lớp, không phải mọi phương thức riêng lẻ. Cô lập các lớp học của bạn, vâng. Cô lập các phương thức trong mỗi lớp, không.
gbjbaanb

8

Các thử nghiệm mà bạn viết trong quá trình Phát triển hướng thử nghiệm được cho là để đảm bảo rằng một lớp thực hiện chính xác API công khai của nó, đồng thời đảm bảo rằng API công khai đó dễ kiểm tra và sử dụng.

Bằng mọi cách, bạn có thể sử dụng các phương thức riêng tư để triển khai API đó, nhưng không cần tạo thử nghiệm thông qua TDD - chức năng sẽ được kiểm tra vì API công khai sẽ hoạt động chính xác.

Bây giờ, giả sử các phương thức riêng tư của bạn đủ phức tạp để chúng có các bài kiểm tra độc lập - nhưng chúng không có ý nghĩa như là một phần của API công khai của lớp ban đầu của bạn. Chà, điều này có lẽ có nghĩa là chúng thực sự nên là các phương thức công khai trên một số lớp khác - một phương thức mà lớp ban đầu của bạn tận dụng lợi thế của nó khi thực hiện.

Bằng cách chỉ kiểm tra API công khai, bạn sẽ dễ dàng sửa đổi các chi tiết triển khai trong tương lai. Các xét nghiệm không hữu ích sẽ chỉ làm phiền bạn sau này khi chúng cần được viết lại để hỗ trợ một số tái cấu trúc thanh lịch mà bạn vừa phát hiện ra.


4

Tôi nghĩ rằng câu trả lời đúng là kết luận mà bạn đã bắt đầu về việc bắt đầu với các phương pháp công khai. Bạn sẽ bắt đầu bằng cách viết một bài kiểm tra gọi phương thức đó. Nó sẽ thất bại vì vậy bạn tạo một phương thức với tên đó mà không làm gì cả. Sau đó, bạn có thể đúng một bài kiểm tra kiểm tra giá trị trả về.

(Tôi không hoàn toàn rõ ràng về chức năng của bạn. Nó có trả về một chuỗi có nội dung tệp với các giá trị không phải là số bị loại bỏ không?)

Nếu phương thức của bạn trả về một chuỗi thì bạn kiểm tra giá trị trả về đó. Vì vậy, bạn chỉ cần tiếp tục xây dựng nó lên.

Tôi nghĩ rằng bất cứ điều gì xảy ra trong một phương thức riêng tư đều phải ở phương thức công khai tại một thời điểm nào đó trong quá trình của bạn, và sau đó chỉ chuyển sang phương thức riêng tư như một phần của bước tái cấu trúc. Tái cấu trúc không yêu cầu phải thực hiện các bài kiểm tra thất bại, theo như tôi biết. Bạn chỉ cần kiểm tra thất bại khi thêm chức năng. Bạn chỉ cần chạy thử nghiệm sau khi bộ tái cấu trúc để đảm bảo tất cả chúng đều vượt qua.


3

cảm giác như tôi đã vẽ mình vào một góc ở đây. Nhưng chính xác thì tôi đã thất bại ở đâu?

Có một câu ngạn ngữ cũ.

Khi bạn thất bại trong kế hoạch, bạn có kế hoạch thất bại.

Mọi người dường như nghĩ rằng khi bạn TDD, bạn chỉ cần ngồi xuống, viết bài kiểm tra, và thiết kế sẽ chỉ xảy ra một cách kỳ diệu. Điều này không đúng. Bạn cần phải có một kế hoạch cấp cao. Tôi thấy rằng tôi nhận được kết quả tốt nhất từ ​​TDD khi tôi thiết kế giao diện (API công khai) trước tiên. Cá nhân, tôi tạo ra một thực tế interfacexác định lớp đầu tiên.

Thở hổn hển Tôi đã viết một số "mã" trước khi viết bất kỳ bài kiểm tra! Ồ không. Tôi đã không. Tôi đã viết một hợp đồng để được theo dõi, một thiết kế . Tôi nghi ngờ bạn có thể nhận được kết quả tương tự bằng cách ghi sơ đồ UML trên giấy biểu đồ. Vấn đề là, bạn phải có một kế hoạch. TDD không phải là giấy phép để hack willy nilly tại một đoạn mã.

Tôi thực sự cảm thấy như "Test First" là một cách viết sai. Thiết kế Đầu tiên sau đó thử nghiệm.

Tất nhiên, hãy làm theo lời khuyên mà những người khác đã đưa ra về việc trích xuất nhiều lớp hơn từ mã của bạn. Nếu bạn cảm thấy cần phải kiểm tra các phần bên trong của một lớp, hãy trích xuất các phần bên trong đó thành một đơn vị được kiểm tra dễ dàng và tiêm nó.


2

Hãy nhớ rằng các bài kiểm tra có thể được tái cấu trúc quá! Nếu bạn đặt phương thức ở chế độ riêng tư, bạn đang giảm API công khai và do đó, hoàn toàn có thể chấp nhận để loại bỏ một số thử nghiệm tương ứng cho "chức năng bị mất" đó (AKA giảm độ phức tạp).

Những người khác đã nói rằng phương thức riêng tư của bạn sẽ được gọi là một phần của các thử nghiệm API khác của bạn hoặc nó sẽ không thể truy cập được và do đó có thể xóa được. Trong thực tế, mọi thứ sẽ tốt hơn nếu chúng ta nghĩ về các đường dẫn thực thi .

Ví dụ: nếu chúng ta có một phương thức công khai thực hiện phép chia, chúng ta có thể muốn kiểm tra đường dẫn dẫn đến chia cho 0. Nếu chúng ta đặt phương thức ở chế độ riêng tư, chúng ta sẽ có một lựa chọn: chúng ta có thể xem xét đường dẫn chia cho 0 hoặc chúng ta có thể loại bỏ đường dẫn đó bằng cách xem xét cách nó được gọi bởi các phương thức khác.

Theo cách này, chúng tôi có thể loại bỏ một số thử nghiệm (ví dụ: chia cho 0) và cấu trúc lại các thử nghiệm khác theo API công khai còn lại. Tất nhiên, trong một thế giới lý tưởng, các bài kiểm tra hiện tại quan tâm đến tất cả những con đường còn lại, nhưng thực tế luôn là một sự thỏa hiệp;)


1
Trong khi các câu trả lời khác là chính xác ở chỗ phương pháp riêng không nên được viết trong chu kỳ đỏ, con người lại mắc lỗi. Và khi bạn đã đi vào con đường sai lầm đủ xa thì đây là giải pháp thích hợp.
slebetman

2

Đôi khi một phương thức riêng tư có thể được tạo thành một phương thức công khai của một lớp khác.

Ví dụ, bạn có thể có các phương thức riêng không an toàn cho luồng và để lớp ở trạng thái tạm thời. Các phương thức này có thể được chuyển đến một lớp riêng được tổ chức riêng bởi lớp đầu tiên của bạn. Vì vậy, nếu lớp của bạn là Hàng đợi, bạn có thể có lớp InternalQueue có các phương thức công khai và lớp Queue giữ riêng đối tượng InternalQueue. Điều này cho phép bạn kiểm tra hàng đợi nội bộ và nó cũng cho biết rõ các hoạt động riêng lẻ trên InternalQueue là gì.

(Điều này là rõ ràng nhất khi bạn tưởng tượng không có lớp Danh sách và nếu bạn đã cố gắng thực hiện các chức năng Danh sách như các phương thức riêng tư trong lớp sử dụng chúng.)


2
"Có những lúc một phương thức riêng tư có thể được tạo thành một phương thức công khai của một lớp khác." Tôi không thể nhấn mạnh đủ. Đôi khi, một phương thức riêng chỉ đơn giản là một lớp khác hét lên cho danh tính của chính nó.

0

Tôi tự hỏi tại sao ngôn ngữ của bạn chỉ có hai cấp độ riêng tư, hoàn toàn công khai và hoàn toàn riêng tư.

Bạn có thể sắp xếp các phương thức không công khai của bạn có thể truy cập được gói hoặc một cái gì đó tương tự không? Sau đó đặt các bài kiểm tra của bạn trong cùng một gói và tận hưởng kiểm tra các hoạt động bên trong không phải là một phần của giao diện công cộng. Hệ thống xây dựng của bạn sẽ loại trừ các kiểm tra khi xây dựng nhị phân phát hành.

Tất nhiên đôi khi bạn cần phải có các phương thức thực sự riêng tư, không thể truy cập được vào bất cứ thứ gì ngoại trừ lớp định nghĩa. Tôi hy vọng tất cả các phương pháp như vậy là rất nhỏ. Nói chung, việc giữ các phương thức nhỏ (ví dụ dưới 20 dòng) giúp ích rất nhiều: kiểm tra, bảo trì và chỉ cần hiểu mã trở nên dễ dàng hơn.


3
Thay đổi một công cụ sửa đổi truy cập phương thức chỉ để chạy thử nghiệm là tình huống một cái đuôi vẫy một con chó. Tôi nghĩ rằng việc kiểm tra các bộ phận bên trong của một đơn vị sẽ khiến việc tái cấu trúc sau này trở nên khó khăn hơn. Kiểm tra giao diện công cộng, ngược lại, là tuyệt vời vì nó hoạt động như một "hợp đồng" cho một đơn vị.
scriptin

Bạn không cần thay đổi cấp độ truy cập của một phương thức. Điều tôi đang cố gắng nói là bạn có một cấp độ truy cập trung gian cho phép viết một số mã nhất định, bao gồm các bài kiểm tra, dễ dàng hơn mà không cần thực hiện hợp đồng công khai. Tất nhiên bạn phải kiểm tra giao diện công cộng, nhưng đôi khi cũng có ích khi kiểm tra một số hoạt động bên trong một cách cô lập.
9000

0

Thỉnh thoảng tôi đã sử dụng các phương thức riêng tư để bảo vệ để cho phép thử nghiệm chi tiết hơn (chặt chẽ hơn API công khai). Đây phải là ngoại lệ (hy vọng rất hiếm) thay vì quy tắc, nhưng nó có thể hữu ích trong một số trường hợp cụ thể mà bạn có thể chạy qua. Ngoài ra, đó là điều bạn hoàn toàn không muốn xem xét khi xây dựng API công khai, nhiều "cheat" mà người ta có thể sử dụng trên phần mềm sử dụng nội bộ trong những tình huống hiếm gặp đó.


0

Tôi đã trải nghiệm điều này và cảm thấy nỗi đau của bạn.

Giải pháp của tôi là:

ngừng điều trị như xây dựng một tảng đá nguyên khối.

Hãy nhớ rằng khi bạn đã viết một tập các bài kiểm tra, giả sử 5, để loại bỏ một số chức năng, bạn không phải giữ tất cả các bài kiểm tra này , đặc biệt khi điều này trở thành một phần của điều khác.

Ví dụ tôi thường có:

  • kiểm tra trình độ thấp 1
  • mã để đáp ứng nó
  • kiểm tra trình độ thấp 2
  • mã để đáp ứng nó
  • kiểm tra trình độ thấp 3
  • mã để đáp ứng nó
  • kiểm tra trình độ thấp 4
  • mã để đáp ứng nó
  • kiểm tra trình độ thấp 5
  • mã để đáp ứng nó

vậy thì tôi có

  • kiểm tra trình độ thấp 1
  • kiểm tra trình độ thấp 2
  • kiểm tra trình độ thấp 3
  • kiểm tra trình độ thấp 4
  • kiểm tra trình độ thấp 5

Tuy nhiên nếu bây giờ tôi thêm (các) hàm cấp cao hơn gọi nó, có rất nhiều bài kiểm tra, thì bây giờ tôi thể giảm các bài kiểm tra cấp thấp đó thành:

  • kiểm tra trình độ thấp 1
  • kiểm tra trình độ thấp 5

Ma quỷ là trong các chi tiết và khả năng để làm điều đó sẽ phụ thuộc vào hoàn cảnh.


-2

Mặt trời quay quanh trái đất hay trái đất quanh mặt trời? Theo Einstein, câu trả lời là có, hoặc cả hai vì cả hai mô hình chỉ khác nhau theo quan điểm, đóng gói tương tự và phát triển theo hướng kiểm tra chỉ là xung đột vì chúng tôi nghĩ rằng chúng là như vậy. Chúng tôi ngồi đây như Galileo và giáo hoàng, xúc phạm những lời lăng mạ lẫn nhau: đồ ngốc, bạn không thấy rằng các phương pháp riêng tư cũng cần thử nghiệm; dị giáo, đừng phá vỡ đóng gói! Tương tự như vậy khi chúng ta nhận ra rằng sự thật vĩ đại hơn chúng ta nghĩ, chúng ta có thể thử một cái gì đó như gói gọn các thử nghiệm cho các giao diện riêng tư để các thử nghiệm cho các giao diện công cộng không phá vỡ đóng gói.

Hãy thử điều này: thêm hai phương thức, một phương thức không có đầu vào nhưng jires trả về số lượng kiểm tra riêng và một phương thức lấy số kiểm tra làm tham số và trả về pass / fail.


1
Những lời lăng mạ được chọn đã được Galileo và The Pope sử dụng, chứ không phải bởi bất kỳ câu trả lời nào cho câu hỏi này.
hildred
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.