Là mã thử nghiệm mã tốt hơn?


103

Tôi đang cố gắng tập thói quen viết bài kiểm tra đơn vị thường xuyên bằng mã của mình, nhưng tôi đã đọc rằng điều quan trọng đầu tiên là viết mã có thể kiểm tra được . Câu hỏi này liên quan đến các nguyên tắc RẮN của việc viết mã kiểm tra, nhưng tôi muốn biết liệu các nguyên tắc thiết kế đó có lợi (hoặc ít nhất là không có hại) mà không có kế hoạch viết thử nghiệm nào không. Để làm rõ - tôi hiểu tầm quan trọng của bài kiểm tra viết; đây không phải là một câu hỏi về tính hữu dụng của chúng

Để minh họa cho sự nhầm lẫn của tôi, trong tác phẩm đã truyền cảm hứng cho câu hỏi này, người viết đưa ra một ví dụ về chức năng kiểm tra thời gian hiện tại và trả về một số giá trị tùy thuộc vào thời gian. Tác giả chỉ ra đây là mã xấu vì nó tạo ra dữ liệu (thời gian) mà nó sử dụng nội bộ, do đó gây khó khăn cho việc kiểm tra. Đối với tôi, mặc dù, có vẻ như quá mức cần thiết để vượt qua trong thời gian như là một cuộc tranh luận. Tại một số điểm giá trị cần phải được khởi tạo, và tại sao không gần nhất với tiêu dùng? Thêm vào đó, mục đích của phương pháp trong tâm trí tôi là trả về một số giá trị dựa trên thời gian hiện tại , bằng cách biến nó thành một tham số mà bạn ngụ ý rằng mục đích này có thể / nên được thay đổi. Điều này và các câu hỏi khác, khiến tôi tự hỏi liệu mã có thể kiểm tra có đồng nghĩa với mã "tốt hơn" không.

Viết mã thử nghiệm vẫn còn thực hành tốt ngay cả khi không có thử nghiệm?


Là mã thử nghiệm thực sự ổn định hơn? đã được đề xuất như là một bản sao. Tuy nhiên, câu hỏi đó là về "tính ổn định" của mã, nhưng tôi đang hỏi rộng hơn về việc liệu mã có tốt hơn không vì những lý do khác, chẳng hạn như khả năng đọc, hiệu suất, khớp nối, v.v.


24
Có một thuộc tính đặc biệt của hàm yêu cầu bạn vượt qua trong thời gian được gọi là idempotency. Hàm như vậy sẽ tạo ra kết quả tương tự mỗi lần nó được gọi với một giá trị đối số đã cho, điều này không chỉ làm cho nó dễ kiểm tra hơn, mà còn dễ tổng hợp hơn và dễ lý luận hơn.
Robert Harvey

4
Bạn có thể định nghĩa "mã tốt hơn"? bạn có nghĩa là "có thể duy trì"?, "dễ sử dụng hơn mà không cần IOC-Container-Magic"?
k3b

7
Tôi đoán bạn chưa bao giờ bị lỗi vì nó sử dụng thời gian thực của hệ thống và sau đó phần bù múi giờ đã thay đổi.
Andy

5
Nó là tốt hơn so với mã không thể kiểm chứng.
Tulains Córdova

14
@RobertHarvey Tôi sẽ không gọi đó là tính không minh bạch , tôi sẽ nói đó là tính minh bạch tham chiếu : nếu func(X)trả về "Morning", sau đó thay thế tất cả các lần xuất hiện func(X)bằng "Morning"sẽ không thay đổi chương trình (ví dụ: gọi funckhông làm gì khác ngoài trả về giá trị). Idempotency ngụ ý rằng func(func(X)) == X(không đúng loại) hoặc func(X); func(X);thực hiện các tác dụng phụ tương tự như func(X)(nhưng không có tác dụng phụ nào ở đây)
Warbo

Câu trả lời:


116

Liên quan đến định nghĩa chung của các bài kiểm tra đơn vị, tôi muốn nói không. Tôi đã thấy mã đơn giản được tạo ra do cần phải xoắn nó để phù hợp với khung thử nghiệm (ví dụ: giao diện và IoC ở mọi nơi khiến mọi thứ khó theo dõi qua các lớp của giao diện và dữ liệu nên được truyền qua phép thuật). Đưa ra lựa chọn giữa mã dễ hiểu hoặc mã dễ kiểm thử đơn vị, tôi đi với mã có thể duy trì mỗi lần.

Điều này không có nghĩa là không kiểm tra, nhưng để phù hợp với các công cụ phù hợp với bạn, không phải cách khác. Có nhiều cách khác để kiểm tra (nhưng mã khó hiểu luôn là mã xấu). Ví dụ: bạn có thể tạo các bài kiểm tra đơn vị ít chi tiết hơn (ví dụ: thái độ của Martin Fowler rằng một đơn vị nói chung là một lớp, không phải là một phương thức) hoặc thay vào đó bạn có thể đánh vào chương trình của mình bằng các bài kiểm tra tích hợp tự động. Như vậy có thể không đẹp như khung thử nghiệm của bạn sáng lên với các dấu tick màu xanh lá cây, nhưng chúng ta sau khi đã kiểm tra mã, không phải là quá trình ứng dụng của quá trình, phải không?

