Chúng ta có nên thiết kế mã của chúng tôi ngay từ đầu để cho phép thử nghiệm đơn vị không?


91

Hiện tại có một cuộc tranh luận đang diễn ra trong nhóm của chúng tôi về việc liệu việc sửa đổi thiết kế mã để cho phép thử nghiệm đơn vị là mùi mã hay ở mức độ nào có thể được thực hiện mà không phải là mùi mã. Điều này đã xảy ra bởi vì chúng ta chỉ mới bắt đầu áp dụng các thực tiễn có mặt ở mọi công ty phát triển phần mềm khác.

Cụ thể, chúng tôi sẽ có một dịch vụ API Web sẽ rất mỏng. Trách nhiệm chính của nó sẽ là xử lý các yêu cầu / phản hồi trên web và gọi một API cơ bản có chứa logic nghiệp vụ.

Một ví dụ là chúng tôi dự định tạo một nhà máy sẽ trả về loại phương thức xác thực. Chúng tôi không cần nó để kế thừa một giao diện vì chúng tôi không dự đoán nó sẽ là bất cứ thứ gì khác ngoài loại cụ thể. Tuy nhiên, để kiểm tra đơn vị dịch vụ API Web, chúng tôi sẽ cần phải giả định nhà máy này.

Điều này về cơ bản có nghĩa là chúng ta hoặc thiết kế lớp trình điều khiển API Web để chấp nhận DI (thông qua hàm tạo hoặc trình thiết lập của nó), có nghĩa là chúng ta thiết kế một phần của trình điều khiển chỉ để cho phép DI và thực hiện giao diện mà chúng ta không cần, hoặc chúng ta sử dụng một khung bên thứ ba như Ninject để tránh phải thiết kế bộ điều khiển theo cách này, nhưng chúng ta vẫn phải tạo giao diện.

Một số người trong nhóm dường như miễn cưỡng thiết kế mã chỉ vì mục đích thử nghiệm. Dường như với tôi rằng phải có một sự thỏa hiệp nào đó nếu bạn hy vọng kiểm tra đơn vị, nhưng tôi không chắc làm thế nào để làm giảm sự lo lắng của họ.

Rõ ràng, đây là một dự án hoàn toàn mới, vì vậy nó không thực sự về việc sửa đổi mã để cho phép thử nghiệm đơn vị; đó là về việc thiết kế mã chúng ta sẽ viết để có thể kiểm tra được đơn vị.


33
Hãy để tôi nhắc lại điều này: các đồng nghiệp của bạn muốn kiểm tra đơn vị cho mã mới, nhưng họ từ chối viết mã theo cách đơn vị có thể kiểm tra được, mặc dù không có rủi ro trong việc phá vỡ bất cứ điều gì hiện có? Nếu đó là sự thật, bạn nên chấp nhận câu trả lời của @ KilianFoth và yêu cầu anh ấy làm nổi bật câu đầu tiên trong câu trả lời in đậm! Đồng nghiệp của bạn rõ ràng có một sự hiểu lầm rất lớn về công việc của họ là gì.
Doc Brown

20
@Lee: Ai nói rằng tách rời luôn là một ý tưởng tốt? Bạn đã bao giờ thấy một cơ sở mã trong đó mọi thứ được thông qua như một giao diện được tạo từ một nhà máy giao diện sử dụng một số giao diện cấu hình? Tôi có; nó được viết bằng Java và nó là một mớ hỗn độn, lỗi không thể nhầm lẫn. Phân tách cực đoan là mã obfuscation.
Christian Hackl

8
Hoạt động hiệu quả với Mã di sản của Michael Feathers xử lý rất tốt vấn đề này và sẽ cho bạn ý tưởng tốt về các lợi thế của việc thử nghiệm ngay cả trong một cơ sở mã mới.
l0b0

8
@ l0b0 Đó là khá nhiều kinh thánh cho việc này. Trên stackexchange nó sẽ không phải là một câu trả lời cho câu hỏi, nhưng trong RL tôi sẽ nói với OP để có được cuốn sách này đọc nó (ít nhất là một phần). OP, hãy làm việc hiệu quả với Legacy Code và đọc nó, ít nhất là một phần (hoặc nói với sếp của bạn để có được nó). Nó giải quyết các câu hỏi như thế này. Đặc biệt là nếu bạn không thực hiện thử nghiệm và bây giờ bạn đang tham gia vào nó - bạn có thể có 20 năm kinh nghiệm, nhưng bây giờ bạn sẽ làm những việc bạn không có kinh nghiệm . Thật dễ dàng để đọc về chúng hơn là cố gắng tìm hiểu tất cả những điều đó bằng cách thử và sai.
R. Schmitz

4
Cảm ơn lời giới thiệu của cuốn sách của Michael Feathers, tôi chắc chắn sẽ chọn một bản sao.
Lee

Câu trả lời:


204

Miễn cưỡng sửa đổi mã vì mục đích thử nghiệm cho thấy rằng một nhà phát triển đã không hiểu vai trò của các thử nghiệm và theo hàm ý, vai trò của chính họ trong tổ chức.

Kinh doanh phần mềm xoay quanh việc cung cấp một cơ sở mã tạo ra giá trị kinh doanh. Chúng tôi đã tìm thấy, qua kinh nghiệm lâu dài và cay đắng, rằng chúng tôi không thể tạo ra các cơ sở mã như vậy có kích thước không cần thiết mà không cần thử nghiệm. Do đó, bộ thử nghiệm là một phần không thể thiếu của doanh nghiệp.

Nhiều lập trình viên trả tiền dịch vụ môi theo nguyên tắc này nhưng trong tiềm thức không bao giờ chấp nhận nó. Thật dễ hiểu tại sao điều này là; nhận thức rằng khả năng tinh thần của chúng ta không phải là vô hạn, và trên thực tế, bị hạn chế một cách đáng ngạc nhiên khi phải đối mặt với sự phức tạp to lớn của một cơ sở mã hiện đại, không được chào đón và dễ dàng bị triệt tiêu hoặc hợp lý hóa. Việc mã kiểm tra không được giao cho khách hàng giúp bạn dễ dàng tin rằng đó là một công dân hạng hai và không thiết yếu so với mã doanh nghiệp "thiết yếu". Và ý tưởng về việc thêm mã thử nghiệm vào mã doanh nghiệp dường như gây khó chịu cho nhiều người.

