Làm thế nào chính xác các bài kiểm tra đơn vị nên được viết mà không chế nhạo rộng rãi?


80

Theo tôi hiểu, điểm của các bài kiểm tra đơn vị là kiểm tra các đơn vị mã trong sự cô lập . Điều này có nghĩa rằng:

  1. Họ không nên phá vỡ bởi bất kỳ thay đổi mã không liên quan ở nơi nào khác trong cơ sở mã.
  2. Chỉ có một thử nghiệm đơn vị nên phá vỡ bởi một lỗi trong đơn vị được thử nghiệm, trái ngược với các thử nghiệm tích hợp (có thể phá vỡ thành từng đống).

Tất cả điều này ngụ ý, rằng mọi sự phụ thuộc bên ngoài của một đơn vị được thử nghiệm, nên bị chế giễu. Và ý tôi là tất cả các phụ thuộc bên ngoài , không chỉ các "lớp bên ngoài" như mạng, hệ thống tập tin, cơ sở dữ liệu, v.v.

Điều này dẫn đến một kết luận hợp lý, rằng hầu như mọi bài kiểm tra đơn vị đều cần phải chế giễu . Mặt khác, một tìm kiếm nhanh trên Google về chế nhạo cho thấy hàng tấn bài báo cho rằng "chế giễu là mùi mã" và chủ yếu nên (dù không hoàn toàn) nên tránh.

Bây giờ, đến (các) câu hỏi.

  1. Làm thế nào các bài kiểm tra đơn vị nên được viết đúng?
  2. Chính xác thì ranh giới giữa chúng và các bài kiểm tra tích hợp nằm ở đâu?

Cập nhật 1

Vui lòng xem xét mã giả sau đây:

class Person {
    constructor(calculator) {}

    calculate(a, b) {
        const sum = this.calculator.add(a, b);

        // do some other stuff with the `sum`
    }
}

Một thử nghiệm kiểm tra Person.calculatephương thức mà không chế nhạo sự Calculatorphụ thuộc (được đưa ra, đó Calculatorlà một lớp nhẹ không truy cập vào "thế giới bên ngoài") có thể được coi là một thử nghiệm đơn vị không?


10
Một phần của điều này chỉ là kinh nghiệm thiết kế sẽ đi cùng với thời gian. Bạn sẽ học cách cấu trúc các thành phần của mình để chúng không gặp nhiều khó khăn để chế giễu các phụ thuộc. Điều này có nghĩa là khả năng kiểm tra phải là mục tiêu thiết kế thứ cấp của bất kỳ phần mềm nào. Mục tiêu này dễ thỏa mãn hơn nhiều nếu kiểm tra được viết trước hoặc cùng với mã, ví dụ: sử dụng TDD và / hoặc BDD.
amon

33
Đặt các bài kiểm tra chạy nhanh và đáng tin cậy vào một thư mục. Đặt các bài kiểm tra chậm và có khả năng dễ vỡ vào một bài kiểm tra khác. Chạy các bài kiểm tra trong thư mục đầu tiên càng thường xuyên càng tốt (theo nghĩa đen mỗi khi bạn tạm dừng nhập và mã biên dịch là lý tưởng, nhưng không phải tất cả các môi trường dev đều hỗ trợ điều này). Chạy các bài kiểm tra chậm ít thường xuyên hơn (ví dụ khi bạn nghỉ giải lao). Đừng lo lắng về tên đơn vị và tích hợp. Gọi cho họ nhanh và chậm nếu bạn muốn. Nó không thành vấn đề.
David Arno

6
"mà hầu như mọi bài kiểm tra đơn vị cần phải chế giễu" Vâng, vậy sao? "Một tìm kiếm nhanh trên Google về chế nhạo cho thấy hàng tấn bài báo cho rằng" chế giễu là mùi mã "" họ đã sai .
Michael

13
@Michael Đơn giản chỉ cần nói 'ừ, vậy' và tuyên bố quan điểm đối lập sai không phải là cách hay để tiếp cận một chủ đề gây tranh cãi như thế này. Có lẽ viết một câu trả lời và giải thích lý do tại sao bạn nghĩ chế giễu nên ở khắp mọi nơi, và có lẽ tại sao bạn nghĩ rằng "hàng tấn bài viết" vốn đã sai?
AJFaraday

6
Vì bạn đã không đưa ra một trích dẫn cho "chế nhạo là mùi mã", tôi chỉ có thể đoán rằng bạn đang hiểu sai những gì bạn đọc. Mocking không phải là một mùi mã. Sự cần thiết phải sử dụng sự phản chiếu hoặc các shenanigans khác để tiêm giả của bạn là một mùi mã. Khó khăn của việc chế nhạo tỷ lệ nghịch với chất lượng thiết kế API của bạn. Nếu bạn có thể viết các bài kiểm tra đơn vị đơn giản đơn giản chỉ cần chuyển giả cho các nhà xây dựng, bạn đã làm đúng.
TKK

Câu trả lời:


59

điểm của các bài kiểm tra đơn vị là để kiểm tra các đơn vị mã trong sự cô lập.