Bạn có thể làm cho mã của mình dễ bảo trì và vẫn tốt cho các bài kiểm tra đơn vị bằng cách xác định các giao diện tốt giữa chúng và sau đó viết các bài kiểm tra thực hiện giao diện chung của thành phần; hoặc bạn có thể có được một khung kiểm tra tốt hơn (một khung thay thế các hàm trong thời gian chạy để giả định chúng, thay vì yêu cầu mã được biên dịch với các giả định tại chỗ). Khung kiểm tra đơn vị tốt hơn cho phép bạn thay thế chức năng GetCienTime () của hệ thống bằng chức năng của riêng bạn, trong thời gian chạy, do đó bạn không cần phải giới thiệu trình bao bọc nhân tạo để phù hợp với công cụ kiểm tra.


3
Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Kỹ sư thế giới

2
Tôi nghĩ rằng đáng chú ý là tôi biết ít nhất một ngôn ngữ cho phép bạn thực hiện những gì đoạn cuối cùng của bạn mô tả: Python với Mock. Do cách thức nhập mô-đun hoạt động, khá nhiều thứ ngoài các từ khóa có thể được thay thế bằng một mô hình, các phương thức / lớp / API tiêu chuẩn. Vì vậy, điều đó là có thể, nhưng nó có thể yêu cầu ngôn ngữ phải được kiến ​​trúc theo cách hỗ trợ loại linh hoạt đó.
jpmc26

5
Tôi nghĩ rằng có một sự khác biệt giữa "mã có thể kiểm tra" và "mã [xoắn] để phù hợp với khung thử nghiệm". Tôi không chắc chắn mình sẽ đi đâu với bình luận này, ngoài việc nói rằng tôi đồng ý rằng mã "xoắn" là xấu và mã "có thể kiểm tra" với giao diện tốt là tốt.
Bryan Oakley

2
Tôi đã bày tỏ một số suy nghĩ của tôi trong các bình luận của bài viết (vì các bình luận mở rộng không được phép ở đây), hãy xem thử! Để rõ ràng: Tôi là tác giả của bài viết được đề cập :)
Sergey Kolodiy

Tôi phải đồng ý với @BryanOakley. "Mã có thể kiểm tra" đề xuất các mối quan tâm của bạn được tách riêng: có thể kiểm tra một khía cạnh (mô-đun) mà không bị can thiệp từ các khía cạnh khác. Tôi muốn nói rằng điều này khác với "điều chỉnh các quy ước thử nghiệm cụ thể hỗ trợ dự án của bạn". Điều này tương tự với các mẫu thiết kế: chúng không nên bị ép buộc. Mã sử dụng đúng các mẫu thiết kế sẽ được coi là mã mạnh. Áp dụng tương tự cho các nguyên tắc thử nghiệm. Nếu làm cho mã của bạn "có thể kiểm tra" dẫn đến việc xoắn mã dự án của bạn quá mức, bạn đã làm sai điều gì đó.
Vince Emigh

68

Viết mã kiểm tra vẫn còn thực hành tốt ngay cả khi không có kiểm tra?

Trước tiên, sự vắng mặt của các bài kiểm tra là một vấn đề lớn hơn so với mã của bạn có thể kiểm tra được hay không. Không có kiểm tra đơn vị có nghĩa là bạn không hoàn thành với mã / tính năng của mình.

Dù vậy, tôi sẽ không nói rằng việc viết mã có thể kiểm tra là rất quan trọng - việc viết mã linh hoạt là rất quan trọng . Mã không linh hoạt rất khó kiểm tra, do đó, có rất nhiều sự trùng lặp trong cách tiếp cận và mọi người gọi nó là gì.

Vì vậy, với tôi, luôn có một tập hợp các ưu tiên trong việc viết mã:

  1. Làm cho nó hoạt động - nếu mã không làm những gì nó cần làm, nó là vô giá trị.
  2. Làm cho nó có thể duy trì - nếu mã không thể bảo trì, nó sẽ nhanh chóng ngừng hoạt động.
  3. Làm cho nó linh hoạt - nếu mã không linh hoạt, nó sẽ ngừng hoạt động khi doanh nghiệp chắc chắn xuất hiện và hỏi liệu mã có thể thực hiện XYZ không.
  4. Làm cho nó nhanh - vượt quá mức cơ sở chấp nhận được, hiệu suất chỉ là hấp dẫn.

Kiểm tra đơn vị giúp mã duy trì, nhưng chỉ đến một điểm. Nếu bạn làm cho mã ít đọc hơn hoặc dễ vỡ hơn để làm cho các bài kiểm tra đơn vị hoạt động, điều đó sẽ phản tác dụng. "Mã có thể kiểm tra" nói chung là mã linh hoạt, vì vậy điều đó tốt, nhưng không quan trọng bằng chức năng hoặc khả năng bảo trì. Đối với một cái gì đó như thời điểm hiện tại, làm cho tính linh hoạt đó là tốt nhưng nó gây hại cho khả năng bảo trì bằng cách làm cho mã khó sử dụng hơn và phức tạp hơn. Vì khả năng bảo trì là quan trọng hơn, tôi thường sẽ sai lầm đối với cách tiếp cận đơn giản hơn ngay cả khi nó ít kiểm tra hơn.


