Làm thế nào để viết các bài kiểm tra đơn vị tốt


61

Bị kích thích bởi chủ đề này , tôi (một lần nữa) đang suy nghĩ về việc cuối cùng sử dụng các bài kiểm tra đơn vị trong các dự án của tôi. Một vài áp phích ở đó có nội dung như "Các bài kiểm tra rất tuyệt, nếu chúng là các bài kiểm tra tốt". Câu hỏi của tôi bây giờ: các bài kiểm tra "tốt" là gì?

Trong các ứng dụng của tôi, phần chính thường là một số loại phân tích số, tùy thuộc vào lượng lớn dữ liệu được quan sát và dẫn đến một hàm phù hợp có thể được sử dụng để mô hình hóa dữ liệu này. Tôi thấy rất khó để xây dựng các thử nghiệm cho các phương thức này, vì số lượng đầu vào và kết quả có thể quá lớn để chỉ kiểm tra mọi trường hợp và bản thân các phương thức này thường khá dài và không thể dễ dàng được cấu trúc lại mà không làm giảm hiệu suất. Tôi đặc biệt quan tâm đến các bài kiểm tra "tốt" cho loại phương pháp này.


8
Bất kỳ bài kiểm tra đơn vị tốt nào cũng chỉ nên kiểm tra một điều - nếu thất bại, bạn nên biết chính xác những gì đã sai.
gablin

2
Khi có lượng dữ liệu lớn, điều tốt là viết các bài kiểm tra chung có thể lấy các tệp dữ liệu làm đầu vào. Các tệp dữ liệu thường chứa cả đầu vào và kết quả mong đợi. Với các khung kiểm tra xunit, bạn có thể tạo các trường hợp thử nghiệm một cách nhanh chóng - một cho mỗi mẫu dữ liệu.
froderik

2
@gablin "Nếu thất bại, bạn nên biết chính xác những gì đã sai" sẽ gợi ý rằng các xét nghiệm có nhiều nguyên nhân thất bại có thể xảy ra, miễn là bạn có thể xác định nguyên nhân từ đầu ra của thử nghiệm ...?
dùng253751

Không ai có vẻ đã đề cập rằng các bài kiểm tra đơn vị có thể kiểm tra thời gian hoạt động. Bạn có thể cấu trúc lại mã của mình với hiệu suất trong tâm trí, đảm bảo rằng bài kiểm tra đơn vị cho bạn biết liệu nó vượt qua hay thất bại dựa trên thời gian cũng như kết quả.
CJ Dennis

Câu trả lời:


52

Nghệ thuật kiểm tra đơn vị có những điều sau đây để nói về kiểm tra đơn vị:

Một bài kiểm tra đơn vị nên có các thuộc tính sau:

  • Nó nên được tự động và lặp lại.
  • Nó phải dễ thực hiện.
  • Một khi nó được viết, nó sẽ vẫn còn để sử dụng trong tương lai.
  • Bất cứ ai cũng có thể chạy nó.
  • Nó sẽ chạy khi nhấn nút.
  • Nó sẽ chạy nhanh.

và sau đó thêm nó nên hoàn toàn tự động, đáng tin cậy, dễ đọc và có thể bảo trì.

Tôi thực sự khuyên bạn nên đọc cuốn sách này nếu bạn chưa có.

Theo tôi, tất cả những thứ này đều rất quan trọng, nhưng ba đặc điểm cuối cùng (đáng tin cậy, dễ đọc và có thể duy trì) đặc biệt, như thể các bài kiểm tra của bạn có ba thuộc tính này thì mã của bạn cũng thường có chúng.


1
+1 cho danh sách toàn diện nhắm vào thử nghiệm đơn vị (không phải tích hợp hoặc thử nghiệm chức năng)
Gary Rowe

1
+1 cho liên kết. Tài liệu thú vị được tìm thấy ở đó.
Joris Meys