Martin Fowler trong bài kiểm tra đơn vị

Kiểm thử đơn vị thường được nói đến trong phát triển phần mềm và là một thuật ngữ mà tôi đã quen thuộc trong suốt thời gian viết chương trình. Tuy nhiên, giống như hầu hết các thuật ngữ phát triển phần mềm, nó rất khó xác định và tôi thấy sự nhầm lẫn thường có thể xảy ra khi mọi người nghĩ rằng nó được định nghĩa chặt chẽ hơn thực tế.

Những gì Kent Beck đã viết trong Test Driven Development, Ví dụ

Tôi gọi chúng là "bài kiểm tra đơn vị", nhưng chúng không khớp với định nghĩa được chấp nhận của bài kiểm tra đơn vị

Bất kỳ khiếu nại nào về "điểm của bài kiểm tra đơn vị là" sẽ phụ thuộc rất nhiều vào định nghĩa của "bài kiểm tra đơn vị" đang được xem xét.

Nếu quan điểm của bạn là chương trình của bạn bao gồm nhiều đơn vị nhỏ phụ thuộc vào nhau và nếu bạn tự giới hạn một phong cách kiểm tra từng đơn vị riêng lẻ, thì rất nhiều thử nghiệm nhân đôi là kết luận không thể tránh khỏi.

Lời khuyên mâu thuẫn mà bạn thấy xuất phát từ những người hoạt động dưới một loạt các giả định khác nhau.

Ví dụ: nếu bạn đang viết các bài kiểm tra để hỗ trợ các nhà phát triển trong quá trình tái cấu trúc và chia một đơn vị thành hai là một cấu trúc lại cần được hỗ trợ, thì cần phải cung cấp một cái gì đó. Có lẽ loại thử nghiệm này cần một tên khác? Hoặc có lẽ chúng ta cần một sự hiểu biết khác nhau về "đơn vị".

Bạn có thể muốn so sánh:

Một bài kiểm tra có thể kiểm tra phương thức Person.calculate mà không chế nhạo sự phụ thuộc của Máy tính (được đưa ra, rằng Máy tính là một lớp nhẹ không truy cập vào "thế giới bên ngoài") có thể được coi là một bài kiểm tra đơn vị không?

Tôi nghĩ đó là câu hỏi sai; đó lại là một cuộc tranh luận về các nhãn , khi tôi tin rằng những gì chúng ta thực sự quan tâm là các thuộc tính .

Khi tôi giới thiệu các thay đổi về mã, tôi không quan tâm đến việc cô lập các bài kiểm tra - tôi đã biết rằng "lỗi" nằm ở đâu đó trong chồng các chỉnh sửa chưa được xác minh hiện tại của tôi. Nếu tôi chạy thử nghiệm thường xuyên, thì tôi sẽ giới hạn độ sâu của ngăn xếp đó và việc tìm ra lỗi là không đáng kể (trong trường hợp cực đoan, các thử nghiệm được chạy sau mỗi lần chỉnh sửa - độ sâu tối đa của ngăn xếp là một). Nhưng chạy thử nghiệm không phải là mục tiêu - đó là một sự gián đoạn - vì vậy có giá trị trong việc giảm tác động của sự gián đoạn. Một cách để giảm sự gián đoạn là đảm bảo các bài kiểm tra nhanh ( Gary Bernhardt gợi ý 300ms , nhưng tôi chưa tìm ra cách để làm điều đó trong hoàn cảnh của mình).

Nếu việc gọi Calculator::addkhông làm tăng đáng kể thời gian cần thiết để chạy thử nghiệm (hoặc bất kỳ thuộc tính quan trọng nào khác cho trường hợp sử dụng này), thì tôi sẽ không sử dụng thử nghiệm gấp đôi - nó không mang lại lợi ích lớn hơn chi phí .

Lưu ý hai giả định ở đây: một con người như là một phần của đánh giá chi phí và một loạt các thay đổi chưa được xác minh trong đánh giá lợi ích. Trong trường hợp những điều kiện đó không giữ được, giá trị của "sự cô lập" thay đổi khá nhiều.

Xem thêm Lava nóng , của Harry Percival.


5
một điều mà mocking add làm là chứng minh rằng máy tính có thể bị chế giễu, tức là thiết kế không ghép đôi người và máy tính (mặc dù điều này cũng có thể được kiểm tra theo những cách khác)
jk.

40

Làm thế nào chính xác các bài kiểm tra đơn vị nên được viết mà không chế nhạo rộng rãi?

Bằng cách giảm thiểu tác dụng phụ trong mã của bạn.

Lấy mã ví dụ của bạn, calculatorví dụ như nói chuyện với API web, thì bạn có thể tạo các thử nghiệm dễ vỡ dựa trên khả năng tương tác với API web đó hoặc bạn tạo ra một giả lập của nó. Tuy nhiên, nếu nó là một tập hợp các hàm tính toán xác định, không có trạng thái, thì bạn không (và không nên) chế giễu nó. Nếu bạn làm như vậy, bạn có nguy cơ giả lập của bạn cư xử khác với mã thực, dẫn đến lỗi trong các bài kiểm tra của bạn.

