Có một điểm để kiểm tra đơn vị mà sơ khai và chế nhạo mọi thứ công khai?


59

Khi thực hiện kiểm tra đơn vị theo cách "phù hợp", tức là loại bỏ mọi cuộc gọi công khai và trả về các giá trị hoặc giả định đặt trước, tôi cảm thấy như mình không thực sự kiểm tra bất cứ điều gì. Tôi thực sự đang xem mã của tôi và tạo các ví dụ dựa trên luồng logic thông qua các phương thức công khai của tôi. Và mỗi khi việc triển khai thay đổi, tôi phải đi và thay đổi các bài kiểm tra đó, một lần nữa, không thực sự cảm thấy rằng tôi đang hoàn thành bất cứ điều gì hữu ích (có thể là trung hạn hoặc dài hạn). Tôi cũng thực hiện các bài kiểm tra tích hợp (bao gồm cả những con đường không hạnh phúc) và tôi không thực sự bận tâm về thời gian thử nghiệm tăng lên. Với những người đó, tôi cảm thấy như tôi thực sự đang thử nghiệm hồi quy, bởi vì họ đã bắt được nhiều, trong khi tất cả các thử nghiệm đơn vị làm là cho tôi thấy rằng việc thực hiện phương thức công khai của tôi đã thay đổi, điều mà tôi đã biết.

Kiểm thử đơn vị là một chủ đề rộng lớn và tôi cảm thấy mình là người không hiểu điều gì ở đây. Lợi thế quyết định của thử nghiệm đơn vị so với thử nghiệm tích hợp (không bao gồm chi phí thời gian) là gì?


4
Hai xu của tôi: Đừng lạm dụng quá nhiều giả ( googletesting.blogspot.com/2013/05/)
Juan Mendes

Câu trả lời:


37

Khi thực hiện kiểm tra đơn vị theo cách "phù hợp", tức là loại bỏ mọi cuộc gọi công khai và trả về các giá trị hoặc giả định đặt trước, tôi cảm thấy như mình không thực sự kiểm tra bất cứ điều gì. Tôi thực sự đang xem mã của tôi và tạo các ví dụ dựa trên luồng logic thông qua các phương thức công khai của tôi.

Điều này nghe có vẻ như phương thức bạn đang kiểm tra cần một số trường hợp lớp khác (mà bạn phải giả định) và tự gọi một số phương thức.

Loại mã này thực sự khó kiểm tra đơn vị, vì những lý do bạn phác thảo.

Điều tôi thấy hữu ích là chia các lớp như vậy thành:

  1. Các lớp học với "logic kinh doanh" thực tế. Chúng sử dụng một vài hoặc không có cuộc gọi đến các lớp khác và dễ dàng kiểm tra (value (s) in - value out).
  2. Các lớp giao diện với các hệ thống bên ngoài (tệp, cơ sở dữ liệu, v.v.). Chúng bao bọc hệ thống bên ngoài và cung cấp một giao diện thuận tiện cho nhu cầu của bạn.
  3. Các lớp học "gắn kết mọi thứ lại với nhau"

Sau đó, các lớp từ 1. dễ dàng kiểm tra đơn vị, vì chúng chỉ chấp nhận các giá trị và trả về một kết quả. Trong các trường hợp phức tạp hơn, các lớp này có thể cần tự thực hiện các cuộc gọi, nhưng chúng sẽ chỉ gọi các lớp từ 2. (và không gọi trực tiếp, ví dụ như một hàm cơ sở dữ liệu) và các lớp từ 2. rất dễ giả định (vì chúng chỉ phơi bày các bộ phận của hệ thống bọc mà bạn cần).

Các lớp từ 2. và 3. thường không thể được kiểm tra đơn vị một cách có ý nghĩa (vì chúng không tự làm bất cứ điều gì hữu ích, chúng chỉ là mã "keo"). OTOH, các lớp này có xu hướng tương đối đơn giản (và ít), vì vậy chúng cần được bao phủ đầy đủ bằng các bài kiểm tra tích hợp.


Một ví dụ

Một lớp

Giả sử bạn có một lớp lấy giá từ cơ sở dữ liệu, áp dụng một số giảm giá và sau đó cập nhật cơ sở dữ liệu.

