Phải làm gì khi các bài kiểm tra TDD tiết lộ chức năng mới cần thiết mà cũng cần các bài kiểm tra?


13

Bạn sẽ làm gì khi bạn đang viết một bài kiểm tra và bạn đạt đến điểm cần thực hiện bài kiểm tra và bạn nhận ra rằng bạn cần một phần chức năng bổ sung cần được tách thành chức năng của chính nó? Chức năng mới đó cũng cần phải được kiểm tra, nhưng chu trình TDD nói rằng làm cho một thử nghiệm thất bại, làm cho nó vượt qua sau đó tái cấu trúc. Nếu tôi đang ở bước mà tôi đang cố gắng vượt qua bài kiểm tra của mình, tôi không cần phải tắt và bắt đầu một bài kiểm tra thất bại khác để kiểm tra chức năng mới mà tôi cần thực hiện.

Ví dụ: tôi đang viết một lớp điểm có chức năng WillCollideWith ( LineSegment ) :

public class Point {
    // Point data and constructor ...

    public bool CollidesWithLine(LineSegment lineSegment) {
        Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
                                               Position.Y + Velocity.Y);
        LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
        if (lineSegment.Intersects(pointPath)) return true;
        return false;
    }
}

Tôi đã viết một bài kiểm tra cho CollidesWithLine khi tôi nhận ra rằng tôi sẽ cần một hàm LineSegment.Intersects ( LineSegment ) . Nhưng, tôi có nên dừng những gì tôi đang làm trong chu kỳ thử nghiệm của mình để tạo ra chức năng mới này không? Điều đó dường như phá vỡ nguyên tắc "Đỏ, Xanh lục, Tái cấu trúc".

Tôi có nên chỉ cần viết mã mà phát hiện rằng lineSegments Intersect bên trong CollidesWithLine chức năng và cấu trúc lại nó sau khi nó đang làm việc? Điều đó sẽ hoạt động trong trường hợp này vì tôi có thể truy cập dữ liệu từ LineSegment , nhưng trong trường hợp loại dữ liệu đó là riêng tư thì sao?

Câu trả lời:


14

Chỉ cần nhận xét bài kiểm tra của bạn và mã gần đây (hoặc bỏ vào ngăn) để thực tế bạn đã quay ngược đồng hồ về đầu chu kỳ. Sau đó bắt đầu với LineSegment.Intersects(LineSegment)test / code / refactor. Khi đã xong, bỏ ghi chú kiểm tra / mã trước đó của bạn (hoặc lấy từ stash) và giữ theo chu kỳ.


Làm thế nào là khác nhau sau đó chỉ cần bỏ qua nó và trở lại với nó sau?
Joshua Harris

1
chỉ là chi tiết nhỏ: không có thêm kiểm tra "bỏ qua cho tôi" trong các báo cáo và nếu bạn sử dụng stash, mã không thể phân biệt được với trường hợp 'sạch'.
Javier

Một stash là gì? Điều đó giống như kiểm soát phiên bản?
Joshua Harris

1
một số VCS triển khai nó như một tính năng (ít nhất là Git và Fossil). Nó cho phép bạn loại bỏ một thay đổi nhưng lưu nó để áp dụng lại một thời gian sau. Không khó để làm thủ công, chỉ cần lưu một khác biệt và trở lại trạng thái cuối cùng. Sau đó, bạn áp dụng lại diff và tiếp tục.
Javier

6

Trên chu trình TDD:

Trong giai đoạn "thực hiện kiểm tra vượt qua", bạn có nghĩa vụ phải viết cách thực hiện đơn giản nhất sẽ thực hiện kiểm tra vượt qua . Để vượt qua bài kiểm tra của bạn, bạn đã quyết định tạo một cộng tác viên mới để xử lý logic còn thiếu vì có thể quá nhiều công việc để đưa vào lớp điểm của bạn để vượt qua bài kiểm tra của bạn. Đó là vấn đề nằm ở đâu. Tôi cho rằng bài kiểm tra mà bạn buộc phải vượt qua là một bước tiến quá lớn . Vì vậy, tôi nghĩ rằng vấn đề nằm ở chính bài kiểm tra của bạn, bạn nên xóa / nhận xét bài kiểm tra đó và tìm ra một bài kiểm tra đơn giản hơn cho phép bạn thực hiện một bước bé mà không cần giới thiệu phần LineSegment.Intersects (LineSegment). Một bạn có bài kiểm tra đó, bạn có thể cấu trúc lạimã của bạn (Ở đây bạn sẽ áp dụng nguyên tắc SRP) bằng cách di chuyển logic mới này vào phương thức LineSegment.Intersects (LineSegment). Các bài kiểm tra của bạn vẫn sẽ vượt qua vì bạn sẽ không thay đổi bất kỳ hành vi nào mà chỉ cần di chuyển một số mã xung quanh.