Rắc rối với việc chứng minh thực tiễn này có liên quan đến thực tế là toàn bộ bức tranh về cách tạo ra giá trị trong kinh doanh phần mềm thường chỉ được hiểu bởi những người cấp cao trong hệ thống phân cấp công ty, nhưng những người này không có hiểu biết kỹ thuật chi tiết về quy trình mã hóa được yêu cầu để hiểu lý do tại sao thử nghiệm không thể được loại bỏ. Do đó, họ thường quá bình tĩnh bởi các học viên đảm bảo với họ rằng xét nghiệm có thể là một ý tưởng tốt nói chung, nhưng "Chúng tôi là những lập trình viên ưu tú, những người không cần nạng như vậy" hoặc "chúng tôi không có thời gian cho việc đó ngay bây giờ", v.v. Thực tế là thành công trong kinh doanh là một trò chơi số và tránh nợ kỹ thuật, đảm bảo chất lượng vv cho thấy giá trị của nó chỉ trong dài hạn có nghĩa là họ thường khá chân thành trong niềm tin đó.

Câu chuyện dài: làm cho mã có thể kiểm tra là một phần thiết yếu của quá trình phát triển, không khác gì trong các lĩnh vực khác (nhiều vi mạch được thiết kế với một tỷ lệ đáng kể các yếu tố chỉ dành cho mục đích thử nghiệm), nhưng rất dễ bỏ qua những lý do rất tốt cho cái đó. Đừng rơi vào cái bẫy đó.


39
Tôi sẽ tranh luận nó phụ thuộc vào loại thay đổi. Có một sự khác biệt giữa việc làm cho mã dễ kiểm tra hơn và giới thiệu các móc cụ thể kiểm tra KHÔNG BAO GIỜ được sử dụng trong sản xuất. Cá nhân tôi cảnh giác với người đến sau, bởi vì Murphy ...
Matthieu M.

61
Các thử nghiệm đơn vị thường phá vỡ đóng gói và làm cho mã được thử nghiệm trở nên phức tạp hơn so với yêu cầu khác (ví dụ: bằng cách giới thiệu các loại giao diện bổ sung hoặc thêm cờ). Như mọi khi trong công nghệ phần mềm, mọi thực hành tốt và mọi quy tắc tốt đều có phần trách nhiệm pháp lý. Việc mù quáng sản xuất nhiều bài kiểm tra đơn vị có thể có tác động bất lợi đến giá trị doanh nghiệp, chưa kể đến việc viết và duy trì các bài kiểm tra đã tốn thời gian và công sức. Theo kinh nghiệm của tôi, các bài kiểm tra tích hợp có ROI lớn hơn nhiều và có xu hướng cải thiện kiến ​​trúc phần mềm với ít thỏa hiệp hơn.
Christian Hackl

20
@Lee Chắc chắn nhưng bạn cần xem xét liệu có một loại thử nghiệm cụ thể nào đảm bảo sự gia tăng độ phức tạp của mã hay không. Kinh nghiệm cá nhân của tôi là các bài kiểm tra đơn vị là một công cụ tuyệt vời cho đến khi chúng yêu cầu thay đổi thiết kế cơ bản để phù hợp với chế độ chế nhạo. Đó là nơi tôi chuyển sang một loại thử nghiệm khác. Viết các bài kiểm tra đơn vị với chi phí làm cho kiến ​​trúc phức tạp hơn đáng kể, với mục đích duy nhất là có các bài kiểm tra đơn vị, là nhìn chằm chằm.
Konrad Rudolph

21
@ChristianHackl tại sao một đơn vị kiểm tra phá vỡ đóng gói? Tôi đã thấy rằng đối với mã tôi đã làm việc, nếu có nhu cầu nhận biết thêm chức năng bổ sung để cho phép thử nghiệm, thì vấn đề thực tế là chức năng bạn đang kiểm tra cần tái cấu trúc, vì vậy tất cả các chức năng đều giống nhau mức độ trừu tượng (đó là sự khác biệt về mức độ trừu tượng thường tạo ra "nhu cầu" này cho mã bổ sung), với mã cấp thấp hơn được chuyển sang các hàm (có thể kiểm tra) của riêng chúng.
Baldrickk

29
@ChristianHackl Các bài kiểm tra đơn vị không bao giờ phá vỡ đóng gói, nếu bạn đang cố truy cập các biến riêng tư, được bảo vệ hoặc cục bộ từ một bài kiểm tra đơn vị, bạn đã làm sai. Nếu bạn đang kiểm tra chức năng foo, bạn chỉ kiểm tra nếu nó thực sự hoạt động, chứ không phải nếu biến cục bộ x là căn bậc hai của đầu vào y trong lần lặp thứ ba của vòng lặp thứ hai. Nếu một số chức năng là riêng tư thì cũng vậy, bạn sẽ thử nghiệm nó một cách liên tục. nếu nó thực sự lớn và riêng tư? Đó là một lỗ hổng thiết kế, nhưng có lẽ thậm chí không thể có bên ngoài C và C ++ với phân tách thực hiện tiêu đề.
opa

75

Nó không đơn giản như bạn nghĩ. Hãy phá vỡ nó.

  • Viết bài kiểm tra đơn vị chắc chắn là một điều tốt.

NHƯNG!

  • Bất kỳ thay đổi mã của bạn có thể giới thiệu một lỗi. Vì vậy, thay đổi mã mà không có lý do kinh doanh tốt không phải là một ý tưởng tốt.

  • Webapi 'rất mỏng' của bạn dường như không phải là trường hợp lớn nhất để thử nghiệm đơn vị.

  • Thay đổi mã và kiểm tra cùng một lúc là một điều xấu.