Nếu bạn có tất cả những thứ này trong một lớp, bạn sẽ cần gọi các hàm DB, rất khó để giả định. Trong mã giả:

1 select price from database
2 perform price calculation, possibly fetching parameters from database
3 update price in database

Tất cả ba bước sẽ cần truy cập DB, vì vậy rất nhiều (phức tạp) chế giễu, có khả năng bị phá vỡ nếu mã hoặc cấu trúc DB thay đổi.

Chia ra

Bạn chia thành ba lớp: PriceCalculation, PriceRep repository, App.

PriceCalculation chỉ thực hiện tính toán thực tế và được cung cấp các giá trị cần thiết. Ứng dụng liên kết mọi thứ với nhau:

App:
fetch price data from PriceRepository
call PriceCalculation with input values
call PriceRepository to update prices

Theo cách đó:

  • PriceCalculation đóng gói "logic kinh doanh". Thật dễ dàng để kiểm tra vì nó không tự gọi bất cứ thứ gì.
  • PriceRep repository có thể được kiểm tra giả đơn vị bằng cách thiết lập cơ sở dữ liệu giả và kiểm tra các cuộc gọi đọc và cập nhật. Nó có ít logic, do đó ít mã hóa, vì vậy bạn không cần quá nhiều thử nghiệm này.
  • Ứng dụng không thể được kiểm tra đơn vị một cách có ý nghĩa, bởi vì đó là mã keo. Tuy nhiên, nó cũng rất đơn giản, vì vậy kiểm thử tích hợp là đủ. Nếu sau này Ứng dụng trở nên quá phức tạp, bạn sẽ thoát ra nhiều lớp "logic kinh doanh" hơn.

Cuối cùng, nó có thể hóa ra PriceCalculation phải thực hiện các cuộc gọi cơ sở dữ liệu của riêng mình. Ví dụ vì chỉ có PriceCalculation biết dữ liệu nào cần, nên ứng dụng không thể tìm nạp trước. Sau đó, bạn có thể truyền cho nó một thể hiện của PriceRep repository (hoặc một số lớp kho lưu trữ khác), được tùy chỉnh theo nhu cầu của PriceCalculation. Lớp này sau đó sẽ cần phải được chế giễu, nhưng điều này sẽ đơn giản, bởi vì giao diện của PriceRep repository rất đơn giản, ví dụ PriceRepository.getPrice(articleNo, contractType). Quan trọng nhất, giao diện của PriceRep repository cách ly PriceCalculation khỏi cơ sở dữ liệu, do đó, các thay đổi đối với lược đồ DB hoặc tổ chức dữ liệu không có khả năng thay đổi giao diện của nó và do đó phá vỡ các giả định.


5
Tôi nghĩ rằng tôi đã một mình không nhìn thấy điểm trong đơn vị kiểm tra tất cả mọi thứ, cảm ơn
nhập vào

4
Tôi chỉ không đồng ý khi bạn nói các lớp loại 3 là số ít, tôi cảm thấy như hầu hết các mã của tôi là loại 3 và hầu như không có logic kinh doanh. Ý tôi là: stackoverflow.com/questions/38496185/ Kẻ
Rodrigo Ruiz

27

Lợi thế quyết định của thử nghiệm đơn vị so với thử nghiệm tích hợp là gì?

Đó là một sự phân đôi giả.

Kiểm thử đơn vị và kiểm thử tích hợp phục vụ hai mục đích tương tự, nhưng khác nhau. Mục đích của kiểm tra đơn vị là để đảm bảo các phương pháp của bạn hoạt động. Trong điều kiện thực tế, các thử nghiệm đơn vị đảm bảo rằng mã hoàn thành hợp đồng được phác thảo bởi các thử nghiệm đơn vị. Điều này thể hiện rõ qua cách các bài kiểm tra đơn vị được thiết kế: chúng nêu cụ thể những gì mã được cho là phải làm và khẳng định rằng mã thực hiện điều đó.

Kiểm tra tích hợp là khác nhau. Kiểm thử tích hợp thực hiện sự tương tác giữa các thành phần phần mềm. Bạn có thể có các thành phần phần mềm vượt qua tất cả các thử nghiệm của chúng và vẫn thất bại trong các thử nghiệm tích hợp vì chúng không tương tác đúng.