Chỉ cần các giả lập cho mã đọc / ghi vào hệ thống tệp, cơ sở dữ liệu, điểm cuối URL, v.v; đó là phụ thuộc vào môi trường bạn đang chạy theo; hoặc đó là rất có tính trạng thái và không xác định trong tự nhiên. Vì vậy, nếu bạn giữ các phần đó của mã ở mức tối thiểu và ẩn chúng đằng sau sự trừu tượng, thì chúng rất dễ bị chế giễu và phần còn lại của mã của bạn sẽ tránh được nhu cầu giả.

Đối với các điểm mã có tác dụng phụ, đáng để viết các bài kiểm tra giả và kiểm tra không. Loại thứ hai mặc dù cần được chăm sóc vì chúng sẽ dễ vỡ và có thể chậm. Vì vậy, bạn có thể chỉ muốn chạy chúng nói qua đêm trên máy chủ CI, thay vì mỗi lần bạn lưu và xây dựng mã của mình. Các bài kiểm tra trước đây nên được chạy thường xuyên nhất có thể. Về việc mỗi bài kiểm tra sau đó là một bài kiểm tra đơn vị hay tích hợp sẽ trở thành học thuật và tránh "chiến tranh lửa" đối với những gì và không phải là một bài kiểm tra đơn vị.


8
Đây là câu trả lời đúng cả trong thực tế và về mặt né tránh một cuộc tranh luận ngữ nghĩa vô nghĩa.
Jared Smith

Bạn có một ví dụ về một cơ sở mã nguồn mở không tầm thường sử dụng kiểu như vậy mà vẫn có được phạm vi kiểm tra tốt không?
Joeri Sebrechts

4
@JoeriSebrechts mỗi một FP? Ví dụ
Jared Smith

Không hoàn toàn là những gì tôi đang tìm kiếm, vì đó chỉ là một tập hợp các chức năng độc lập với nhau, không phải là một hệ thống các chức năng gọi nhau. Làm thế nào để bạn có được xung quanh việc phải xây dựng các đối số phức tạp cho một hàm cho mục đích kiểm tra nó, nếu hàm đó là một trong những đối số cấp cao nhất? Ví dụ: vòng lặp cốt lõi của một trò chơi.
Joeri Sebrechts

1
@JoeriSebrechts hmm, hoặc tôi đang hiểu sai những gì bạn muốn, hoặc bạn đã không đào sâu vào ví dụ tôi đã đưa ra. Các hàm ramda sử dụng các cuộc gọi nội bộ ở mọi nơi trong nguồn của chúng (ví dụ R.equals). Bởi vì chúng dành cho hầu hết các hàm thuần túy, chúng thường không bị chế giễu trong các thử nghiệm.
Jared Smith

31

Những câu hỏi này khá khác nhau về độ khó của họ. Trước tiên hãy đặt câu hỏi 2.

Kiểm tra đơn vị và kiểm tra tích hợp được phân tách rõ ràng. Một bài kiểm tra đơn vị kiểm tra một đơn vị (phương thức hoặc lớp) và chỉ sử dụng các đơn vị khác nhiều nhất có thể để đạt được mục tiêu đó. Mocking có thể là cần thiết, nhưng nó không phải là điểm của bài kiểm tra. Một bài kiểm tra tích hợp kiểm tra sự tương tác giữa các đơn vị thực tế khác nhau . Sự khác biệt này là toàn bộ lý do tại sao chúng tôi cần cả thử nghiệm đơn vị và tích hợp - nếu một công việc của người kia đủ tốt, chúng tôi sẽ không, nhưng hóa ra việc sử dụng hai công cụ chuyên dụng thay vì một công cụ tổng quát thường hiệu quả hơn .

Bây giờ cho câu hỏi quan trọng: bạn nên kiểm tra đơn vị như thế nào? Như đã nói ở trên, các bài kiểm tra đơn vị chỉ nên xây dựng các cấu trúc phụ trợ khi cần thiết . Thông thường sẽ dễ sử dụng cơ sở dữ liệu giả hơn cơ sở dữ liệu thực của bạn hoặc thậm chí bất kỳ cơ sở dữ liệu thực nào . Tuy nhiên, tự chế giễu không có giá trị. Nếu thường xảy ra, trên thực tế sẽ dễ dàng hơn khi sử dụng các thành phần thực tế của lớp khác làm đầu vào cho một bài kiểm tra đơn vị cấp trung. Nếu vậy, đừng ngần ngại sử dụng chúng.

Nhiều học viên sợ rằng nếu bài kiểm tra đơn vị B sử dụng lại các lớp đã được kiểm tra bằng bài kiểm tra đơn vị A, thì lỗi ở bài A gây ra lỗi bài kiểm tra ở nhiều nơi. Tôi coi đây không phải là một vấn đề: một bộ kiểm tra có để thành công 100% để cung cấp cho bạn sự yên tâm bạn cần, vì vậy nó không phải là một vấn đề lớn có quá nhiều thất bại - sau khi tất cả, bạn làm có một khiếm khuyết. Vấn đề quan trọng duy nhất sẽ là nếu một khiếm khuyết gây ra quá ít thất bại.

