Tại sao kiểm thử đơn vị khó hơn trong lập trình Hướng đối tượng so với lập trình Hàm?


9

Tôi đang trải qua loạt bài này . Tác giả đề cập rằng vì trạng thái được duy trì trong lập trình hướng đối tượng, nên việc viết các bài kiểm tra đơn vị sẽ khó hơn. Ông cũng nói rằng vì lập trình chức năng không duy trì trạng thái (không phải luôn luôn), nên việc viết bài kiểm tra đơn vị sẽ dễ dàng hơn. Tôi không thấy bất kỳ ví dụ nào chứng minh vấn đề này. Nếu điều này là đúng, bạn có thể cung cấp cho tôi một ví dụ so sánh kiểm thử đơn vị trong lập trình chức năng và hướng đối tượng không?


2
Tôi không đồng ý với ý kiến ​​rằng lập trình chức năng dễ kiểm tra hơn. Mặc dù chức năng không trạng thái thực sự dễ kiểm tra hơn đối tượng trạng thái, việc chuyển đổi đối tượng trạng thái thành chức năng không trạng thái dẫn đến chức năng statele phức tạp hơn nhiều, có thể với một số mô phỏng trạng thái phức tạp, như đơn nguyên.
Euphoric

1
@Euphoric Một biểu hiện chức năng của một giải pháp cho một vấn đề có thể sẽ khá khác so với việc chỉ thay thế trạng thái bằng một Stateđơn nguyên (mô phỏng trạng thái). Bạn một phần có một điểm - mã được viết theo kiểu đó không có tương tác không cục bộ - nhưng với mục đích kiểm tra đơn vị, bạn vẫn có thể vượt qua một cách rõ ràng trong trạng thái, bạn không phải chế giễu bất cứ điều gì. Mặt khác, với monads thích STIO(trong đó có thành sự thật, nhà nước có thể thay đổi tiềm ẩn), bạn chỉ đơn giản là đang làm lập trình trạng thái, và đơn vị thử nghiệm nó sẽ không dễ dàng hơn trong bất kỳ ngôn ngữ nào khác.
Derek Elkins rời SE

10
Không có gì trong định nghĩa của OO đòi hỏi trạng thái có thể thay đổi. Hoàn toàn có thể (và được thực hiện rộng rãi) để viết mã OO thuần túy, minh bạch.
Jörg W Mittag

1
Tất cả các kỹ thuật liên quan đến lập trình chức năng đều có thể áp dụng hoàn hảo trong lập trình hướng đối tượng. Các chương trình OO được thiết kế tốt thường sử dụng các kỹ thuật chức năng ở các cấp thấp hơn, ở mức tối thiểu.
Frank Hileman

1
@Fabio Nếu đó là trường hợp, nó có ý nghĩa hơn để sử dụng lập trình FP. OO không yêu cầu trạng thái có thể thay đổi, nhưng khả năng biến đổi tiềm ẩn trong ví dụ tôi vừa đưa ra cho bạn. Tương tự với f # - bạn có thể đánh dấu một biến có thể thay đổi để phù hợp với nhu cầu oo. Nhưng nếu bạn định sử dụng quá nhiều biến có thể thay đổi, bạn cũng có thể sử dụng c #.

Câu trả lời:


17

Tôi muốn phân biệt giữa hai cách khác nhau để tiếp cận lập trình hướng đối tượng

  1. Trình mô phỏng: các đối tượng của bạn đại diện cho các đối tượng miền thực, bạn đã lập trình chúng để xử lý bất kỳ chức năng nào liên quan đến miền đó. Các đối tượng được lập trình theo cách này có thể có rất nhiều trạng thái có thể thay đổi và các cộng tác viên ẩn được sử dụng để thực hiện chức năng này.
  2. Bản ghi + chức năng: đối tượng của bạn chỉ là gói dữ liệu và chức năng hoạt động trên dữ liệu đó. Các đối tượng được lập trình theo cách này có nhiều khả năng trở thành bất biến, đảm nhận ít trách nhiệm hơn và cho phép một người tiêm chích cộng tác viên.

Một nguyên tắc nhỏ là một đối tượng được lập trình theo cách thứ nhất sẽ có nhiều phương thức hơn và nhiều voidphương thức hơn so với cách thứ hai. Giả sử chúng tôi sẽ viết một chuyến bay giả lập và đang thiết kế một lớp máy bay. Chúng tôi sẽ có một cái gì đó như:

class Plane {
    void accelerate();
    void deccelerate();
    void toggleRightFlaps();
    void toggleLeftFlaps();
    void turnRudderRight();
    void turnRudderLeft();
    void deployLandingGear();
    void liftLandingGear();
    // etc.
    void tick() throws PlaneCrashedException;
}

Điều này có thể là một chút cực đoan hơn một người có thể gặp phải, nhưng nó được điểm. Nếu bạn muốn thực hiện loại giao diện này, bạn phải giữ bên trong đối tượng:

  1. Tất cả các thông tin về trạng thái của thiết bị máy bay.
  2. Tất cả các thông tin về vận tốc / gia tốc của máy bay.
  3. Tốc độ làm mới mô phỏng của chúng tôi (để thực hiện đánh dấu).
  4. Chi tiết đầy đủ về mô hình 3d của mô phỏng và vật lý của nó để thực hiện đánh dấu.