Tuy nhiên, nếu có một lợi thế quyết định đối với các bài kiểm tra đơn vị, thì đó là: các bài kiểm tra đơn vị dễ cài đặt hơn nhiều và đòi hỏi ít thời gian và công sức hơn nhiều so với các bài kiểm tra tích hợp. Khi được sử dụng đúng cách, các bài kiểm tra đơn vị khuyến khích phát triển mã "có thể kiểm tra", có nghĩa là kết quả cuối cùng sẽ đáng tin cậy hơn, dễ hiểu hơn và dễ bảo trì hơn. Mã có thể kiểm tra có một số đặc điểm nhất định, như API mạch lạc, hành vi lặp lại và nó trả về kết quả dễ xác nhận.

Kiểm tra tích hợp là khó khăn hơn và tốn kém hơn, bởi vì bạn thường cần chế giễu phức tạp, thiết lập phức tạp và xác nhận khó khăn. Ở mức độ tích hợp hệ thống cao nhất, hãy tưởng tượng cố gắng mô phỏng sự tương tác của con người trong một giao diện người dùng. Toàn bộ hệ thống phần mềm được dành cho loại tự động hóa đó. Và đó là tự động hóa mà chúng ta theo sau; thử nghiệm của con người là không thể lặp lại, và không có quy mô như thử nghiệm tự động.

Cuối cùng, kiểm thử tích hợp không đảm bảo về phạm vi bảo hiểm mã. Có bao nhiêu kết hợp các vòng lặp mã, điều kiện và các nhánh bạn đang kiểm tra với các bài kiểm tra tích hợp của mình? Bạn có thực sự biết? Có những công cụ mà bạn có thể sử dụng với các bài kiểm tra đơn vị và phương pháp được kiểm tra sẽ cho bạn biết mức độ bao phủ của mã và mức độ phức tạp theo chu kỳ của mã của bạn. Nhưng chúng chỉ thực sự hoạt động tốt ở cấp độ phương thức, nơi các bài kiểm tra đơn vị sống.


Nếu các bài kiểm tra của bạn thay đổi mỗi khi bạn tái cấu trúc, thì đó là một vấn đề khác. Các bài kiểm tra đơn vị được cho là về việc ghi lại những gì phần mềm của bạn làm, chứng minh rằng nó làm điều đó, và sau đó chứng minh rằng nó làm điều đó một lần nữa khi bạn cấu trúc lại việc triển khai cơ bản. Nếu API của bạn thay đổi hoặc bạn cần các phương thức của mình thay đổi theo sự thay đổi trong thiết kế hệ thống, đó là điều sẽ xảy ra. Nếu nó xảy ra nhiều, hãy xem xét việc viết bài kiểm tra của bạn trước, trước khi bạn viết mã. Điều này sẽ buộc bạn phải suy nghĩ về kiến ​​trúc tổng thể và cho phép bạn viết mã với API đã được thiết lập.

Nếu bạn đang dành nhiều thời gian để viết bài kiểm tra đơn vị cho mã tầm thường như

public string SomeProperty { get; set; }

sau đó bạn nên xem xét lại cách tiếp cận của bạn. Kiểm thử đơn vị được cho là để kiểm tra hành vi và không có hành vi nào trong dòng mã ở trên. Tuy nhiên, bạn đã tạo ra một sự phụ thuộc trong mã của mình ở đâu đó, vì thuộc tính đó gần như chắc chắn sẽ được chuyển đến nơi khác trong mã của bạn. Thay vì làm điều đó, hãy xem xét các phương thức viết chấp nhận thuộc tính cần thiết làm tham số:

public string SomeMethod(string someProperty);

Bây giờ phương thức của bạn không có bất kỳ sự phụ thuộc nào vào một cái gì đó bên ngoài nó, và giờ đây nó có thể kiểm chứng hơn, vì nó hoàn toàn khép kín. Cấp, bạn sẽ không thể luôn làm điều này, nhưng nó sẽ di chuyển mã của bạn theo hướng dễ kiểm tra hơn và lần này bạn đang viết một bài kiểm tra đơn vị cho hành vi thực tế.