Do đó, đừng tạo ra một tôn giáo chế giễu. Đó là một phương tiện, không phải là kết thúc, vì vậy nếu bạn có thể thoát khỏi việc tránh nỗ lực thêm, bạn nên làm như vậy.


4
The only critical problem would be if a defect triggered too few failures.đây là một trong những điểm yếu của chế giễu. Chúng tôi phải "lập trình" hành vi dự kiến ​​vì vậy, chúng tôi có thể thất bại trong việc đó, khiến các thử nghiệm của chúng tôi kết thúc là "dương tính giả". Nhưng chế nhạo là một kỹ thuật rất hữu ích để đạt được tính quyết định (điều kiện quan trọng nhất của thử nghiệm). Tôi sử dụng chúng trong tất cả các dự án của tôi khi có thể. Họ cũng chỉ cho tôi khi tích hợp quá phức tạp hoặc sự phụ thuộc quá chặt chẽ.
Laiv

1
Nếu một đơn vị đang được thử nghiệm sử dụng các đơn vị khác, thì đó có thực sự là một thử nghiệm tích hợp không? Bởi vì về bản chất, đơn vị này sẽ kiểm tra sự tương tác giữa các đơn vị nói trên, giống hệt như thử nghiệm tích hợp.
Alexander Lomia

11
@AlexanderLomia: Bạn sẽ gọi một đơn vị là gì? Bạn có thể gọi 'String' là một đơn vị không? Tôi sẽ, nhưng tôi sẽ không mơ ước chế giễu nó.
Bart van Ingen Schenau 27/11/18

5
"Các bài kiểm tra đơn vị và kiểm tra tích hợp được phân tách rõ ràng. Một bài kiểm tra đơn vị kiểm tra một đơn vị (phương pháp hoặc lớp) và chỉ sử dụng các đơn vị khác nhiều nhất có thể để đạt được mục tiêu đó ". Đây là chà. Đó là định nghĩa của bạn về một bài kiểm tra đơn vị. Của tôi là khá khác nhau. Vì vậy, sự khác biệt giữa chúng chỉ là "tách biệt rõ ràng" cho bất kỳ định nghĩa cụ thể nào nhưng sự tách biệt khác nhau giữa các định nghĩa.
David Arno

4
@Voo Đã làm việc với các cơ sở mã như vậy, trong khi việc tìm ra vấn đề ban đầu có thể gây phiền toái (đặc biệt là nếu kiến ​​trúc đã vẽ lên những thứ bạn sử dụng để gỡ lỗi), tôi vẫn gặp nhiều rắc rối hơn từ các giả tạo gây ra các thử nghiệm để tiếp tục hoạt động sau khi mã được sử dụng để kiểm tra đã bị hỏng.
James_pic

6

OK, vì vậy để trả lời câu hỏi của bạn trực tiếp:

Làm thế nào các bài kiểm tra đơn vị nên được viết đúng?

Như bạn nói, bạn nên chế giễu các phụ thuộc và chỉ kiểm tra đơn vị được đề cập.

Chính xác thì ranh giới giữa chúng và các bài kiểm tra tích hợp nằm ở đâu?

Kiểm tra tích hợp là kiểm tra đơn vị trong đó các phụ thuộc của bạn không bị chế giễu.

Một bài kiểm tra có thể kiểm tra phương pháp Person.calculate mà không chế nhạo Máy tính có thể được coi là một bài kiểm tra đơn vị không?

Không. Bạn cần đưa phần phụ thuộc của máy tính vào mã này và bạn có lựa chọn giữa phiên bản giả hoặc phiên bản thật. Nếu bạn sử dụng một thử nghiệm giả, đó là thử nghiệm đơn vị, nếu bạn sử dụng thử nghiệm thực sự thì đó là thử nghiệm tích hợp.

Tuy nhiên, một cảnh báo. Bạn có thực sự quan tâm những gì mọi người nghĩ rằng bài kiểm tra của bạn nên được gọi là?

Nhưng câu hỏi thực sự của bạn dường như là thế này:

một tìm kiếm nhanh của Google về chế nhạo cho thấy hàng tấn bài báo cho rằng "chế nhạo là mùi mã" và chủ yếu nên (dù không hoàn toàn) nên tránh.

Tôi nghĩ vấn đề ở đây là rất nhiều người sử dụng giả để tái tạo hoàn toàn các phụ thuộc. Ví dụ tôi có thể chế giễu máy tính trong ví dụ của bạn là

public class MockCalc : ICalculator
{
     public Add(int a, int b) { return 4; }
}

Tôi sẽ không làm một cái gì đó như:

myMock = Mock<ICalculator>().Add((a,b) => {return a + b;})
myPerson.Calculate()
Assert.WasCalled(myMock.Add());

Tôi sẽ lập luận rằng, đó sẽ là "thử nghiệm giả của tôi" hoặc "thử nghiệm việc thực hiện". Tôi sẽ nói " Đừng viết Mocks! * Như thế".