Viết một bài kiểm tra đơn vị cho một đối tượng được viết trong chế độ là rất khó vì:

  1. Bạn cần cung cấp tất cả các bit dữ liệu và cộng tác viên khác nhau mà đối tượng này cần khi bắt đầu thử nghiệm (việc tạo ra chúng có thể rất tẻ nhạt).
  2. Khi bạn muốn kiểm tra một phương pháp bạn gặp phải hai vấn đề: a) giao diện thường không tiết lộ đủ dữ liệu để bạn kiểm tra (vì vậy cuối cùng bạn phải sử dụng giả / phản ánh để thậm chí xác minh kỳ vọng) b) có nhiều thành phần bị ràng buộc thành một cái mà bạn phải xác minh hành vi trong mỗi bài kiểm tra.

Về cơ bản, bạn bắt đầu với một giao diện có vẻ hợp lý và giống như nó phù hợp với miền, nhưng tính độc đáo của mô phỏng đã khiến bạn tạo ra một đối tượng thực sự khó kiểm tra.

Tuy nhiên, bạn có thể tạo các đối tượng sẽ thực hiện cùng một mục đích. Bạn sẽ muốn hãm bạn Planethành các bit nhỏ hơn. Có một dấu PlaneParticlevết theo dõi các bit vật lý của mặt phẳng, vị trí, vận tốc, gia tốc, lăn, ngáp, v.v., v.v., phơi bày và cho phép người ta thao tác những thứ này. Sau đó, một PlanePartsđối tượng có thể theo dõi trạng thái của. Bạn sẽ gửi tick()đến một nơi hoàn toàn khác, giả sử có một PlanePhysicsđối tượng được tham số hóa, ví dụ, lực hấp dẫn, biết được PlaneParticlevà đưa ra một PlanePartscách mới để tạo ra một vật mới PlaneParticle. Tất cả điều này có thể là hoàn toàn bất biến, mặc dù nó không cần phải là một ví dụ.

Bây giờ bạn có những lợi thế về mặt kiểm tra:

  1. Mỗi thành phần riêng lẻ có ít việc phải làm và dễ thiết lập hơn.
  2. Bạn có thể kiểm tra các thành phần của bạn trong sự cô lập.
  3. Những đối tượng này có thể thoát khỏi việc phơi bày nội bộ của họ (đặc biệt là nếu chúng được tạo ra bất biến), vì vậy người ta không cần phải khéo léo để đo lường chúng.

Đây là mẹo: cách tiếp cận hướng đối tượng thứ hai mà tôi mô tả rất gần với lập trình chức năng. Có thể trong các chương trình chức năng thuần túy, hồ sơ và chức năng của bạn là riêng biệt và không bị ràng buộc với nhau trong các đối tượng, chắc chắn một chương trình chức năng sẽ đảm bảo rằng tất cả những điều này. Những gì tôi nghĩ thực sự làm cho thử nghiệm đơn vị dễ dàng là

  1. Đơn vị nhỏ (không gian nhà nước nhỏ).
  2. Chức năng với đầu vào tối thiểu (không có đầu vào ẩn).
  3. Chức năng với đầu ra tối thiểu.

Lập trình chức năng khuyến khích những điều này (nhưng người ta có thể viết các chương trình xấu trong bất kỳ mô hình nào) nhưng chúng có thể đạt được trong các chương trình hướng đối tượng. Và tôi sẽ nhấn mạnh thêm rằng lập trình chức năng và lập trình hướng đối tượng không tương thích.


2
Tôi không chắc đây là một sự phân biệt tốt. Ngay cả khi sử dụng phương pháp 1, nhiều người sẽ coi các kỹ thuật bạn mô tả cho phương pháp 2 là thực hành tiêu chuẩn. Hai cách tiếp cận không loại trừ lẫn nhau. Các bit của Mặt phẳng có thể được kết hợp với nhau để tạo ra sự trừu tượng lớn hơn và bây giờ bạn đang ở trong khu vực của cách tiếp cận 1. Tôi sẽ đi xa như nói cách tiếp cận 1, như bạn mô tả, không logic.
Frank Hileman

3
"Và tôi sẽ nhấn mạnh thêm rằng lập trình chức năng và lập trình hướng đối tượng không tương thích.": Một số người sẽ nói rằng lập trình hướng đối tượng chỉ được đặc trưng bởi công văn động. Vì vậy, bạn có thể viết theo kiểu OOP bắt buộc : các đối tượng có thể thay đổi + công văn động hoặc theo kiểu OOP chức năng : các đối tượng bất biến + công văn động.
Giorgio

3
Chia 1 đối tượng lớn thành các phần nhỏ hơn không phải là lập trình chức năng. Và giải pháp cho một đối tượng lớn là không tách logic khỏi dữ liệu và làm cho nó bị thiếu máu, đó là chức năng . Tôi có thể hiểu lầm toàn bộ của bạn tuy nhiên phần mặc dù.
Chris Wohlert
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.