Về giải pháp thiết kế hiện tại của bạn

Nhưng với tôi, bạn có một vấn đề thiết kế sâu sắc hơn ở đây là bạn đang vi phạm Nguyên tắc Trách nhiệm duy nhất . Vai trò của Điểm là .... là một điểm, đó là tất cả. Không có sự thông minh trong việc trở thành một điểm, đó chỉ là giá trị x và y. Điểm là loại giá trị . Đây là điều tương tự cho Phân đoạn, phân đoạn là loại giá trị bao gồm hai điểm. Ví dụ, chúng có thể chứa một chút "thông minh" để tính toán độ dài dựa trên vị trí điểm của chúng. Nhưng kia là nó.

Bây giờ quyết định nếu một điểm và một phân khúc đang va chạm, là toàn bộ trách nhiệm. Và chắc chắn là quá nhiều công việc cho một điểm hoặc phân khúc để tự xử lý. Nó không thể thuộc về lớp Điểm, vì nếu không, Điểm sẽ biết về Phân đoạn. Và nó không thể thuộc về Phân đoạn vì Phân khúc đã có trách nhiệm chăm sóc các điểm trong phân khúc và cũng có thể tính toán độ dài của chính phân khúc.

Vì vậy, trách nhiệm này phải được sở hữu bởi một lớp khác, ví dụ như "PointSegmentCollisionDetector" sẽ có một phương thức như:

bool AreInCollision (Điểm p, Phân đoạn s)

Và đó là thứ mà bạn sẽ kiểm tra riêng biệt từ Điểm và Phân đoạn.

Điều tuyệt vời với thiết kế đó là bây giờ bạn có thể thực hiện các trình phát hiện va chạm khác nhau. Vì vậy, thật dễ dàng để lấy điểm chuẩn cho công cụ trò chơi của bạn (tôi giả sử bạn đang viết một trò chơi: p) bằng cách chuyển đổi phương thức phát hiện va chạm của bạn khi chạy. Hoặc để thực hiện một số kiểm tra / thử nghiệm trực quan trong thời gian chạy giữa các chiến lược phát hiện va chạm khác nhau.

Hiện tại, bằng cách đưa logic này vào lớp điểm của bạn, bạn đang khóa mọi thứ và đẩy quá nhiều trách nhiệm lên lớp Point.

Hy vọng nó có ý nghĩa,


Bạn nói đúng rằng tôi đã cố gắng kiểm tra sự thay đổi quá lớn và tôi nghĩ rằng bạn đúng khi tách nó ra khỏi một lớp va chạm, nhưng điều đó khiến tôi hỏi một câu hỏi hoàn toàn mới mà bạn có thể giúp tôi: Tôi có nên sử dụng một giao diện khi các phương thức chỉ tương tự nhau? .
Joshua Harris

2

Cách dễ nhất để thực hiện theo kiểu TDD là trích xuất một giao diện cho LineSegment và thay đổi tham số phương thức của bạn để có trong giao diện. Sau đó, bạn có thể mô phỏng phân đoạn dòng đầu vào và mã / kiểm tra phương thức Intersect một cách độc lập.


1
Tôi biết rằng đó là phương pháp TDD mà tôi nghe nhiều nhất, nhưng ILineSegment không có ý nghĩa. Đó là một điều để giao diện với tài nguyên bên ngoài hoặc thứ gì đó có thể có ở nhiều dạng, nhưng tôi không thể thấy một lý do nào đó tôi sẽ gắn bất kỳ chức năng nào với bất kỳ phân đoạn nào khác ngoài phân đoạn dòng.
Joshua Harris

0

Với jUnit4, bạn có thể sử dụng @Ignorechú thích cho các bài kiểm tra bạn muốn hoãn lại.

Thêm chú thích cho mỗi phương thức bạn muốn hoãn lại và tiếp tục viết bài kiểm tra cho các chức năng cần thiết. Vòng lại để tái cấu trúc các trường hợp thử nghiệm cũ hơn sau này.

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.