Những người khác sẽ không đồng ý với tôi, chúng tôi sẽ bắt đầu những cuộc chiến nảy lửa lớn trên blog của chúng tôi về Cách tốt nhất để Mock, điều này thực sự vô nghĩa trừ khi bạn hiểu toàn bộ nền tảng của các phương pháp khác nhau và thực sự không mang lại nhiều giá trị cho một người chỉ muốn viết bài kiểm tra tốt.


Cảm ơn cho một câu trả lời đầy đủ. Khi quan tâm đến những gì người khác nghĩ về các bài kiểm tra của tôi - thực sự tôi muốn tránh viết các bài kiểm tra bán tích hợp, các bài kiểm tra bán đơn vị có xu hướng trở thành một mớ hỗn độn không đáng tin cậy khi dự án tiến triển.
Alexander Lomia

không có probs, tôi nghĩ vấn đề là định nghĩa của hai điều không được mọi người đồng ý 100%.
Ewan

Tôi sẽ đổi tên lớp của bạn MockCalcthành StubCalc, và gọi nó là sơ khai không phải là giả. martinfowler.com/articles/ từ
bdsl

@bdsl Bài báo đó 15 tuổi
Ewan

4
  1. Làm thế nào để kiểm tra đơn vị nên được thực hiện đúng?

Nguyên tắc của tôi là kiểm tra đơn vị thích hợp:

  • Được mã hóa chống lại các giao diện, không triển khai . Điều này có nhiều lợi ích. Đối với một, nó đảm bảo rằng các lớp của bạn tuân theo Nguyên tắc đảo ngược phụ thuộc từ RẮN . Ngoài ra, đây là những gì các lớp khác của bạn làm ( phải không? ) Để các bài kiểm tra của bạn nên làm như vậy. Ngoài ra, điều này cho phép bạn kiểm tra nhiều triển khai của cùng một giao diện trong khi sử dụng lại nhiều mã kiểm tra (chỉ khởi tạo và một số xác nhận sẽ thay đổi).
  • Là khép kín . Như bạn đã nói, những thay đổi trong bất kỳ mã bên ngoài nào có thể ảnh hưởng đến kết quả kiểm tra. Như vậy, các bài kiểm tra đơn vị có thể thực hiện tại thời điểm xây dựng. Điều này có nghĩa là bạn cần giả để loại bỏ bất kỳ tác dụng phụ. Tuy nhiên, nếu bạn đang tuân theo Nguyên tắc đảo ngược phụ thuộc, việc này sẽ tương đối dễ dàng. Các khung kiểm tra tốt như Spock có thể được sử dụng để tự động cung cấp các triển khai giả của bất kỳ giao diện nào để sử dụng trong các thử nghiệm của bạn với mã hóa tối thiểu. Điều này có nghĩa là mỗi lớp kiểm tra chỉ cần thực hiện mã từ chính xác một lớp thực hiện, cộng với khung kiểm tra (và có thể các lớp mô hình ["bean"]).
  • Không yêu cầu một ứng dụng chạy riêng . Nếu thử nghiệm cần "nói chuyện với một cái gì đó", cho dù là cơ sở dữ liệu hoặc dịch vụ web, thì đó là thử nghiệm tích hợp, không phải thử nghiệm đơn vị. Tôi vẽ đường tại các kết nối mạng hoặc hệ thống tập tin. Ví dụ, một cơ sở dữ liệu SQLite hoàn toàn trong bộ nhớ là trò chơi công bằng theo quan điểm của tôi đối với bài kiểm tra đơn vị nếu bạn thực sự cần nó.

Nếu có các lớp tiện ích từ các khung làm phức tạp việc kiểm tra đơn vị, bạn thậm chí có thể thấy hữu ích khi tạo các giao diện và lớp "trình bao bọc" rất đơn giản để tạo điều kiện cho việc loại bỏ các phụ thuộc đó. Những hàm bao sau đó không nhất thiết phải chịu các bài kiểm tra đơn vị.

  1. Chính xác thì ranh giới giữa chúng [kiểm tra đơn vị] và kiểm tra tích hợp nằm ở đâu?

Tôi đã tìm thấy sự khác biệt này là hữu ích nhất:

  • Các thử nghiệm đơn vị mô phỏng "mã người dùng" , xác minh hành vi của các lớp thực hiện dựa trên hành vi và ngữ nghĩa mong muốn của các giao diện cấp mã .
  • Kiểm tra tích hợp mô phỏng người dùng , xác minh hành vi của ứng dụng đang chạy đối với các trường hợp sử dụng được chỉ định và / hoặc API chính thức. Đối với dịch vụ web, "người dùng" sẽ là ứng dụng khách.

Có khu vực màu xám ở đây. Ví dụ: nếu bạn có thể chạy một ứng dụng trong bộ chứa Docker và chạy các bài kiểm tra tích hợp như là giai đoạn cuối cùng của bản dựng và phá hủy bộ chứa sau đó, bạn có thể đưa các bài kiểm tra đó làm "kiểm tra đơn vị" không? Nếu đây là cuộc tranh luận sôi nổi của bạn, bạn đang ở một nơi khá tốt.

  1. Có đúng là hầu như mọi bài kiểm tra đơn vị cần phải chế giễu?