4
Tôi thích mối quan hệ bạn chỉ ra giữa có thể kiểm tra và linh hoạt - điều đó làm cho toàn bộ vấn đề trở nên dễ hiểu hơn đối với tôi. Tính linh hoạt cho phép mã của bạn thích ứng, nhưng nhất thiết phải làm cho nó trừu tượng hơn và ít trực quan hơn để hiểu, nhưng đó là sự hy sinh đáng giá cho lợi ích.
WannabeCoder

3
điều đó nói rằng, tôi thường thấy các phương thức lẽ ra là riêng tư bị ép buộc ở cấp độ công cộng hoặc gói để khung kiểm tra đơn vị có thể truy cập trực tiếp vào chúng. Xa một cách tiếp cận lý tưởng.
jwenting

4
@WannabeCoder Tất nhiên, nó chỉ đáng để thêm tính linh hoạt khi cuối cùng nó giúp bạn tiết kiệm thời gian. Đó là lý do tại sao chúng tôi không viết mọi phương thức đơn lẻ trên một giao diện - hầu hết thời gian để viết lại mã dễ dàng hơn thay vì kết hợp quá nhiều tính linh hoạt từ khi khởi động. YAGNI vẫn là một nguyên tắc cực kỳ mạnh mẽ - chỉ cần đảm bảo rằng bất cứ điều gì "bạn sẽ không cần" là gì, việc thêm nó hồi tố sẽ không mang lại cho bạn nhiều công việc trung bình hơn là thực hiện trước thời hạn. Đó là mã không tuân theo YAGNI có nhiều vấn đề nhất với tính linh hoạt trong trải nghiệm của tôi.
Luaan

3
"Không có kiểm tra đơn vị nghĩa là bạn chưa hoàn thành mã / tính năng của mình" - Không đúng. "Định nghĩa hoàn thành" là điều mà nhóm quyết định. Nó có thể hoặc không bao gồm một số mức độ bao phủ thử nghiệm. Nhưng không nơi nào có một yêu cầu nghiêm ngặt nói rằng một tính năng không thể được "thực hiện" nếu không có thử nghiệm nào cho nó. Nhóm có thể chọn yêu cầu kiểm tra, hoặc có thể không.
aroth

3
@Telastyn Trong hơn 10 năm phát triển, tôi chưa bao giờ có một nhóm bắt buộc một khung thử nghiệm đơn vị, và chỉ có hai nhóm thậm chí có một (cả hai đều có phạm vi bảo hiểm kém). Một nơi yêu cầu một tài liệu từ về cách kiểm tra tính năng bạn đang viết. Đó là nó. Có lẽ tôi không may mắn? Tôi không phải là bài kiểm tra chống đơn vị (nghiêm túc, tôi sửa đổi trang web SQA.SE, tôi rất kiểm tra đơn vị!) Nhưng tôi không thấy chúng phổ biến như tuyên bố của bạn.
corsiKa

50

Vâng, đó là thực hành tốt. Lý do là khả năng kiểm tra không phải vì mục đích kiểm tra. Đó là vì lợi ích của sự rõ ràng và dễ hiểu mà nó mang theo.

Không ai quan tâm đến các bài kiểm tra chính mình. Một thực tế đáng buồn của cuộc sống là chúng ta cần các bộ kiểm tra hồi quy lớn bởi vì chúng ta không đủ xuất sắc để viết mã hoàn hảo mà không liên tục kiểm tra bước chân của mình. Nếu chúng ta có thể, khái niệm kiểm tra sẽ không được biết, và tất cả những điều này sẽ không thành vấn đề. Tôi chắc chắn ước tôi có thể. Nhưng kinh nghiệm đã chỉ ra rằng hầu hết tất cả chúng ta đều không thể, do đó các bài kiểm tra bao gồm mã của chúng tôi là một điều tốt ngay cả khi họ mất thời gian từ việc viết mã doanh nghiệp.

Làm thế nào để có các bài kiểm tra cải thiện mã doanh nghiệp của chúng tôi độc lập với bài kiểm tra? Bằng cách buộc chúng tôi phân chia chức năng của chúng tôi thành các đơn vị dễ dàng được chứng minh là chính xác. Những đơn vị này cũng dễ dàng có được quyền hơn những đơn vị chúng tôi muốn viết.

Ví dụ thời gian của bạn là một điểm tốt. Miễn là bạn chỉ có một chức năng trả về thời gian hiện tại, bạn có thể nghĩ rằng không có điểm nào để nó có thể lập trình được. Làm thế nào khó khăn để có được điều này phải không? Nhưng chắc chắn chương trình của bạn sẽ sử dụng chức năng này trong mã khác và bạn chắc chắn muốn kiểm tra mã đó trong các điều kiện khác nhau, kể cả tại các thời điểm khác nhau. Do đó, đó là một ý tưởng tốt để có thể điều khiển thời gian mà chức năng của bạn trả về - không phải vì bạn không tin tưởng currentMillis()cuộc gọi một đường của mình , mà bởi vì bạn cần xác minh người gọi cuộc gọi đó trong các trường hợp được kiểm soát. Vì vậy, bạn thấy, có mã kiểm tra là hữu ích ngay cả khi tự nó, nó dường như không đáng chú ý nhiều.


Một ví dụ khác là nếu bạn muốn rút một phần mã của một dự án sang một nơi khác (vì bất kỳ lý do gì). Càng nhiều phần khác nhau của chức năng độc lập với nhau, càng dễ dàng trích xuất chính xác chức năng bạn cần và không có gì hơn.
valenterry 01/07/2015