Tôi muốn đề xuất cách tiếp cận sau:

  1. Viết các bài kiểm tra tích hợp . Điều này không nên yêu cầu bất kỳ thay đổi mã. Nó sẽ cung cấp cho bạn các trường hợp kiểm tra cơ bản của bạn và cho phép bạn kiểm tra xem có bất kỳ thay đổi mã nào khác mà bạn thực hiện không đưa ra bất kỳ lỗi nào không.

  2. Đảm bảo mã mới có thể kiểm tra được và có các bài kiểm tra đơn vị và tích hợp.

  3. Đảm bảo chuỗi CI của bạn chạy thử nghiệm sau khi xây dựng và triển khai.

Khi bạn đã thiết lập những thứ đó, chỉ sau đó bắt đầu suy nghĩ về việc tái cấu trúc các dự án cũ để kiểm tra.

Hy vọng mọi người sẽ học được bài học từ quy trình và có một ý tưởng tốt về nơi cần thử nghiệm nhất, cách bạn muốn cấu trúc nó và giá trị mà nó mang lại cho doanh nghiệp.

EDIT : Kể từ khi tôi viết câu trả lời này, OP đã làm rõ câu hỏi để cho thấy rằng họ đang nói về mã mới, chứ không phải sửa đổi mã hiện có. Có lẽ tôi ngây thơ nghĩ rằng "Thử nghiệm đơn vị có tốt không?" cuộc tranh cãi đã được giải quyết vài năm trước.

Thật khó để tưởng tượng những thay đổi mã nào sẽ được yêu cầu bởi các bài kiểm tra đơn vị nhưng không phải là thông lệ tốt chung mà bạn muốn trong mọi trường hợp. Có lẽ sẽ là khôn ngoan khi kiểm tra các phản đối thực tế, có thể đó là phong cách thử nghiệm đơn vị đang bị phản đối.


12
Đây là một câu trả lời tốt hơn nhiều so với câu trả lời được chấp nhận. Sự mất cân bằng trong phiếu bầu là mất tinh thần.
Konrad Rudolph

4
@Lee Một bài kiểm tra đơn vị nên kiểm tra một đơn vị chức năng , có thể hoặc không thể tương ứng với một lớp. Một đơn vị chức năng nên được kiểm tra tại giao diện của nó (có thể là API trong trường hợp này). Thử nghiệm có thể làm nổi bật mùi thiết kế và sự cần thiết phải áp dụng một số mức độ khác nhau / nhiều hơn. Xây dựng hệ thống của bạn từ những mảnh ghép nhỏ, chúng sẽ dễ dàng lý luận và kiểm tra hơn.
Wes Toleman

2
@KonradRudolph: Tôi đoán đã bỏ lỡ điểm mà OP nói thêm rằng câu hỏi này là về thiết kế mã mới, không thay đổi mã hiện có. Vì vậy, không có gì để phá vỡ, mà làm cho hầu hết câu trả lời này không áp dụng.
Doc Brown

1
Tôi hoàn toàn không đồng ý với tuyên bố rằng viết bài kiểm tra đơn vị luôn là một điều tốt. Kiểm tra đơn vị là tốt chỉ trong một số trường hợp. Thật ngớ ngẩn khi sử dụng các bài kiểm tra đơn vị để kiểm tra mã frontend (UI), chúng được thực hiện để kiểm tra logic nghiệp vụ. Ngoài ra, thật tốt khi viết các bài kiểm tra đơn vị để thay thế các kiểm tra biên dịch bị thiếu (ví dụ: trong Javascript). Hầu hết các mã chỉ có giao diện nên viết riêng thử nghiệm đầu cuối, không phải thử nghiệm đơn vị.
Sulthan

1
Các thiết kế chắc chắn có thể bị "thiệt hại do thử nghiệm". Thông thường khả năng kiểm tra cải thiện thiết kế: Bạn nhận thấy khi viết bài kiểm tra rằng một cái gì đó không thể tìm nạp nhưng phải được truyền vào, làm cho giao diện rõ ràng hơn, v.v. Nhưng đôi khi bạn sẽ vấp phải thứ gì đó đòi hỏi một thiết kế không thoải mái chỉ để thử nghiệm. Một ví dụ có thể là một hàm tạo chỉ kiểm tra được yêu cầu trong mã mới của bạn do mã bên thứ ba hiện có sử dụng một singleton chẳng hạn. Khi điều đó xảy ra: lùi lại một bước và chỉ thực hiện kiểm tra tích hợp, thay vì làm hỏng thiết kế của chính bạn dưới danh nghĩa kiểm tra.
Anders Forsgren

18

Thiết kế mã để có thể kiểm tra vốn không phải là mùi mã; ngược lại, đó là dấu hiệu của một thiết kế tốt. Có một số mẫu thiết kế nổi tiếng và được sử dụng rộng rãi dựa trên điều này (ví dụ, Model-View-Presenter) cung cấp thử nghiệm dễ dàng (dễ dàng hơn) là một lợi thế lớn.

Vì vậy, nếu bạn cần viết một giao diện cho lớp cụ thể của mình để dễ dàng kiểm tra nó hơn, đó là một điều tốt. Nếu bạn đã có lớp cụ thể, hầu hết các IDE có thể trích xuất một giao diện từ nó, làm cho nỗ lực cần tối thiểu. Sẽ cần thêm một chút công việc để giữ cho cả hai đồng bộ, nhưng dù sao giao diện cũng không nên thay đổi nhiều và lợi ích từ việc thử nghiệm có thể vượt xa nỗ lực thêm đó.

Mặt khác, như @MatthieuM. được đề cập trong một nhận xét, nếu bạn thêm các điểm nhập cụ thể vào mã của mình mà không bao giờ được sử dụng trong sản xuất, chỉ vì mục đích thử nghiệm, đó có thể là một vấn đề.


Vấn đề đó có thể được giải quyết thông qua phân tích mã tĩnh - gắn cờ các phương thức (ví dụ: phải được đặt tên _ForTest) và kiểm tra cơ sở mã cho các cuộc gọi từ mã không kiểm tra.
Đi xe đạp