Không. Một số trường hợp kiểm tra riêng lẻ sẽ dành cho các điều kiện lỗi, như truyền nulldưới dạng tham số và xác minh bạn có ngoại lệ. Rất nhiều bài kiểm tra như thế sẽ không yêu cầu bất kỳ giả. Ngoài ra, việc triển khai không có tác dụng phụ, ví dụ như xử lý chuỗi hoặc hàm toán học, có thể không yêu cầu bất kỳ giả định nào vì bạn chỉ cần xác minh đầu ra. Nhưng hầu hết các lớp đáng để có, tôi nghĩ, sẽ yêu cầu ít nhất một giả ở đâu đó trong mã kiểm tra. (Càng ít, càng tốt.)

Vấn đề "mùi mã" mà bạn đề cập phát sinh khi bạn có một lớp quá phức tạp, đòi hỏi một danh sách dài các phụ thuộc giả để viết bài kiểm tra của bạn. Đây là một đầu mối mà bạn cần cấu trúc lại việc thực hiện và phân chia mọi thứ, sao cho mỗi lớp có dấu chân nhỏ hơn và trách nhiệm rõ ràng hơn, và do đó dễ kiểm tra hơn. Điều này sẽ cải thiện chất lượng trong thời gian dài.

Chỉ có một bài kiểm tra đơn vị nên phá vỡ bởi một lỗi trong đơn vị được kiểm tra.

Tôi không nghĩ rằng đây là một kỳ vọng hợp lý, bởi vì nó hoạt động chống lại việc sử dụng lại. Bạn có thể có một privatephương thức, ví dụ, được gọi bằng nhiều publicphương thức được xuất bản bởi giao diện của bạn. Một lỗi được đưa vào một phương thức sau đó có thể gây ra nhiều lỗi thử nghiệm. Điều này không có nghĩa là bạn nên sao chép cùng một mã vào mỗi publicphương thức.


3
  1. Họ không nên phá vỡ bởi bất kỳ thay đổi mã không liên quan ở nơi nào khác trong cơ sở mã.

Tôi không thực sự chắc chắn làm thế nào quy tắc này hữu ích. Nếu một sự thay đổi trong một lớp / phương thức / bất cứ điều gì có thể phá vỡ hành vi của người khác trong mã sản xuất, thì thực tế, những người cộng tác, và không liên quan. Nếu các thử nghiệm của bạn bị hỏng và mã sản xuất của bạn không có, thì các thử nghiệm của bạn sẽ bị nghi ngờ.

  1. Chỉ có một thử nghiệm đơn vị nên phá vỡ bởi một lỗi trong đơn vị được thử nghiệm, trái ngược với các thử nghiệm tích hợp (có thể phá vỡ thành từng đống).

Tôi cũng coi quy tắc này với sự nghi ngờ. Nếu bạn thực sự đủ tốt để cấu trúc mã của bạn và viết các bài kiểm tra của bạn sao cho một lỗi gây ra chính xác một lỗi kiểm tra đơn vị, thì bạn đã nói rằng bạn đã xác định được tất cả các lỗi tiềm ẩn, ngay cả khi codebase phát triển để sử dụng các trường hợp bạn không lường trước được.

Chính xác thì ranh giới giữa chúng và các bài kiểm tra tích hợp nằm ở đâu?

Tôi không nghĩ đó là một sự khác biệt quan trọng. Dù sao thì một "đơn vị" mã là gì?

Cố gắng tìm các điểm vào mà tại đó bạn có thể viết các bài kiểm tra chỉ "có ý nghĩa" về mặt quy tắc của miền / vấn đề kinh doanh mà mức mã đó đang xử lý. Thông thường các thử nghiệm này có phần 'chức năng' về bản chất - đưa vào một đầu vào và kiểm tra xem đầu ra có như mong đợi không. Nếu các thử nghiệm thể hiện một hành vi mong muốn của hệ thống, thì chúng thường vẫn khá ổn định ngay cả khi mã sản xuất phát triển và được tái cấu trúc.

Làm thế nào chính xác các bài kiểm tra đơn vị nên được viết mà không chế nhạo rộng rãi?

Đừng đọc quá nhiều từ 'đơn vị' và nghiêng về sử dụng các lớp sản xuất thực tế của bạn trong các bài kiểm tra, mà không phải lo lắng quá nhiều nếu bạn liên quan đến nhiều hơn một trong số chúng trong một bài kiểm tra. Nếu một trong số chúng khó sử dụng (vì cần nhiều khởi tạo hoặc cần phải truy cập cơ sở dữ liệu / máy chủ email thực sự, v.v.), thì hãy để suy nghĩ của bạn chuyển sang chế giễu / giả mạo.