1
"Chạy nhanh" có ý nghĩa lớn. Đó là một lý do tại sao các bài kiểm tra đơn vị nên chạy một cách cô lập, tránh xa các tài nguyên bên ngoài như cơ sở dữ liệu, hệ thống tệp, dịch vụ web, v.v ... Điều này, đến lượt nó, dẫn đến giả / cuống.
Michael Easter

1
khi nó nói It should run at the push of a button, điều đó có nghĩa là một bài kiểm tra đơn vị không nên yêu cầu các container (máy chủ ứng dụng) đang chạy (đối với đơn vị đang được kiểm tra) hoặc kết nối tài nguyên (như DB, các dịch vụ web bên ngoài, v.v.)? Tôi chỉ bối rối không biết phần nào của ứng dụng nên được kiểm tra đơn vị và phần nào không nên. Tôi đã được thông báo rằng các bài kiểm tra đơn vị không nên phụ thuộc vào kết nối DB và các container đang chạy và có thể sử dụng các mockup thay thế.
lưỡng cư

42

Một bài kiểm tra đơn vị tốt không phản ánh chức năng mà nó đang kiểm tra.

Như một ví dụ đơn giản hóa rất nhiều, hãy xem xét bạn có một hàm trả về trung bình hai int. Kiểm tra toàn diện nhất sẽ gọi hàm và kiểm tra xem kết quả trên thực tế có phải là trung bình không. Điều này hoàn toàn không có ý nghĩa gì: bạn đang phản chiếu (sao chép) chức năng bạn đang kiểm tra. Nếu bạn mắc lỗi trong hàm chính, bạn sẽ mắc lỗi tương tự trong bài kiểm tra.

Nói cách khác, nếu bạn thấy mình sao chép chức năng chính trong bài kiểm tra đơn vị, đó có thể là dấu hiệu cho thấy bạn đang lãng phí thời gian.


21
+1 Những gì bạn sẽ làm trong trường hợp này là kiểm tra với các đối số được mã hóa cứng và kiểm tra câu trả lời đã biết của bạn.
Michael K

Tôi đã nhìn thấy mùi đó trước đây.
Paul Butcher

Bạn có thể đưa ra một ví dụ về một bài kiểm tra đơn vị tốt cho hàm trả về trung bình không?
VLAS

2
@VLAS kiểm tra các giá trị được xác định trước, ví dụ: đảm bảo avg (1, 3) == 2, cũng quan trọng hơn là kiểm tra các trường hợp cạnh, chẳng hạn như INT_MAX, số 0, giá trị âm, v.v ... Nếu tìm thấy lỗi và sửa lỗi trong hàm, hãy thêm một lỗi khác kiểm tra để đảm bảo lỗi này không bao giờ được giới thiệu lại.
mojuba

Hấp dẫn. Làm thế nào để bạn đề xuất để có được câu trả lời chính xác cho các đầu vào kiểm tra đó và không có khả năng mắc lỗi tương tự như mã chịu thử nghiệm?
Timo

10

Kiểm tra đơn vị tốt về cơ bản là đặc điểm kỹ thuật ở dạng runnable:

  1. mô tả hành vi của mã tương ứng với các trường hợp sử dụng
  2. bao gồm các trường hợp góc kỹ thuật (điều gì xảy ra nếu null được thông qua) - nếu không có kiểm tra cho trường hợp góc, hành vi không được xác định.
  3. phá vỡ nếu mã được thử nghiệm thay đổi từ đặc điểm kỹ thuật

Tôi đã thấy Test-Driven-Development rất phù hợp với các thói quen của thư viện vì về cơ bản bạn viết API trước và THÌ việc triển khai thực tế.


7

đối với TDD, các tính năng kiểm tra "tốt" mà khách hàng muốn ; các tính năng không nhất thiết phải tương ứng với các chức năng và các kịch bản thử nghiệm không được nhà phát triển tạo ra trong chân không

trong trường hợp của bạn - tôi đoán - 'tính năng' là chức năng phù hợp mô hình hóa dữ liệu đầu vào trong một khả năng chịu lỗi nhất định. Vì tôi không biết bạn đang làm gì, tôi đang làm gì đó; hy vọng nó là analgous.