2
Tôi biết rằng máy chủ thử nghiệm tích hợp và thử nghiệm tích hợp các mục tiêu khác nhau, tuy nhiên, tôi vẫn không hiểu làm thế nào thử nghiệm đơn vị hữu ích nếu bạn bỏ qua và chế nhạo tất cả các cuộc gọi công khai mà thử nghiệm đơn vị thực hiện. Tôi sẽ hiểu "mã hoàn thành hợp đồng được phác thảo bởi các bài kiểm tra đơn vị", nếu nó không dành cho sơ khai và giả; các bài kiểm tra đơn vị của tôi thực sự phản ánh logic bên trong các phương thức mà tôi đang kiểm tra. Bạn (tôi) không thực sự kiểm tra bất cứ điều gì, chỉ cần nhìn vào mã của bạn và 'chuyển đổi' nó thành kiểm tra. Liên quan đến khó khăn trong việc tự động hóa và bảo hiểm mã, tôi hiện đang thực hiện Rails và cả hai đều được chăm sóc tốt.
nhập

2
Nếu các bài kiểm tra của bạn chỉ là sự phản ánh của logic phương thức, thì bạn đã làm sai. Các bài kiểm tra đơn vị của bạn về cơ bản nên trao cho phương thức một giá trị, chấp nhận giá trị trả về và đưa ra khẳng định về giá trị trả về đó. Không có logic được yêu cầu để làm điều đó.
Robert Harvey

2
Có ý nghĩa, nhưng vẫn phải bỏ qua tất cả các cuộc gọi công khai khác (db, một số 'toàn cầu', như trạng thái người dùng hiện tại, v.v.) và kết thúc bằng mã thử nghiệm theo logic phương thức.
nhập

1
Vì vậy, tôi đoán các bài kiểm tra đơn vị dành cho những thứ bị cô lập chủ yếu là loại xác nhận cho 'bộ đầu vào -> bộ kết quả mong đợi'?
nhập

1
Kinh nghiệm của tôi trong việc tạo ra nhiều thử nghiệm đơn vị và tích hợp (không đề cập đến các công cụ mô phỏng, kiểm tra tích hợp và bảo hiểm mã nâng cao được sử dụng bởi các thử nghiệm đó) mâu thuẫn với hầu hết các khiếu nại của bạn ở đây: 1) "Mục đích của thử nghiệm đơn vị là để đảm bảo mã thực hiện những gì nó được cho là ": tương tự áp dụng cho thử nghiệm tích hợp (thậm chí còn hơn thế); 2) "kiểm tra đơn vị dễ cài đặt hơn nhiều": không, chúng không (khá thường xuyên, đó là các thử nghiệm tích hợp dễ dàng hơn); 3) "Khi được sử dụng đúng cách, các thử nghiệm đơn vị khuyến khích phát triển mã" có thể kiểm tra ": giống với các thử nghiệm tích hợp; (tiếp theo)
Rogério

4

Các bài kiểm tra đơn vị với giả là để đảm bảo việc thực hiện lớp là chính xác. Bạn chế giễu các giao diện công cộng về sự phụ thuộc của mã mà bạn đang kiểm tra. Bằng cách này, bạn có quyền kiểm soát mọi thứ bên ngoài lớp và chắc chắn rằng một bài kiểm tra thất bại là do một cái gì đó bên trong lớp chứ không phải ở một trong các đối tượng khác.

Bạn cũng đang kiểm tra hành vi của lớp đang kiểm tra chứ không phải việc thực hiện. Nếu bạn cấu trúc lại mã (tạo các phương thức nội bộ mới, v.v.), các bài kiểm tra đơn vị sẽ không thất bại. Nhưng nếu bạn đang thay đổi phương thức công khai thì hoàn toàn các bài kiểm tra sẽ thất bại vì bạn đã thay đổi hành vi.

Có vẻ như bạn đang viết bài kiểm tra sau khi bạn đã viết mã, thay vào đó hãy thử viết bài kiểm tra trước. Hãy thử phác thảo hành vi mà lớp nên có và sau đó viết số lượng mã tối thiểu để thực hiện các bài kiểm tra.