10
Nobody cares about the tests themselves-- Tôi làm. Tôi thấy các bài kiểm tra là một tài liệu tốt hơn về những gì mã làm hơn bất kỳ tập tin bình luận hoặc tập tin readme nào.
jcollum

Tôi đã chậm đọc về các thực hành kiểm tra một thời gian rồi (vì bằng cách nào đó chưa có ai kiểm thử đơn vị) và tôi phải nói, phần cuối cùng về việc xác minh cuộc gọi trong các trường hợp được kiểm soát và mã linh hoạt hơn đi kèm nó, làm cho tất cả các loại bấm vào vị trí. Cảm ơn bạn.
plast1k

12

Tại một số điểm, giá trị cần phải được khởi tạo, và tại sao không gần nhất với mức tiêu thụ?

Bởi vì bạn có thể cần phải sử dụng lại mã đó, với một giá trị khác với mã được tạo bên trong. Khả năng chèn giá trị bạn sẽ sử dụng làm tham số, đảm bảo rằng bạn có thể tạo các giá trị đó dựa trên bất kỳ lúc nào bạn muốn, không chỉ "ngay bây giờ" (với nghĩa là "bây giờ" khi bạn gọi mã).

Làm cho mã có thể kiểm tra có hiệu lực có nghĩa là làm cho mã có thể (từ đầu) được sử dụng trong hai kịch bản khác nhau (sản xuất và thử nghiệm).

Về cơ bản, trong khi bạn có thể lập luận rằng không có động cơ nào để làm cho mã có thể kiểm tra được trong trường hợp không có kiểm tra, thì có một lợi thế lớn trong việc viết mã có thể sử dụng lại và hai từ này là từ đồng nghĩa.

Thêm vào đó, mục đích của phương pháp trong tâm trí tôi là trả về một số giá trị dựa trên thời gian hiện tại, bằng cách biến nó thành một tham số mà bạn ngụ ý rằng mục đích này có thể / nên được thay đổi.

Bạn cũng có thể lập luận rằng mục đích của phương pháp này là trả về một số giá trị dựa trên giá trị thời gian và bạn cần nó để tạo ra giá trị đó dựa trên "ngay bây giờ". Một trong số chúng linh hoạt hơn, và nếu bạn quen với việc chọn biến thể đó, theo thời gian, tốc độ tái sử dụng mã của bạn sẽ tăng lên.


10

Có vẻ ngớ ngẩn khi nói theo cách này, nhưng nếu bạn muốn có thể kiểm tra mã của mình, thì có, viết mã kiểm tra là tốt hơn. Bạn hỏi:

Tại một số điểm, giá trị cần phải được khởi tạo, và tại sao không gần nhất với mức tiêu thụ?

Chính xác bởi vì, trong ví dụ mà bạn đang đề cập đến, nó làm cho mã đó không thể kiểm chứng được. Trừ khi bạn chỉ chạy một tập hợp con các bài kiểm tra của bạn tại các thời điểm khác nhau trong ngày. Hoặc bạn đặt lại đồng hồ hệ thống. Hoặc một số cách giải quyết khác. Tất cả đều tệ hơn đơn giản là làm cho mã của bạn linh hoạt.

Ngoài việc không linh hoạt, phương pháp nhỏ trong câu hỏi đó còn có hai trách nhiệm: (1) lấy thời gian hệ thống và sau đó (2) trả lại một số giá trị dựa trên nó.

public static string GetTimeOfDay()
{
    DateTime time = DateTime.Now;
    if (time.Hour >= 0 && time.Hour < 6)
    {
        return "Night";
    }
    if (time.Hour >= 6 && time.Hour < 12)
    {
        return "Morning";
    }
    if (time.Hour >= 12 && time.Hour < 18)
    {
        return "Afternoon";
    }
    return "Evening";
}

Việc chia nhỏ các trách nhiệm hơn nữa để phần ngoài tầm kiểm soát của bạn ( DateTime.Now) có tác động ít nhất đến phần còn lại của mã. Làm như vậy sẽ làm cho mã ở trên đơn giản hơn, với tác dụng phụ là có thể kiểm tra một cách có hệ thống.


1
Vì vậy, bạn phải kiểm tra vào sáng sớm để kiểm tra xem bạn có nhận được kết quả của "Đêm" khi bạn muốn không. Điều đó thật khó khăn. Bây giờ giả sử bạn muốn kiểm tra xem xử lý ngày có chính xác vào ngày 29 tháng 2 năm 2016 không ... Và một số lập trình viên iOS (và có thể là những người khác) đang lo lắng về lỗi của người mới bắt đầu làm rối tung mọi thứ ngay trước hoặc sau đầu năm, bạn thế nào kiểm tra cho điều đó. Và từ kinh nghiệm, tôi sẽ kiểm tra xử lý ngày vào ngày 2 tháng 2 năm 2020.
gnasher729

1
@ gnasher729 Chính xác là quan điểm của tôi. "Làm cho mã này có thể kiểm tra được" là một thay đổi đơn giản có thể giải quyết rất nhiều vấn đề (thử nghiệm). Nếu bạn không muốn tự động hóa thử nghiệm, thì tôi đoán mã này có thể qua được. Nhưng nó sẽ tốt hơn một khi nó "có thể kiểm chứng".
Eric King