Ví dụ câu chuyện:

Là một [Phi công cánh X] tôi muốn [không quá 0,0001% lỗi phù hợp] để [máy tính nhắm mục tiêu có thể chạm vào cổng xả của Death Star khi di chuyển hết tốc độ qua hẻm núi hộp]

Vì vậy, bạn đi nói chuyện với các phi công (và với máy tính nhắm mục tiêu, nếu có tình cảm). Đầu tiên bạn nói về những gì là 'bình thường', sau đó nói về những điều bất thường. Bạn tìm ra những gì thực sự quan trọng trong kịch bản này, những gì phổ biến, những gì không thể và những gì chỉ có thể.

Giả sử thông thường bạn sẽ có một cửa sổ nửa giây trên bảy kênh dữ liệu đo từ xa: tốc độ, cao độ, cuộn, ngáp, vectơ đích, kích thước mục tiêu và vận tốc mục tiêu và các giá trị này sẽ không đổi hoặc thay đổi tuyến tính. Bất thường, bạn có thể có ít kênh hơn và / hoặc các giá trị có thể thay đổi nhanh chóng. Vì vậy, cùng nhau bạn đưa ra một số thử nghiệm như:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Bây giờ, bạn có thể nhận thấy rằng không có kịch bản nào cho tình huống cụ thể được mô tả trong câu chuyện. Hóa ra, sau khi nói chuyện với khách hàng và các bên liên quan khác, mục tiêu đó trong câu chuyện ban đầu chỉ là một ví dụ giả định. Các thử nghiệm thực sự ra khỏi các cuộc thảo luận sau đó. Điều này có thể xảy ra. Câu chuyện nên được viết lại, nhưng nó không phải là [vì câu chuyện chỉ là một phần giữ chỗ cho một cuộc trò chuyện với khách hàng].


5

Tạo các thử nghiệm cho các trường hợp góc, như một bộ thử nghiệm chỉ chứa số lượng đầu vào tối thiểu (có thể là 1 hoặc 0) và một vài trường hợp tiêu chuẩn. Những bài kiểm tra đơn vị này không phải là sự thay thế cho các bài kiểm tra chấp nhận kỹ lưỡng, cũng không nên như vậy.


5

Tôi đã thấy rất nhiều trường hợp mọi người đầu tư rất nhiều nỗ lực để viết các bài kiểm tra cho mã hiếm khi được nhập và không viết các bài kiểm tra cho mã được nhập thường xuyên.

Trước khi ngồi viết bất kỳ bài kiểm tra nào, bạn nên xem xét một loại biểu đồ cuộc gọi nào đó, để đảm bảo bạn lên kế hoạch bảo hiểm đầy đủ.

Ngoài ra, tôi không tin vào việc viết bài kiểm tra chỉ vì mục đích nói "Vâng, chúng tôi kiểm tra điều đó". Nếu tôi đang sử dụng một thư viện bị bỏ trong đó và sẽ không thay đổi, tôi sẽ không lãng phí một ngày để viết bài kiểm tra để đảm bảo các bộ phận của API sẽ không bao giờ thay đổi hoạt động như mong đợi, ngay cả khi một số phần nhất định của nó cao trên biểu đồ cuộc gọi. Các thử nghiệm tiêu thụ thư viện nói (mã của riêng tôi) chỉ ra điều này.


Nhưng điều gì vào một ngày sau đó khi thư viện có phiên bản mới hơn với sửa lỗi?

@ Thorbjørn Ravn Andersen - Nó phụ thuộc vào thư viện, những gì đã thay đổi và quá trình thử nghiệm của riêng họ. Tôi sẽ không viết các bài kiểm tra mã mà tôi biết hoạt động khi tôi thả nó vào vị trí và không bao giờ chạm vào. Vì vậy, nếu nó hoạt động sau khi cập nhật, hãy nhớ rằng :) Tất nhiên cũng có ngoại lệ.
Tim Post