Cả kiểm thử đơn vị và kiểm thử tích hợp đều hữu ích để đảm bảo chất lượng mã của bạn. Các bài kiểm tra đơn vị kiểm tra từng thành phần trong sự cô lập. Và các bài kiểm tra tích hợp đảm bảo rằng tất cả các thành phần tương tác đúng. Tôi muốn có cả hai loại trong bộ thử nghiệm của tôi.

Các bài kiểm tra đơn vị đã giúp tôi phát triển khi tôi có thể tập trung vào một phần của ứng dụng tại một thời điểm. Chế giễu các thành phần mà tôi chưa thực hiện. Chúng cũng là một công cụ tuyệt vời cho hồi quy, vì chúng ghi lại bất kỳ lỗi nào trong logic mà tôi đã tìm thấy (ngay cả trong các bài kiểm tra đơn vị).

CẬP NHẬT

Tạo một thử nghiệm chỉ đảm bảo rằng các phương thức được gọi có giá trị trong đó bạn đang đảm bảo rằng các phương thức thực sự được gọi. Đặc biệt nếu bạn đang viết bài kiểm tra của mình trước, bạn có một danh sách kiểm tra các phương pháp cần phải xảy ra. Vì mã này có khá nhiều thủ tục, nên bạn không có nhiều thứ để kiểm tra ngoài các phương thức được gọi. Bạn đang bảo vệ mã để thay đổi trong tương lai. Khi bạn cần gọi một phương thức trước phương thức khác. Hoặc là một phương thức luôn được gọi ngay cả khi phương thức ban đầu đưa ra một ngoại lệ.

Thử nghiệm cho phương pháp này có thể không bao giờ thay đổi hoặc chỉ có thể thay đổi khi bạn thay đổi phương pháp. Tại sao điều này là một điều xấu? Nó giúp củng cố bằng cách sử dụng các bài kiểm tra. Nếu bạn phải sửa một bài kiểm tra sau khi thay đổi mã, bạn sẽ có thói quen thay đổi các bài kiểm tra với mã.


Và nếu một phương thức không gọi bất cứ thứ gì là riêng tư, thì không có điểm nào trong đơn vị kiểm tra nó, đúng không?
nhập

Nếu phương thức này là riêng tư, bạn không kiểm tra nó một cách rõ ràng, nó nên được kiểm tra thông qua giao diện chung. Tất cả các phương pháp công cộng nên được kiểm tra, để đảm bảo rằng hành vi đó là chính xác.
Schleis

Không, ý tôi là nếu một phương thức công khai không gọi bất cứ điều gì riêng tư, liệu nó có ý nghĩa để kiểm tra phương thức công khai đó không?
nhập

Đúng. Phương pháp nào đó không phải là nó? Vì vậy, nó cần được thử nghiệm. Từ quan điểm kiểm tra tôi không biết liệu nó có đang sử dụng bất cứ thứ gì riêng tư không. Tôi chỉ biết rằng nếu tôi cung cấp đầu vào A, tôi sẽ nhận được đầu ra B.
Schleis

Ồ vâng, phương thức này làm một cái gì đó và cái gì đó được gọi đến các phương thức công khai khác (và chỉ có thế). Vì vậy, cách bạn sẽ 'kiểm tra' đúng cách sẽ ngắt các cuộc gọi với một số giá trị trả về và sau đó thiết lập các kỳ vọng tin nhắn. Những gì chính xác bạn đang thử nghiệm trong trường hợp này? Đó là cuộc gọi đúng được thực hiện? Vâng, bạn đã viết phương pháp đó và bạn có thể nhìn vào nó và xem chính xác những gì nó làm. Tôi nghĩ rằng kiểm thử đơn vị phù hợp hơn với các phương thức riêng biệt được cho là được sử dụng như 'đầu vào -> đầu ra', vì vậy bạn có thể thiết lập một loạt các ví dụ và sau đó thực hiện kiểm tra hồi quy khi bạn tái cấu trúc.
nhập

3

Tôi đã trải qua một câu hỏi tương tự - cho đến khi tôi phát hiện ra sức mạnh của các bài kiểm tra thành phần. Nói tóm lại, chúng giống như các bài kiểm tra đơn vị ngoại trừ việc bạn không chế nhạo theo mặc định mà sử dụng các đối tượng thực (lý tưởng là thông qua tiêm phụ thuộc).