9

Nó chắc chắn có một chi phí, nhưng một số nhà phát triển đã quá quen với việc trả nó đến mức họ đã quên mất chi phí ở đó. Ví dụ của bạn, bây giờ bạn có hai đơn vị thay vì một đơn vị, bạn đã yêu cầu mã cuộc gọi để khởi tạo và quản lý một phụ thuộc bổ sung, và trong khi GetTimeOfDaycó thể kiểm tra được nhiều hơn, bạn quay lại cùng một chiếc thuyền thử nghiệm mới của bạn IDateTimeProvider. Chỉ là nếu bạn có các bài kiểm tra tốt, lợi ích thường cao hơn chi phí.

Ngoài ra, ở một mức độ nhất định, việc viết loại mã có thể kiểm tra sẽ khuyến khích bạn thiết kế mã của mình theo cách dễ bảo trì hơn. Mã quản lý phụ thuộc mới gây khó chịu, vì vậy bạn sẽ muốn nhóm tất cả các chức năng phụ thuộc thời gian của mình lại với nhau, nếu có thể. Điều đó có thể giúp giảm thiểu và sửa các lỗi như, ví dụ như khi bạn tải một trang ngay trên ranh giới thời gian, có một số yếu tố được hiển thị bằng cách sử dụng thời gian trước và một số sử dụng thời gian sau. Nó cũng có thể tăng tốc chương trình của bạn bằng cách tránh các cuộc gọi hệ thống lặp đi lặp lại để có được thời gian hiện tại.

Tất nhiên, những cải tiến kiến ​​trúc đó phụ thuộc rất nhiều vào việc ai đó nhận thấy các cơ hội và thực hiện chúng. Một trong những mối nguy hiểm lớn nhất của việc tập trung chặt chẽ vào các đơn vị là đánh mất tầm nhìn của bức tranh lớn hơn.

Nhiều khung kiểm tra đơn vị cho phép bạn vá một đối tượng giả trong thời gian chạy, điều này cho phép bạn có được lợi ích của khả năng kiểm tra mà không gặp phải sự lộn xộn. Tôi thậm chí đã thấy nó được thực hiện trong C ++. Nhìn vào khả năng đó trong các tình huống có vẻ như chi phí kiểm tra không đáng.


+1 - bạn cần cải thiện thiết kế và kiến ​​trúc để làm cho bài kiểm tra đơn vị viết dễ dàng hơn.
Bовић

3
+ - đó là kiến ​​trúc mã của bạn có vấn đề. Kiểm tra dễ dàng hơn chỉ là một tác dụng phụ hạnh phúc.
gbjbaanb

8

Có thể là không phải mọi đặc tính đóng góp cho khả năng kiểm tra đều được mong muốn bên ngoài bối cảnh của khả năng kiểm tra - tôi gặp khó khăn khi đưa ra một biện minh không liên quan đến kiểm tra cho tham số thời gian mà bạn trích dẫn - nhưng nói rộng ra là các đặc điểm góp phần kiểm tra cũng đóng góp cho mã tốt bất kể khả năng kiểm tra.

Nói rộng ra, mã có thể kiểm tra là mã dễ uốn. Nó ở dạng khối nhỏ, rời rạc, gắn kết, do đó các bit riêng lẻ có thể được gọi để sử dụng lại. Nó được tổ chức tốt và được đặt tên tốt (để có thể kiểm tra một số chức năng mà bạn chú ý hơn đến việc đặt tên; nếu bạn không viết kiểm tra tên cho một chức năng sử dụng một lần sẽ ít quan trọng hơn). Nó có xu hướng tham số hơn (như ví dụ thời gian của bạn), vì vậy mở để sử dụng từ các bối cảnh khác ngoài mục đích ban đầu. Đó là DRY, vì vậy ít lộn xộn và dễ hiểu hơn.

Đúng. Đó là cách thực hành tốt để viết mã có thể kiểm tra, thậm chí không phân biệt kiểm tra.


không đồng ý về việc nó là DRY - gói GetCienTime trong một phương thức MyGetCienTime lặp lại rất nhiều cuộc gọi HĐH mà không có lợi ích gì ngoài việc hỗ trợ công cụ kiểm tra. Đó chỉ là ví dụ đơn giản nhất, chúng trở nên tồi tệ hơn trong thực tế.
gbjbaanb

1
"lặp lại cuộc gọi hệ điều hành không có lợi ích" - cho đến khi bạn kết thúc việc chạy trên một máy chủ với một đồng hồ, nói chuyện với một máy chủ aws ở một múi giờ khác, và điều đó phá vỡ mã của bạn, và sau đó bạn phải duyệt tất cả mã của mình và cập nhật nó để sử dụng MyGetCienTime, thay vào đó trả về UTC. ; lệch đồng hồ, tiết kiệm ánh sáng ban ngày và có nhiều lý do khác khiến bạn không thể tin tưởng một cách mù quáng vào cuộc gọi hệ điều hành, hoặc ít nhất có một điểm duy nhất để bạn có thể bỏ qua một thay thế khác.
Andrew Hill

8

Viết mã kiểm tra là rất quan trọng nếu bạn muốn có thể chứng minh rằng mã của bạn thực sự hoạt động.