" Dù sao thì một" đơn vị "mã là gì? " Câu hỏi rất hay có thể có câu trả lời bất ngờ thậm chí có thể phụ thuộc vào người đang trả lời. Thông thường, hầu hết các định nghĩa về kiểm thử đơn vị giải thích chúng liên quan đến một phương thức hoặc một lớp nhưng đó không phải là thước đo thực sự hữu ích của một "đơn vị" trong mọi trường hợp. Nếu tôi có một Person:tellStory()phương thức kết hợp các chi tiết của một người vào một chuỗi thì trả về điều đó, thì "câu chuyện" có thể là một đơn vị. Nếu tôi thực hiện một phương thức trợ giúp riêng giúp loại bỏ một số mã, thì tôi không tin rằng tôi đã giới thiệu một đơn vị mới - tôi không cần phải kiểm tra riêng điều đó.
VLAZ

1

Đầu tiên, một số định nghĩa:

Một thử nghiệm đơn vị kiểm tra các đơn vị cách ly với các đơn vị khác, nhưng điều đó có nghĩa là không được xác định cụ thể bởi bất kỳ nguồn có thẩm quyền nào, vì vậy hãy xác định nó tốt hơn một chút: Nếu ranh giới I / O được vượt qua (cho dù I / O là mạng, đĩa, màn hình hoặc đầu vào UI), có một vị trí bán mục tiêu chúng ta có thể vẽ một đường. Nếu mã phụ thuộc vào I / O, nó sẽ vượt qua một ranh giới đơn vị, và do đó, nó sẽ cần phải chế giễu đơn vị chịu trách nhiệm cho I / O đó.

Theo định nghĩa đó, tôi không thấy một lý do thuyết phục nào để chế nhạo những thứ như các hàm thuần túy, nghĩa là thử nghiệm đơn vị cho chính nó vào các hàm thuần túy hoặc các hàm mà không có tác dụng phụ.

Nếu bạn muốn đơn vị thử nghiệm đơn vị có hiệu ứng, thay vào đó, đơn vị chịu trách nhiệm về hiệu ứng sẽ bị chế giễu, nhưng có lẽ bạn nên xem xét thử nghiệm tích hợp, thay vào đó. Vì vậy, câu trả lời ngắn gọn là: "nếu bạn cần chế giễu, hãy tự hỏi xem điều bạn thực sự cần là một bài kiểm tra tích hợp". Nhưng có một câu trả lời tốt hơn, dài hơn ở đây, và lỗ thỏ đi sâu hơn nhiều. Mocks có thể là mùi mã yêu thích của tôi vì có rất nhiều thứ để học hỏi từ chúng.

Mã mùi

Đối với điều này, chúng tôi sẽ chuyển sang Wikipedia:

Trong lập trình máy tính, mùi mã là bất kỳ đặc điểm nào trong mã nguồn của chương trình có thể chỉ ra vấn đề sâu hơn.

Nó tiếp tục sau đó ...

"Mùi là một số cấu trúc nhất định trong mã cho thấy sự vi phạm các nguyên tắc thiết kế cơ bản và ảnh hưởng tiêu cực đến chất lượng thiết kế". Suryanarayana, Girish (tháng 11 năm 2014). Tái cấu trúc cho mùi thiết kế phần mềm. Morgan Kaufmann. tr. 258.

Mùi mã thường không phải là lỗi; chúng không đúng về mặt kỹ thuật và không ngăn chương trình hoạt động. Thay vào đó, họ chỉ ra những điểm yếu trong thiết kế có thể làm chậm sự phát triển hoặc tăng nguy cơ lỗi hoặc thất bại trong tương lai.

Nói cách khác, không phải tất cả các mã mùi đều xấu. Thay vào đó, chúng là những dấu hiệu phổ biến cho thấy một cái gì đó có thể không được thể hiện ở dạng tối ưu của nó và mùi có thể chỉ ra một cơ hội để cải thiện mã được đề cập.

Trong trường hợp chế nhạo, mùi cho thấy các đơn vị dường như đang kêu gọi giả phụ thuộc vào các đơn vị bị chế giễu. Nó có thể là một dấu hiệu cho thấy chúng ta chưa phân tách vấn đề thành các phần có thể giải quyết được về nguyên tử và điều đó có thể chỉ ra một lỗ hổng thiết kế trong phần mềm.

Bản chất của tất cả sự phát triển phần mềm là quá trình chia nhỏ một vấn đề lớn thành các phần nhỏ hơn, độc lập (phân tách) và kết hợp các giải pháp lại với nhau để tạo thành một ứng dụng giải quyết vấn đề lớn (thành phần).

Mocking được yêu cầu khi các đơn vị được sử dụng để phá vỡ vấn đề lớn thành các phần nhỏ hơn phụ thuộc vào nhau. Nói cách khác, việc chế giễu là cần thiết khi các đơn vị thành phần nguyên tử được cho là của chúng ta không thực sự là nguyên tử và chiến lược phân rã của chúng ta đã thất bại trong việc phân tách vấn đề lớn hơn thành các vấn đề nhỏ hơn, độc lập hơn để giải quyết.