Bằng cách đó, bạn có thể nhanh chóng tạo các thử nghiệm mạnh mẽ với độ bao phủ mã tốt. Không cần cập nhật giả của bạn mọi lúc. Nó có thể kém chính xác hơn một chút so với các bài kiểm tra đơn vị với 100% giả, nhưng thời gian và tiền bạc bạn tiết kiệm được bù đắp cho điều đó. Điều duy nhất bạn thực sự cần sử dụng giả hoặc đồ đạc cho là phụ trợ lưu trữ hoặc các dịch vụ bên ngoài.

Trên thực tế, chế nhạo quá mức là một mô hình chống: Mô hình chống giảTDD là xấu xa .


0

Mặc dù op đã đánh dấu một câu trả lời, tôi chỉ thêm 2 xu của tôi vào đây.

Lợi thế quyết định của thử nghiệm đơn vị so với thử nghiệm tích hợp (không bao gồm chi phí thời gian) là gì?

Và cũng để đáp lại

Khi thực hiện kiểm tra đơn vị theo cách "phù hợp", tức là loại bỏ mọi cuộc gọi công khai và trả về các giá trị hoặc giả định đặt trước, tôi cảm thấy như mình không thực sự kiểm tra bất cứ điều gì.

Có một điều hữu ích nhưng không chính xác những gì OP đã hỏi:

Kiểm tra đơn vị làm việc nhưng vẫn còn lỗi?

từ kinh nghiệm nhỏ bé của tôi về các bộ kiểm thử, tôi hiểu rằng Các bài kiểm tra đơn vị luôn kiểm tra chức năng mức phương thức cơ bản nhất của một lớp. Theo tôi, mọi phương pháp công khai, riêng tư hoặc nội bộ đều xứng đáng có một bài kiểm tra đơn vị chuyên dụng. Ngay cả trong kinh nghiệm gần đây của tôi, tôi đã có một phương thức công khai gọi là phương thức riêng tư nhỏ khác. Vì vậy, có hai cách tiếp cận:

  1. không tạo (các) bài kiểm tra đơn vị cho phương thức riêng tư.
  2. tạo một bài kiểm tra đơn vị cho một phương thức riêng tư.

Nếu bạn nghĩ một cách logic, quan điểm của việc có một phương thức riêng là: phương thức công khai chính đang trở nên quá lớn hoặc lộn xộn. Để giải quyết vấn đề này, bạn tái cấu trúc một cách khôn ngoan và tạo ra các đoạn mã nhỏ xứng đáng là các phương thức riêng tư riêng biệt, từ đó làm cho phương thức công khai chính của bạn bớt cồng kềnh hơn. Bạn tái cấu trúc bằng cách ghi nhớ rằng phương pháp riêng tư này có thể được sử dụng lại sau này. Có thể có những trường hợp không có phương pháp công khai nào khác tùy thuộc vào phương pháp riêng tư đó, nhưng ai biết về tương lai.


Xem xét trường hợp khi phương thức riêng được sử dụng lại bằng nhiều phương pháp công cộng khác.

Vì vậy, nếu tôi đã chọn cách tiếp cận 1: Tôi sẽ sao chép các bài kiểm tra Đơn vị và chúng sẽ rất phức tạp, vì Bạn có số lượng Bài kiểm tra đơn vị cho từng nhánh của phương pháp công cộng cũng như phương pháp riêng.

Nếu tôi đã chọn cách tiếp cận 2: mã được viết cho các bài kiểm tra đơn vị sẽ tương đối ít hơn và việc kiểm tra sẽ dễ dàng hơn nhiều.


Xem xét trường hợp khi phương thức riêng không được sử dụng lại Không có điểm nào để viết một bài kiểm tra đơn vị riêng biệt cho phương pháp đó.

Đối với các bài kiểm tra tích hợp có liên quan, chúng có xu hướng toàn diện và nhiều cấp độ cao hơn. Họ sẽ cho bạn biết rằng đã đưa ra một đầu vào, tất cả các lớp của bạn sẽ đi đến kết luận cuối cùng này. Để hiểu thêm về tính hữu ích của kiểm thử tích hợp, vui lòng xem liên kết được đề cập.

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.