Tôi có xu hướng đồng ý với những cảm xúc tiêu cực về việc biến mã của bạn thành những mâu thuẫn khủng khiếp chỉ để phù hợp với nó cho một khung kiểm tra cụ thể.

Mặt khác, mọi người ở đây, vào lúc này hay lúc khác, đã phải đối phó với chức năng ma thuật dài 1.000 dòng mà chỉ cần phải đối phó, hầu như không thể chạm vào mà không phá vỡ một hoặc nhiều điều tối nghĩa, không- phụ thuộc rõ ràng ở một nơi khác (hoặc một nơi nào đó trong chính nó, nơi mà sự phụ thuộc gần như không thể hình dung được) và theo định nghĩa là không thể kiểm chứng được. Theo quan điểm của tôi (không phải là không có công) rằng các khung kiểm tra đã trở nên quá mức không nên được coi là một giấy phép miễn phí để viết mã kém chất lượng, không thể kiểm chứng, theo ý kiến ​​của tôi.

Chẳng hạn, lý tưởng phát triển dựa trên thử nghiệm có xu hướng thúc đẩy bạn viết ra các quy trình trách nhiệm đơn lẻ, và đó chắc chắn là một điều tốt. Cá nhân, tôi nói mua vào một trách nhiệm duy nhất, một nguồn sự thật, phạm vi được kiểm soát (không có biến toàn cầu của freakin) và giữ mức độ phụ thuộc dễ vỡ đến mức tối thiểu và mã của bạn sẽ có thể kiểm tra được. Kiểm tra bằng một số khung thử nghiệm cụ thể? Ai biết. Nhưng sau đó, có lẽ đó là khung thử nghiệm cần tự điều chỉnh theo mã tốt chứ không phải theo cách khác.

Nhưng chỉ cần rõ ràng, mã quá thông minh, hoặc quá dài và / hoặc phụ thuộc lẫn nhau mà nó không dễ hiểu bởi một người khác không phải là mã tốt. Và ngẫu nhiên, nó cũng không phải là mã có thể dễ dàng được kiểm tra.

Vì vậy, gần tóm tắt của tôi, mã có thể kiểm tratốt hơn ?

Tôi không biết, có thể không. Người dân ở đây có một số điểm hợp lệ.

Nhưng tôi tin rằng mã tốt hơn có xu hướng cũng là mã có thể kiểm tra được .

nếu bạn đang nói về phần mềm nghiêm túc để sử dụng cho những nỗ lực nghiêm túc, thì việc vận chuyển mã chưa được kiểm tra không phải là điều có trách nhiệm nhất bạn có thể làm với tiền của chủ nhân hoặc tiền của khách hàng.

Cũng đúng là một số mã yêu cầu kiểm tra nghiêm ngặt hơn các mã khác và thật ngớ ngẩn khi giả vờ khác. Bạn muốn trở thành phi hành gia trên tàu con thoi như thế nào nếu hệ thống thực đơn giao tiếp với bạn với các hệ thống quan trọng trên tàu con thoi không được thử nghiệm? Hoặc một nhân viên tại một nhà máy hạt nhân nơi các hệ thống phần mềm theo dõi nhiệt độ trong lò phản ứng chưa được thử nghiệm? Mặt khác, một chút mã tạo ra một báo cáo chỉ đọc đơn giản có yêu cầu một thùng chứa đầy đủ tài liệu và một nghìn bài kiểm tra đơn vị không? Tôi chắc chắn hy vọng không. Chỉ cần nói ...


1
"mã tốt hơn có xu hướng cũng là mã có thể kiểm tra" Đây là chìa khóa. Làm cho nó có thể kiểm tra không làm cho nó tốt hơn. Làm cho nó tốt hơn thường làm cho nó có thể kiểm tra được và các bài kiểm tra thường cung cấp cho bạn thông tin bạn có thể sử dụng để làm cho nó tốt hơn, nhưng sự hiện diện của các bài kiểm tra không bao hàm chất lượng và có những trường hợp ngoại lệ (hiếm).
anaximander

1
Chính xác. Hãy xem xét các contrapositive. Nếu đó là mã không thể kiểm chứng, nó không được kiểm tra. Nếu nó không được thử nghiệm, làm thế nào để bạn biết liệu nó có hoạt động hay không ngoài một tình huống trực tiếp?
pjc50

1
Tất cả các thử nghiệm chứng minh rằng mã vượt qua các thử nghiệm. Nếu không, mã được kiểm tra đơn vị sẽ không có lỗi và chúng tôi biết đó không phải là trường hợp.
wobbily_col

@anaximander Chính xác. Ít nhất có khả năng rằng sự hiện diện đơn thuần của các thử nghiệm là một chống chỉ định dẫn đến mã chất lượng kém hơn nếu tất cả trọng tâm chỉ là kiểm tra các hộp kiểm. "Ít nhất bảy bài kiểm tra đơn vị cho mỗi chức năng?" "Kiểm tra." Nhưng tôi thực sự tin rằng nếu mã là mã chất lượng, nó sẽ dễ kiểm tra hơn.
Craig

1
... nhưng làm cho một bộ máy quan liêu ra khỏi thử nghiệm có thể là một sự lãng phí hoàn toàn và không tạo ra thông tin hữu ích hoặc kết quả đáng tin cậy. Bất kể; Tôi chắc chắn muốn ai đó đã kiểm tra lỗi SSL Heartbleed , phải không? hoặc Apple goto bị lỗi?
Craig