13

IMHO rất đơn giản để hiểu rằng để tạo các bài kiểm tra đơn vị, mã được kiểm tra phải có ít nhất một số thuộc tính nhất định. Ví dụ: nếu mã không bao gồm các đơn vị riêng lẻ có thể được kiểm tra riêng rẽ , từ "kiểm thử đơn vị" thậm chí không bắt đầu có nghĩa. Nếu mã không có các thuộc tính này, nó phải được thay đổi trước, điều đó khá rõ ràng.

Nói rằng, về mặt lý thuyết, trước tiên, người ta có thể thử viết một số đơn vị mã có thể kiểm tra, áp dụng tất cả các nguyên tắc RẮN và sau đó thử viết một bài kiểm tra cho nó sau đó, mà không cần sửa đổi thêm mã gốc. Thật không may, việc viết mã thực sự có thể kiểm tra được đơn vị không phải lúc nào cũng đơn giản, do đó, rất có thể sẽ có một số thay đổi cần thiết mà người ta sẽ chỉ phát hiện khi cố gắng tạo các bài kiểm tra. Điều này đúng với mã ngay cả khi được viết với ý tưởng kiểm thử đơn vị và chắc chắn đúng hơn với mã được viết trong đó "khả năng kiểm thử đơn vị" không có trong chương trình nghị sự lúc đầu.

Có một cách tiếp cận nổi tiếng cố gắng giải quyết vấn đề bằng cách viết các bài kiểm tra đơn vị trước - nó được gọi là Phát triển hướng thử nghiệm (TDD), và nó chắc chắn có thể giúp làm cho mã dễ kiểm tra hơn ngay từ đầu.

Tất nhiên, sự miễn cưỡng thay đổi mã sau đó để làm cho nó có thể kiểm tra được phát sinh thường xuyên trong tình huống mã được kiểm tra thủ công trước và / hoặc hoạt động tốt trong sản xuất, vì vậy việc thay đổi nó thực sự có thể gây ra lỗi mới, đó là sự thật. Cách tiếp cận tốt nhất để giảm thiểu điều này là trước tiên tạo bộ kiểm thử hồi quy (thường có thể được thực hiện chỉ với những thay đổi rất nhỏ đối với cơ sở mã), cũng như các biện pháp đi kèm khác như đánh giá mã hoặc phiên kiểm tra thủ công mới. Điều đó sẽ giúp bạn có đủ tự tin để đảm bảo thiết kế lại một số nội bộ không phá vỡ bất cứ điều gì quan trọng.


Thật thú vị khi bạn đề cập đến TDD. Chúng tôi đang cố gắng mang lại BDD / TDD, cũng đã gặp phải một số kháng cự - cụ thể là "mã tối thiểu để vượt qua" thực sự có nghĩa là gì.
Lee

2
@Lee: mang lại những thay đổi trong một tổ chức luôn gây ra một số kháng cự và nó luôn cần một chút thời gian để thích nghi với những điều mới, đó không phải là sự khôn ngoan mới. Đây là một vấn đề của mọi người.
Doc Brown

Chắc chắn rồi. Tôi chỉ ước chúng ta sẽ có thêm thời gian!
Lee

Nó thường là một vấn đề cho mọi người thấy rằng làm theo cách này sẽ giúp họ tiết kiệm thời gian (và hy vọng cũng nhanh chóng). Tại sao làm điều gì đó sẽ không có lợi cho bạn?
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen: Nhóm cũng có thể cho OP thấy rằng cách tiếp cận của họ sẽ tiết kiệm thời gian. Ai biết? Nhưng tôi tự hỏi nếu chúng ta không thực sự phải đối mặt với các vấn đề có bản chất kỹ thuật ít hơn ở đây; OP tiếp tục đến đây để cho chúng tôi biết đội của anh ấy đã làm gì sai (theo ý kiến ​​của anh ấy), như thể anh ấy đang cố gắng tìm kiếm đồng minh cho sự nghiệp của mình. Có thể có lợi hơn khi thực sự thảo luận về dự án cùng với nhóm, chứ không phải với những người lạ trên Stack Exchange.
Christian Hackl

11

Tôi có vấn đề với khẳng định (không có căn cứ) mà bạn đưa ra:

để kiểm tra đơn vị dịch vụ API Web, chúng tôi sẽ cần phải giả định nhà máy này

Điều đó không hẳn đúng. Có rất nhiều cách để viết bài kiểm tra, và có nhiều cách để viết bài kiểm tra đơn vị không liên quan đến giả. Quan trọng hơn, có các loại thử nghiệm khác, chẳng hạn như thử nghiệm chức năng hoặc tích hợp. Nhiều lần có thể tìm thấy "đường nối thử nghiệm" tại "giao diện" không phải là ngôn ngữ lập trình OOP interface.

Một số câu hỏi để giúp bạn tìm một đường may thử nghiệm thay thế, có thể tự nhiên hơn:

  • Tôi có bao giờ muốn viết một API Web mỏng trên một API khác không?
  • Tôi có thể giảm trùng lặp mã giữa API Web và API cơ bản không? Một cái có thể được tạo ra về mặt khác?
  • Tôi có thể coi toàn bộ API Web và API cơ bản dưới dạng một đơn vị "hộp đen" duy nhất và đưa ra các xác nhận một cách có ý nghĩa về cách toàn bộ hoạt động không?
  • Nếu API Web phải được thay thế bằng một triển khai mới trong tương lai, chúng ta sẽ thực hiện việc đó như thế nào?
  • Nếu API Web được thay thế bằng triển khai mới trong tương lai, liệu các máy khách của API Web có thể nhận thấy không? Nếu vậy thì thế nào?

Một khẳng định không có căn cứ khác mà bạn đưa ra là về DI:

chúng tôi hoặc thiết kế lớp trình điều khiển API Web để chấp nhận DI (thông qua hàm tạo hoặc trình thiết lập của nó), có nghĩa là chúng tôi thiết kế một phần của trình điều khiển chỉ để cho phép DI và thực hiện giao diện mà chúng tôi không cần, hoặc chúng tôi sử dụng bên thứ ba khung như Ninject để tránh phải thiết kế bộ điều khiển theo cách này, nhưng chúng ta vẫn phải tạo giao diện.

Phụ thuộc tiêm không nhất thiết có nghĩa là tạo ra một mới interface. Ví dụ: trong nguyên nhân của mã thông báo xác thực: bạn có thể chỉ cần tạo mã thông báo xác thực thực sự theo chương trình không? Sau đó, thử nghiệm có thể tạo ra các mã thông báo như vậy và tiêm chúng. Có phải quy trình xác thực mã thông báo phụ thuộc vào một loại bí mật mật mã nào đó không? Tôi hy vọng bạn chưa mã hóa bí mật - Tôi hy vọng bạn có thể đọc nó từ bộ lưu trữ bằng cách nào đó và trong trường hợp đó bạn chỉ cần sử dụng một bí mật khác (nổi tiếng) trong các trường hợp thử nghiệm của mình.

Điều này không có nghĩa là bạn không bao giờ nên tạo một cái mới interface. Nhưng đừng cố định chỉ có một cách để viết bài kiểm tra hoặc một cách để giả mạo một hành vi. Nếu bạn nghĩ bên ngoài hộp, bạn thường có thể tìm ra một giải pháp sẽ yêu cầu tối thiểu các mâu thuẫn của mã của bạn và vẫn mang lại cho bạn hiệu quả bạn muốn.


Quan điểm về các xác nhận liên quan đến giao diện, nhưng ngay cả khi chúng tôi không sử dụng chúng, chúng tôi vẫn phải tiêm đối tượng một số cách, đây là mối quan tâm của các thành viên còn lại trong nhóm. tức là một số người trong nhóm sẽ hài lòng với một ctr không tham số ngay lập tức triển khai cụ thể và để nó ở đó. Trong thực tế, một thành viên đã đưa ra ý tưởng sử dụng sự phản chiếu để tiêm giả, vì vậy chúng tôi không phải thiết kế mã để chấp nhận chúng. Đó là một mã reeky mùi imo
Lee

9

Bạn thật may mắn vì đây là một dự án mới. Tôi đã thấy rằng Test Driven Design hoạt động rất tốt để viết mã tốt (đó là lý do tại sao chúng tôi làm điều đó ngay từ đầu).

Bằng cách tìm hiểu trước cách gọi một đoạn mã nhất định với dữ liệu đầu vào thực tế và sau đó nhận dữ liệu đầu ra thực tế mà bạn có thể kiểm tra như dự định, bạn thực hiện thiết kế API từ rất sớm và có cơ hội nhận được thiết kế hữu ích bởi vì bạn không bị cản trở bởi mã hiện tại phải được viết lại thành chỗ ở. Ngoài ra nó dễ hiểu hơn bởi các đồng nghiệp của bạn để bạn có thể có các cuộc thảo luận tốt một lần nữa trong quá trình này.

Lưu ý rằng "hữu ích" trong câu trên có nghĩa là không chỉ các phương thức kết quả dễ gọi, mà còn có xu hướng nhận được các giao diện sạch, dễ xử lý trong các bài kiểm tra tích hợp và viết các mockup cho.

Hãy xem xét nó. Đặc biệt với đánh giá ngang hàng. Theo kinh nghiệm của tôi, đầu tư thời gian và công sức sẽ rất nhanh được hoàn trả.


Chúng tôi cũng có một vấn đề với TDD, cụ thể là cái gì tạo thành "mã tối thiểu để vượt qua". Tôi đã chứng minh cho nhóm quá trình này và họ đã ngoại lệ không chỉ viết những gì chúng tôi đã thiết kế - điều mà tôi có thể hiểu. "Tối thiểu" dường như không được xác định. Nếu chúng ta viết một bài kiểm tra và có kế hoạch và thiết kế rõ ràng, tại sao không viết nó để vượt qua bài kiểm tra?
Lee

@Lee "mã tối thiểu để vượt qua" ... tốt, điều này nghe có vẻ hơi ngu ngốc, nhưng nó thực sự là những gì nó nói. Ví dụ: nếu bạn có một bài kiểm tra UserCanChangeTheirPassword, thì trong bài kiểm tra bạn gọi hàm (chưa tồn tại) để thay đổi mật khẩu và sau đó bạn xác nhận rằng mật khẩu thực sự đã thay đổi. Sau đó, bạn viết hàm, cho đến khi bạn có thể chạy thử nghiệm và nó không đưa ra ngoại lệ cũng như không có xác nhận sai. Nếu tại thời điểm đó bạn có một lý do để thêm bất kỳ mã nào, thì lý do đó sẽ đi vào một thử nghiệm khác, ví dụ UserCantChangePasswordToEmptyString.
R. Schmitz

@Lee Cuối cùng, các bài kiểm tra của bạn cuối cùng sẽ là tài liệu về những gì mã của bạn làm, ngoại trừ tài liệu kiểm tra xem nó có được thực hiện hay không, thay vì chỉ viết trên giấy. Cũng so sánh với câu hỏi này - Một phương thức CalculateFactorialchỉ trả về 120 và bài kiểm tra vượt qua. Đó mức tối thiểu. Đó rõ ràng không phải là những gì đã được dự định, nhưng điều đó chỉ có nghĩa là bạn cần một bài kiểm tra khác để thể hiện những gì đã được dự định.
R. Schmitz

1
@Lee Bước nhỏ. Mức tối thiểu có thể nhiều hơn bạn nghĩ khi mã tăng trên tầm thường. Ngoài ra, thiết kế bạn thực hiện khi thực hiện toàn bộ một lần nữa có thể kém tối ưu hơn vì bạn đưa ra các giả định về cách thực hiện mà không cần viết các bài kiểm tra chứng minh điều đó. Một lần nữa hãy nhớ rằng, mã nên thất bại lúc đầu.
Thorbjørn Ravn Andersen