nếu bạn phụ thuộc vào thư viện của mình, điều tối thiểu bạn có thể làm là viết các bài kiểm tra cho thấy những gì bạn mong đợi thư viện thực sự làm ,

... và nếu điều đó thay đổi, hãy kiểm tra những thứ tiêu thụ thư viện nói ... tl; dr; Tôi không cần kiểm tra phần bên trong mã của bên thứ ba. Trả lời cập nhật cho rõ ràng, mặc dù.
Tim Post

4

Không hoàn toàn như vậy TDD, nhưng sau khi bạn đã vào QA, bạn có thể cải thiện các bài kiểm tra của mình bằng cách thiết lập các trường hợp kiểm tra để tái tạo bất kỳ lỗi nào phát sinh trong quá trình QA. Điều này có thể đặc biệt có giá trị khi bạn tham gia hỗ trợ dài hạn và bạn bắt đầu đến một nơi mà bạn có nguy cơ vô tình giới thiệu lại các lỗi cũ. Có một bài kiểm tra tại chỗ để nắm bắt đó là đặc biệt có giá trị.


3

Tôi cố gắng để có mọi bài kiểm tra chỉ kiểm tra một điều. Tôi cố gắng cung cấp cho mỗi bài kiểm tra một tên như ShouldDoSthing (). Tôi cố gắng kiểm tra hành vi, không thực hiện. Tôi chỉ thử nghiệm phương pháp công cộng.

Tôi thường có một hoặc một vài bài kiểm tra để thành công, và sau đó có thể là một bài kiểm tra thất bại, theo phương pháp công khai.

Tôi sử dụng mock-up rất nhiều. Một khung mô phỏng tốt có lẽ sẽ khá hữu ích, chẳng hạn như PowerMock. Mặc dù tôi chưa sử dụng.

Nếu lớp A sử dụng lớp B khác, tôi sẽ thêm giao diện X, để A không sử dụng B trực tiếp. Sau đó, tôi sẽ tạo XMockup giả và sử dụng nó thay vì B trong các thử nghiệm của mình. Nó thực sự giúp tăng tốc độ thực hiện kiểm tra, giảm độ phức tạp kiểm tra và cũng giảm số lượng bài kiểm tra tôi viết cho A vì tôi không phải đối phó với các đặc thù của B. Tôi có thể kiểm tra ví dụ mà A gọi là X.someMethod () thay vì tác dụng phụ của việc gọi B.someMethod ().

Giữ cho bạn kiểm tra mã sạch sẽ là tốt.

Khi sử dụng API, chẳng hạn như lớp cơ sở dữ liệu, tôi sẽ mô phỏng nó và cho phép giả lập để đưa ra một ngoại lệ ở mọi cơ hội có thể có trong lệnh. Sau đó, tôi chạy thử nghiệm một lần mà không ném, và trong một vòng lặp, mỗi lần ném một ngoại lệ vào cơ hội tiếp theo cho đến khi thử nghiệm thành công một lần nữa. Một chút giống như các bài kiểm tra bộ nhớ có sẵn cho Symbian.


2

Tôi thấy rằng Andry Lowry đã đăng các số liệu kiểm tra đơn vị của Roy Osherove; nhưng dường như không ai đưa ra bộ (miễn phí) mà chú Bob đưa ra trong Bộ luật sạch (132-133). Anh ấy sử dụng từ viết tắt FIRST (ở đây với bản tóm tắt của tôi):

  • Nhanh (họ nên chạy nhanh, vì vậy mọi người sẽ không chạy chúng)
  • Độc lập (các bài kiểm tra không nên thực hiện thiết lập hoặc phân tích cho nhau)
  • Lặp lại (nên chạy trên tất cả các môi trường / nền tảng)
  • Tự xác thực (hoàn toàn tự động; đầu ra phải là "vượt qua" hoặc "không thành công", không phải là tệp nhật ký)
  • Kịp thời (khi viết chúng ngay trước khi viết mã sản xuất mà họ kiểm tra)
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.