5

Đối với tôi, mặc dù, có vẻ như quá mức cần thiết để vượt qua trong thời gian như là một cuộc tranh luận.

Bạn đã đúng, và với chế độ chế giễu, bạn có thể làm cho mã có thể kiểm tra được tránh thời gian trôi qua (ý định chơi chữ không xác định). Mã ví dụ:

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

Bây giờ hãy nói rằng bạn muốn kiểm tra những gì xảy ra trong một bước nhảy vọt. Như bạn nói, để kiểm tra điều này theo cách quá mức bạn sẽ phải thay đổi mã (sản xuất):

def time_of_day(now=None):
    now = now if now is not None else datetime.datetime.utcnow()
    return now.strftime('%H:%M:%S')

Nếu Python hỗ trợ bước nhảy vọt , mã kiểm tra sẽ như thế này:

def test_handle_leap_second(self):
    actual = time_of_day(
        now=datetime.datetime(year=2015, month=6, day=30, hour=23, minute=59, second=60)
    expected = '23:59:60'
    self.assertEquals(actual, expected)

Bạn có thể kiểm tra điều này, nhưng mã phức tạp hơn mức cần thiết và các thử nghiệm vẫn không thể thực hiện đáng tin cậy nhánh mã mà hầu hết mã sản xuất sẽ sử dụng (nghĩa là không truyền giá trị cho now). Bạn làm việc xung quanh điều này bằng cách sử dụng một giả . Bắt đầu từ mã sản xuất ban đầu:

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

Mã kiểm tra:

@unittest.patch('datetime.datetime.utcnow')
def test_handle_leap_second(self, utcnow_mock):
    utcnow_mock.return_value = datetime.datetime(
        year=2015, month=6, day=30, hour=23, minute=59, second=60)
    actual = time_of_day()
    expected = '23:59:60'
    self.assertEquals(actual, expected)

Điều này mang lại một số lợi ích:

  • Bạn đang thử nghiệm time_of_day độc lập với sự phụ thuộc của nó.
  • Bạn đang kiểm tra đường dẫn mã giống như mã sản xuất.
  • Mã sản xuất càng đơn giản càng tốt.

Bên cạnh đó, hy vọng rằng các khung mô phỏng trong tương lai sẽ làm cho mọi thứ như thế này dễ dàng hơn. Ví dụ, vì bạn phải tham khảo hàm giả định là một chuỗi, bạn không thể dễ dàng làm cho IDE thay đổi tự động khi time_of_daybắt đầu sử dụng nguồn khác theo thời gian.


FYI: đối số mặc định của bạn là sai. Nó sẽ chỉ được xác định một lần, vì vậy chức năng của bạn sẽ luôn trả về thời gian được đánh giá lần đầu tiên.
ahruss 04/07/2015

4

Một chất lượng của mã được viết tốt là nó mạnh mẽ để thay đổi . Đó là, khi một yêu cầu thay đổi xuất hiện, sự thay đổi trong mã phải tỷ lệ thuận. Đây là một lý tưởng (và không phải lúc nào cũng đạt được), nhưng viết mã có thể kiểm tra giúp chúng ta tiến gần hơn đến mục tiêu này.

Tại sao nó giúp chúng ta gần gũi hơn? Trong sản xuất, mã của chúng tôi hoạt động trong môi trường sản xuất, bao gồm tích hợp và tương tác với tất cả các mã khác của chúng tôi. Trong các bài kiểm tra đơn vị, chúng tôi quét sạch phần lớn môi trường này. Mã của chúng tôi hiện đang được thay đổi mạnh mẽ vì các thử nghiệm là một thay đổi . Chúng tôi đang sử dụng các đơn vị theo những cách khác nhau, với các đầu vào khác nhau (giả, đầu vào xấu có thể không bao giờ thực sự được đưa vào, v.v.) so với chúng tôi sẽ sử dụng chúng trong sản xuất.

Điều này chuẩn bị mã của chúng tôi cho ngày khi thay đổi xảy ra trong hệ thống của chúng tôi. Giả sử tính toán thời gian của chúng ta cần có thời gian khác nhau dựa trên múi giờ. Bây giờ chúng tôi có khả năng vượt qua trong thời gian đó và không phải thực hiện bất kỳ thay đổi nào đối với mã. Khi chúng ta không muốn vượt qua thời gian và muốn sử dụng thời gian hiện tại, chúng ta chỉ có thể sử dụng một đối số mặc định. Mã của chúng tôi là mạnh mẽ để thay đổi bởi vì nó có thể kiểm tra được.


4

Từ kinh nghiệm của tôi, một trong những quyết định quan trọng nhất và sâu rộng nhất mà bạn đưa ra khi xây dựng chương trình là cách bạn chia mã thành các đơn vị (trong đó "đơn vị" được sử dụng theo nghĩa rộng nhất). Nếu bạn đang sử dụng ngôn ngữ OO dựa trên lớp, bạn cần chia tất cả các cơ chế bên trong được sử dụng để triển khai chương trình thành một số lớp. Sau đó, bạn cần chia mã của mỗi lớp thành một số phương thức. Trong một số ngôn ngữ, lựa chọn là làm thế nào để chia mã của bạn thành các hàm. Hoặc nếu bạn thực hiện điều SOA, bạn cần quyết định sẽ xây dựng bao nhiêu dịch vụ và những gì sẽ đi vào mỗi dịch vụ.

Sự cố mà bạn chọn có ảnh hưởng rất lớn đến toàn bộ quá trình. Các lựa chọn tốt làm cho mã dễ viết hơn và dẫn đến ít lỗi hơn (ngay cả trước khi bạn bắt đầu thử nghiệm và gỡ lỗi). Họ làm cho nó dễ dàng hơn để thay đổi và duy trì. Thật thú vị, hóa ra rằng một khi bạn tìm thấy một sự cố tốt, nó thường cũng dễ dàng để kiểm tra hơn một người nghèo.

Tại sao cái này rất? Tôi không nghĩ rằng tôi có thể hiểu và giải thích tất cả các lý do. Nhưng một lý do là một sự cố tốt luôn luôn có nghĩa là chọn một "cỡ hạt" vừa phải cho các đơn vị thực hiện. Bạn không muốn nhồi nhét quá nhiều chức năng và quá nhiều logic vào một lớp / phương thức / chức năng / mô-đun / v.v. Điều này làm cho mã của bạn dễ đọc hơn và dễ viết hơn, nhưng nó cũng giúp kiểm tra dễ dàng hơn.

Nó không chỉ là vậy, mặc dù. Một thiết kế nội bộ tốt có nghĩa là hành vi dự kiến ​​(đầu vào / đầu ra / vv) của mỗi đơn vị thực hiện có thể được xác định rõ ràng và chính xác. Điều này rất quan trọng để thử nghiệm. Một thiết kế tốt thường có nghĩa là mỗi đơn vị thực hiện sẽ có một số lượng phụ thuộc vừa phải vào các đơn vị khác. Điều đó làm cho mã của bạn dễ đọc và dễ hiểu hơn, nhưng cũng giúp kiểm tra dễ dàng hơn. Những lý do tiếp tục; có lẽ những người khác có thể nói rõ hơn những lý do mà tôi không thể.

Đối với ví dụ trong câu hỏi của bạn, tôi không nghĩ rằng "thiết kế mã tốt" tương đương với việc nói rằng tất cả các phụ thuộc bên ngoài (chẳng hạn như phụ thuộc vào đồng hồ hệ thống) phải luôn luôn được "tiêm". Đó có thể là một ý tưởng tốt, nhưng nó là một vấn đề riêng biệt với những gì tôi đang mô tả ở đây và tôi sẽ không đi sâu vào những ưu và nhược điểm của nó.

Ngẫu nhiên, ngay cả khi bạn thực hiện cuộc gọi trực tiếp đến các chức năng hệ thống trả về thời gian hiện tại, hành động trên hệ thống tệp, v.v., điều này không có nghĩa là bạn không thể kiểm tra mã của mình một cách đơn lẻ. Mẹo nhỏ là sử dụng một phiên bản đặc biệt của các thư viện tiêu chuẩn cho phép bạn giả mạo các giá trị trả về của các chức năng hệ thống. Tôi chưa bao giờ thấy người khác đề cập đến kỹ thuật này, nhưng nó khá đơn giản để thực hiện với nhiều ngôn ngữ và nền tảng phát triển. (Hy vọng thời gian chạy ngôn ngữ của bạn là nguồn mở và dễ xây dựng. Nếu thực thi mã của bạn bao gồm một bước liên kết, hy vọng nó cũng dễ dàng kiểm soát thư viện nào được liên kết với nó.)

Tóm lại, mã có thể kiểm tra không nhất thiết là mã "tốt", nhưng mã "tốt" thường có thể kiểm tra được.


1

Nếu bạn đang đi với các nguyên tắc RẮN, bạn sẽ có mặt tốt, đặc biệt là nếu mở rộng điều này với KISS , DRYYAGNI .

Một điểm còn thiếu đối với tôi là điểm phức tạp của một phương thức. Đây có phải là một phương thức getter / setter đơn giản? Sau đó, chỉ cần viết các bài kiểm tra để đáp ứng khung thử nghiệm của bạn sẽ là một sự lãng phí thời gian.

Nếu đó là một phương pháp phức tạp hơn khi bạn thao tác dữ liệu và muốn chắc chắn rằng nó sẽ hoạt động ngay cả khi bạn phải thay đổi logic bên trong, thì đó sẽ là một lời kêu gọi tuyệt vời cho phương pháp thử nghiệm. Nhiều lần tôi đã phải thay đổi một đoạn mã sau vài ngày / tuần / tháng và tôi thực sự rất vui khi có trường hợp thử nghiệm. Khi lần đầu tiên phát triển phương pháp tôi đã thử nghiệm nó với phương pháp thử nghiệm, và tôi chắc chắn rằng nó sẽ hoạt động. Sau khi thay đổi, mã kiểm tra chính của tôi vẫn hoạt động. Vì vậy, tôi chắc chắn rằng sự thay đổi của tôi đã không phá vỡ một số mã cũ trong sản xuất.

Một khía cạnh khác của bài kiểm tra viết là chỉ cho các nhà phát triển khác cách sử dụng phương pháp của bạn. Nhiều lần nhà phát triển sẽ tìm kiếm một ví dụ về cách sử dụng một phương thức và giá trị trả về sẽ là gì.

Chỉ hai xu của tôi .

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.