1
Ngoài ra, kiểm tra hồi quy là rất quan trọng. Họ có trong phạm vi cho đội không?
Thorbjørn Ravn Andersen

8

Nếu bạn cần sửa đổi mã, đó là mùi mã.

Từ kinh nghiệm cá nhân, nếu mã của tôi khó viết bài kiểm tra, thì đó là mã xấu. Đó không phải là mã xấu bởi vì nó không chạy hoặc hoạt động như thiết kế, nó xấu vì tôi không thể nhanh chóng hiểu tại sao nó hoạt động. Nếu tôi gặp phải một lỗi, tôi biết đó sẽ là một công việc lâu dài để sửa nó. Mã này cũng khó / không thể sử dụng lại.

Mã tốt (Sạch) chia các nhiệm vụ thành các phần nhỏ dễ hiểu hơn trong nháy mắt (hoặc ít nhất là một cái nhìn tốt). Kiểm tra các phần nhỏ hơn là dễ dàng. Tôi cũng có thể viết các bài kiểm tra chỉ kiểm tra một đoạn của cơ sở mã dễ dàng tương tự nếu tôi khá tự tin về các phần phụ (việc sử dụng lại cũng giúp ở đây vì nó đã được kiểm tra).

Giữ mã dễ kiểm tra, dễ tái cấu trúc và dễ sử dụng lại từ đầu và bạn sẽ không tự sát bất cứ khi nào bạn cần thay đổi.

Tôi đang gõ cái này trong khi xây dựng lại hoàn toàn một dự án đáng lẽ phải là một nguyên mẫu bỏ đi thành mã sạch hơn. Sẽ tốt hơn nhiều để có được nó ngay từ đầu và tái cấu trúc mã xấu càng sớm càng tốt thay vì nhìn chằm chằm vào màn hình hàng giờ liền vì sợ chạm vào bất cứ thứ gì vì sợ phá vỡ thứ gì đó hoạt động một phần.


3
"Nguyên mẫu vứt đi" - mỗi dự án duy nhất từng bắt đầu cuộc sống như một trong những ... tốt nhất để nghĩ về những điều không bao giờ được như vậy. gõ cái này là tôi .. đoán xem? ... tái cấu trúc một nguyên mẫu vứt đi mà hóa ra không phải;)
Algy Taylor

4
Nếu bạn muốn chắc chắn rằng một nguyên mẫu vứt đi sẽ bị vứt đi, hãy viết nó bằng ngôn ngữ nguyên mẫu sẽ không bao giờ được phép trong sản xuất. Clojure và Python là những lựa chọn tốt.
Thorbjørn Ravn Andersen

2
@ ThorbjørnRavnAndersen Điều đó làm tôi cười thầm. Điều đó có nghĩa là đào tại những ngôn ngữ đó? :)
Lee

@Lee. Không, chỉ là ví dụ về các ngôn ngữ có thể không được chấp nhận để sản xuất - thông thường bởi vì không ai trong tổ chức có thể duy trì chúng vì họ không quen thuộc với chúng và đường cong học tập của chúng rất dốc. Nếu những cái đó, được chấp nhận, chọn cái khác không.
Thorbjørn Ravn Andersen

4

Tôi sẽ lập luận rằng viết mã không thể được kiểm tra đơn vị là mùi mã. Nói chung, nếu mã của bạn không thể được kiểm tra đơn vị, thì nó không phải là mô-đun, điều này gây khó khăn cho việc hiểu, duy trì hoặc nâng cao. Có thể nếu mã là mã keo thực sự có ý nghĩa về mặt kiểm thử tích hợp, bạn có thể thay thế thử nghiệm tích hợp để thử nghiệm đơn vị, nhưng ngay cả khi tích hợp thất bại, bạn sẽ phải cách ly vấn đề và thử nghiệm đơn vị là một cách tuyệt vời để làm đi.

Bạn nói

Chúng tôi dự định tạo một nhà máy sẽ trả về một loại phương thức xác thực. Chúng tôi không cần nó để kế thừa một giao diện vì chúng tôi không dự đoán nó sẽ là bất cứ thứ gì khác ngoài loại cụ thể. Tuy nhiên, để kiểm tra đơn vị dịch vụ API Web, chúng tôi sẽ cần phải giả định nhà máy này.

Tôi không thực sự làm theo điều này. Lý do để có một nhà máy tạo ra thứ gì đó là cho phép bạn thay đổi nhà máy hoặc thay đổi những gì nhà máy tạo ra dễ dàng, vì vậy các phần khác của mã không cần phải thay đổi. Nếu phương thức xác thực của bạn sẽ không bao giờ thay đổi, thì nhà máy là mã phình vô dụng. Tuy nhiên, nếu bạn muốn có một phương thức xác thực khác trong thử nghiệm so với trong sản xuất, có một nhà máy trả về một phương thức xác thực khác trong thử nghiệm so với trong sản xuất là một giải pháp tuyệt vời.

Bạn không cần DI hoặc Mocks cho việc này. Bạn chỉ cần nhà máy của mình hỗ trợ các loại xác thực khác nhau và để nó có thể được cấu hình bằng cách nào đó, chẳng hạn như từ tệp cấu hình hoặc biến môi trường.


2

Trong mọi chuyên ngành kỹ thuật tôi có thể nghĩ ra, chỉ có một cách để đạt được mức chất lượng khá hoặc cao hơn:

Để kiểm tra / kiểm tra trong thiết kế.

Điều này đúng trong xây dựng, thiết kế chip, phát triển phần mềm và sản xuất. Bây giờ, điều này không có nghĩa là thử nghiệm là trụ cột mà mọi thiết kế cần phải được xây dựng xung quanh, hoàn toàn không. Nhưng với mỗi quyết định thiết kế, các nhà thiết kế phải rõ ràng về các tác động đối với chi phí thử nghiệm và đưa ra quyết định có ý thức về sự đánh đổi.