Điều khiến cho việc chế nhạo một mùi mã không phải là có bất cứ điều gì vốn đã sai với việc chế giễu - đôi khi nó rất hữu ích. Điều làm cho nó có mùi mã là nó có thể chỉ ra một nguồn khớp nối có vấn đề trong ứng dụng của bạn. Đôi khi loại bỏ nguồn khớp nối đó hiệu quả hơn nhiều so với viết một bản giả.

Có nhiều loại khớp nối, và một số tốt hơn so với những loại khác. Hiểu rằng mocks là một mã số mùi có thể dạy cho bạn để xác định và tránh những loại tồi tệ nhất đầu trong vòng đời thiết kế ứng dụng, trước mùi phát triển thành một cái gì đó tồi tệ hơn.


0

Mocking chỉ nên được sử dụng như là phương sách cuối cùng, ngay cả trong các bài kiểm tra đơn vị.

Một phương thức không phải là một đơn vị và thậm chí một lớp không phải là một đơn vị. Một đơn vị là bất kỳ sự phân tách logic hợp lý nào có ý nghĩa, bất kể bạn gọi nó là gì. Một yếu tố quan trọng của việc có mã được kiểm tra tốt là có thể tự do cấu trúc lại và một phần của khả năng tái cấu trúc tự do có nghĩa là bạn không phải thay đổi các thử nghiệm của mình để làm như vậy. Bạn càng chế giễu, bạn càng phải thay đổi các bài kiểm tra của mình khi bạn tái cấu trúc. Nếu bạn xem xét phương pháp đơn vị, thì bạn phải thay đổi các bài kiểm tra của mình mỗi lần bạn cấu trúc lại. Và nếu bạn coi lớp là đơn vị, thì bạn phải thay đổi các bài kiểm tra của mình mỗi lần bạn muốn chia một lớp thành nhiều lớp. Khi bạn phải cấu trúc lại các bài kiểm tra của mình để cấu trúc lại mã của mình, điều đó khiến mọi người chọn không cấu trúc lại mã của họ, đây là điều tồi tệ nhất có thể xảy ra với một dự án. Điều cần thiết là bạn có thể chia một lớp thành nhiều lớp mà không cần phải cấu trúc lại các bài kiểm tra của mình, hoặc bạn sẽ kết thúc với các lớp spaghetti 500 dòng quá khổ. Nếu bạn đang coi các phương thức hoặc các lớp là các đơn vị của mình bằng kiểm thử đơn vị, thì có lẽ bạn không thực hiện Lập trình hướng đối tượng mà là một số loại lập trình chức năng đột biến với các đối tượng.

Cô lập mã của bạn cho một bài kiểm tra đơn vị không có nghĩa là bạn chế nhạo mọi thứ bên ngoài nó. Nếu đúng như vậy, bạn sẽ phải chế giễu lớp Toán của ngôn ngữ của bạn và hoàn toàn không ai nghĩ đó là một ý tưởng hay. Phụ thuộc bên trong không nên được đối xử khác với phụ thuộc bên ngoài. Bạn tin tưởng rằng họ được kiểm tra tốt và làm việc như họ được yêu cầu. Sự khác biệt thực sự duy nhất là nếu các phần phụ thuộc bên trong của bạn phá vỡ các mô-đun của bạn, bạn có thể dừng những gì bạn đang làm để sửa nó thay vì phải đăng một vấn đề trên GitHub và đào sâu vào một cơ sở mã mà bạn không hiểu để sửa nó hoặc hy vọng điều tốt nhất

Cô lập mã của bạn chỉ có nghĩa là bạn đối xử với các phụ thuộc bên trong của mình như hộp đen và không kiểm tra những thứ đang xảy ra bên trong chúng. Nếu bạn có Mô-đun B chấp nhận đầu vào 1, 2 hoặc 3 và bạn có Mô-đun A, gọi nó, bạn không có các thử nghiệm cho Mô-đun A thực hiện từng tùy chọn đó, bạn chỉ cần chọn một và sử dụng tùy chọn đó. Điều đó có nghĩa là các bài kiểm tra của bạn cho Mô-đun A nên kiểm tra các cách khác nhau mà bạn đối xử với các phản hồi từ Mô-đun B, chứ không phải những điều bạn truyền vào nó.

Vì vậy, nếu bộ điều khiển của bạn chuyển một đối tượng phức tạp sang một phụ thuộc và phụ thuộc đó thực hiện một số điều có thể, có thể lưu nó vào cơ sở dữ liệu và có thể trả về nhiều lỗi khác nhau, nhưng tất cả bộ điều khiển của bạn thực sự chỉ đơn giản là kiểm tra xem nó có trả về không có lỗi hay không và chuyển thông tin đó cùng, sau đó tất cả những gì bạn kiểm tra trong bộ điều khiển của mình là một thử nghiệm nếu nó trả về lỗi và chuyển nó cùng và một thử nghiệm nếu nó không trả về lỗi. Bạn không kiểm tra xem một cái gì đó đã được lưu trong cơ sở dữ liệu hay lỗi đó là gì, bởi vì đó sẽ là một thử nghiệm tích hợp. Bạn không phải chế giễu sự phụ thuộc để làm điều này. Bạn đã cô lập mã.

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.