Trong một số trường hợp, thử nghiệm thủ công hoặc tự động (ví dụ Selenium) sẽ thuận tiện hơn so với Thử nghiệm đơn vị, đồng thời cũng cung cấp phạm vi thử nghiệm chấp nhận được. Trong những trường hợp hiếm hoi ném thứ gì đó ra ngoài mà gần như chưa được kiểm tra cũng có thể được chấp nhận. Nhưng những điều này phải được ý thức từng trường hợp quyết định. Gọi một thiết kế để kiểm tra "mùi mã" cho thấy sự thiếu kinh nghiệm nghiêm trọng.


1

Tôi đã thấy rằng thử nghiệm đơn vị (và các loại thử nghiệm tự động khác) có xu hướng giảm mùi mã và tôi không thể nghĩ ra một ví dụ duy nhất nơi chúng giới thiệu mùi mã. Kiểm tra đơn vị thường buộc bạn phải viết mã tốt hơn. Nếu bạn không thể sử dụng một phương pháp dễ dàng trong thử nghiệm, tại sao nó lại dễ dàng hơn trong mã của bạn?

Các bài kiểm tra đơn vị được viết tốt cho bạn thấy cách mã được dự định sẽ được sử dụng. Chúng là một hình thức của tài liệu thực thi. Tôi đã thấy các bài kiểm tra đơn vị quá dài bằng văn bản mà không thể hiểu được. Đừng viết những cái đó! Nếu bạn cần viết các bài kiểm tra dài để thiết lập các lớp của mình, các lớp của bạn cần tái cấu trúc.

Các bài kiểm tra đơn vị sẽ làm nổi bật nơi một số mã của bạn có mùi. Tôi sẽ khuyên bạn nên đọc hiệu quả của Michael C. Feathers với Bộ luật kế thừa . Mặc dù dự án của bạn là mới, nhưng nếu nó chưa có bất kỳ (hoặc nhiều) bài kiểm tra đơn vị nào, bạn có thể cần một số kỹ thuật không rõ ràng để có được mã của bạn để kiểm tra độc đáo.


3
Bạn có thể được giới thiệu rất nhiều lớp cảm ứng để có thể thử nghiệm, và sau đó không bao giờ sử dụng chúng như mong đợi.
Thorbjørn Ravn Andersen

1

Tóm lại:

Mã có thể kiểm tra là (thường) mã có thể bảo trì - hay đúng hơn, mã khó kiểm tra thường khó bảo trì. Thiết kế mã không thể kiểm tra được cũng giống như thiết kế một máy không thể sửa chữa được - thật đáng tiếc cho người nghèo khó sẽ được chỉ định sửa chữa nó (cuối cùng có thể là bạn).

Một ví dụ là chúng tôi dự định tạo một nhà máy sẽ trả về loại phương thức xác thực. Chúng tôi không cần nó để kế thừa một giao diện vì chúng tôi không dự đoán nó sẽ là bất cứ thứ gì khác ngoài loại cụ thể.

Bạn biết rằng bạn sẽ cần năm loại phương thức xác thực khác nhau trong ba năm, bây giờ bạn đã nói điều đó, phải không? Yêu cầu thay đổi, và trong khi bạn nên tránh áp đảo thiết kế của mình, có một thiết kế có thể kiểm tra được có nghĩa là thiết kế của bạn có (chỉ) đủ đường may để được thay đổi mà không bị đau (quá nhiều) - và các thử nghiệm mô-đun sẽ cung cấp cho bạn phương tiện tự động để thấy rằng những thay đổi của bạn không phá vỡ bất cứ điều gì.


1

Thiết kế xung quanh việc tiêm phụ thuộc không phải là mùi mã - đó là cách tốt nhất. Sử dụng DI không chỉ để kiểm tra. Xây dựng các thành phần của bạn xung quanh DI hỗ trợ tính mô đun và tái sử dụng, dễ dàng hơn cho phép các thành phần chính được hoán đổi (chẳng hạn như lớp giao diện cơ sở dữ liệu). Mặc dù nó thêm một mức độ phức tạp, nhưng được thực hiện đúng, nó cho phép phân tách tốt hơn các lớp và cách ly chức năng, giúp cho sự phức tạp dễ quản lý và điều hướng hơn. Điều này giúp dễ dàng xác nhận chính xác hành vi của từng thành phần, giảm lỗi và cũng có thể giúp theo dõi lỗi dễ dàng hơn.


1
"làm đúng" là một vấn đề. Tôi phải duy trì hai dự án trong đó DI đã làm sai (mặc dù mục đích là làm "đúng"). Điều này làm cho mã đơn giản khủng khiếp và tồi tệ hơn nhiều so với các dự án cũ mà không có DI và thử nghiệm đơn vị. Bắt DI đúng không dễ.
Jan

@Jan thật thú vị. Làm thế nào mà họ làm điều đó sai?
Lee

1
@Lee Một dự án là một dịch vụ cần thời gian bắt đầu nhanh nhưng khởi động chậm khủng khiếp vì tất cả việc khởi tạo lớp được thực hiện trước bởi khung DI (Castle Windsor trong C #). Một vấn đề khác mà tôi thấy trong các dự án này là trộn lẫn DI với việc tạo các đối tượng với "mới", vượt qua DI. Điều đó làm cho việc kiểm tra khó khăn một lần nữa và dẫn đến một số điều kiện cuộc đua khó chịu.
Jan

1

Điều này về cơ bản có nghĩa là chúng ta hoặc thiết kế lớp trình điều khiển API Web để chấp nhận DI (thông qua hàm tạo hoặc trình thiết lập của nó), có nghĩa là chúng ta thiết kế một phần của trình điều khiển chỉ để cho phép DI và thực hiện giao diện mà chúng ta không cần, hoặc chúng ta sử dụng một khung bên thứ ba như Ninject để tránh phải thiết kế bộ điều khiển theo cách này, nhưng chúng ta vẫn phải tạo giao diện.

Hãy xem sự khác biệt giữa một thử nghiệm:

public class MyController : Controller
{
    private readonly IMyDependency _thing;

    public MyController(IMyDependency thing)
    {
        _thing = thing;
    }
}

và bộ điều khiển không kiểm tra được:

public class MyController : Controller
{
}

Tùy chọn trước đây có đúng 5 dòng mã, hai trong số đó có thể được Visual Studio tự tạo. Khi bạn đã thiết lập khung tiêm phụ thuộc của mình để thay thế một loại cụ thể trong IMyDependencythời gian chạy - đối với bất kỳ khung DI nào, là một dòng mã khác - mọi thứ chỉ hoạt động, ngoại trừ bây giờ bạn có thể giả định và do đó kiểm tra bộ điều khiển của bạn với nội dung trái tim của bạn .

6 dòng mã bổ sung để cho phép kiểm tra ... và các đồng nghiệp của bạn đang tranh luận rằng "quá nhiều công việc"? Cuộc tranh cãi đó không bay cùng tôi, và nó không nên bay cùng bạn.

Và bạn không phải tạo và triển khai giao diện để thử nghiệm: Moq , ví dụ, cho phép bạn mô phỏng hành vi của một loại cụ thể cho mục đích thử nghiệm đơn vị. Tất nhiên, điều đó sẽ không hữu dụng với bạn nếu bạn không thể đưa những loại đó vào các lớp bạn đang kiểm tra.

Tiêm phụ thuộc là một trong những điều mà một khi bạn hiểu nó, bạn tự hỏi "làm thế nào tôi làm việc mà không có điều này?". Nó đơn giản, hiệu quả và nó chỉ tạo cảm giác. Xin vui lòng, không cho phép đồng nghiệp thiếu hiểu biết về những điều mới để cản trở dự án của bạn.


1
Những gì bạn nhanh chóng loại bỏ là "thiếu hiểu biết về những điều mới" có thể trở thành một sự hiểu biết tốt về những điều cũ. Phụ thuộc tiêm chắc chắn không phải là mới. Ý tưởng, và có lẽ là những triển khai sớm nhất, đã có hàng thập kỷ. Và vâng, tôi tin rằng câu trả lời của bạn là một ví dụ về mã trở nên phức tạp hơn do kiểm thử đơn vị, và có thể là một ví dụ về kiểm thử đơn vị phá vỡ đóng gói (vì ai nói rằng lớp có một hàm tạo công khai ngay từ đầu?). Tôi thường loại bỏ tiêm phụ thuộc từ các cơ sở mã mà tôi đã thừa hưởng từ người khác, vì sự đánh đổi.
Christian Hackl

Bộ điều khiển luôn có một hàm tạo công khai, ẩn hoặc không, bởi vì MVC yêu cầu nó. "Complicated" - có thể, nếu bạn không hiểu cách các nhà xây dựng làm việc. Đóng gói - có trong một số trường hợp, nhưng cuộc tranh luận về đóng gói DI và là một cuộc tranh luận đang diễn ra, mang tính chủ quan cao sẽ không giúp ích gì ở đây, và đặc biệt đối với hầu hết các ứng dụng, DI sẽ phục vụ bạn tốt hơn là đóng gói IMO.
Ian Kemp

Về các nhà xây dựng công cộng: thực sự, đó là một đặc thù của khung được sử dụng. Tôi đã suy nghĩ về trường hợp tổng quát hơn của một lớp học bình thường không được khởi tạo bởi một khung công tác. Tại sao bạn tin rằng việc xem các tham số phương thức bổ sung khi độ phức tạp thêm vào tương đương với sự thiếu hiểu biết về cách các nhà xây dựng làm việc? Tuy nhiên, tôi đánh giá cao việc bạn thừa nhận sự tồn tại của sự đánh đổi giữa DI và đóng gói.
Christian Hackl

0

Khi tôi viết bài kiểm tra đơn vị, tôi bắt đầu nghĩ về những gì có thể sai trong mã của mình. Nó giúp tôi cải thiện thiết kế mã và áp dụng nguyên tắc trách nhiệm duy nhất (SRP). Ngoài ra, khi tôi quay lại để sửa đổi cùng một mã một vài tháng sau đó, nó giúp tôi xác nhận rằng chức năng hiện có không bị hỏng.

Có một xu hướng sử dụng các chức năng thuần túy càng nhiều càng tốt (ứng dụng không có máy chủ). Kiểm thử đơn vị giúp tôi cách ly trạng thái và viết các hàm thuần túy.

Cụ thể, chúng tôi sẽ có một dịch vụ API Web sẽ rất mỏng. Trách nhiệm chính của nó sẽ là xử lý các yêu cầu / phản hồi trên web và gọi một API cơ bản có chứa logic nghiệp vụ.

Trước tiên hãy viết các bài kiểm tra đơn vị cho API cơ bản và nếu bạn có đủ thời gian phát triển, bạn cũng cần viết các bài kiểm tra cho dịch vụ API Web mỏng.

TL; DR, kiểm tra đơn vị giúp cải thiện chất lượng mã và giúp thực hiện các thay đổi trong tương lai để không có rủi ro mã. Nó cũng cải thiện khả năng đọc của mã. Sử dụng các bài kiểm tra thay vì bình luận để đưa ra quan điểm của bạn.


0

Điểm mấu chốt, và những gì nên tranh luận của bạn với rất nhiều bất đắc dĩ, là không có xung đột. Một sai lầm lớn dường như là ai đó đã đặt ra ý tưởng "thiết kế để thử nghiệm" cho những người ghét thử nghiệm. Họ chỉ nên ngậm miệng hoặc nói từ khác, như "hãy dành thời gian để làm điều này đúng".

Ý tưởng rằng "bạn phải thực hiện một giao diện" để làm cho một cái gì đó có thể kiểm tra được là sai. Giao diện đã được triển khai, nó chưa được khai báo trong khai báo lớp. Đó là vấn đề nhận ra các phương thức công khai hiện có, sao chép chữ ký của chúng vào một giao diện và khai báo giao diện đó trong khai báo của lớp. Không lập trình, không thay đổi logic hiện có.

Rõ ràng một số người có một ý tưởng khác về điều này. Tôi đề nghị bạn cố gắng khắc phục điều này đầu tiê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.