Điều gì xảy ra với các thử nghiệm của phương thức khi phương thức đó trở thành riêng tư sau khi thiết kế lại trong TDD?


29

Giả sử tôi bắt đầu phát triển một trò chơi nhập vai với các nhân vật tấn công các nhân vật khác và những thứ đó.

Áp dụng TDD, tôi thực hiện một số trường hợp thử nghiệm để kiểm tra logic bên trong Character.receiveAttack(Int)phương thức. Một cái gì đó như thế này:

@Test
fun healthIsReducedWhenCharacterIsAttacked() {
    val c = Character(100) //arg is the health
    c.receiveAttack(50) //arg is the suffered attack damage
    assertThat(c.health, is(50));
}

Nói rằng tôi có 10 phương pháp thử nghiệm receiveAttackphương pháp. Bây giờ, tôi thêm một phương thức Character.attack(Character)(gọi receiveAttackphương thức đó) và sau một số chu kỳ TDD kiểm tra nó, tôi đưa ra quyết định: Character.receiveAttack(Int)nên private.

Điều gì xảy ra với 10 trường hợp thử nghiệm trước đó? Tôi có nên xóa chúng? Tôi có nên giữ phương pháp public(tôi không nghĩ vậy)?

Câu hỏi này không phải là về cách kiểm tra các phương thức riêng tư mà là cách xử lý chúng sau khi thiết kế lại khi áp dụng TDD



10
Nếu nó là riêng tư bạn không kiểm tra nó, nó dễ dàng. Hủy bỏ và thực hiện điệu nhảy refactor
kayess

6
Có lẽ tôi đang chống lại hạt gạo ở đây. Nhưng, tôi thường tránh các phương pháp riêng tư bằng mọi giá. Tôi thích nhiều bài kiểm tra hơn bài kiểm tra ít hơn. Tôi biết mọi người đang nghĩ gì "Cái gì, vì vậy bạn không bao giờ có bất kỳ loại chức năng nào bạn không muốn tiếp xúc với người tiêu dùng?". Vâng, tôi có rất nhiều điều tôi không muốn phơi bày. Thay vào đó, khi tôi có một phương thức riêng, thay vào đó tôi sẽ cấu trúc lại nó thành lớp riêng và sử dụng lớp đã nói từ lớp ban đầu. Lớp mới có thể được đánh dấu là internalhoặc ngôn ngữ của bạn tương đương với việc vẫn ngăn chặn nó bị lộ. Trong thực tế, câu trả lời của Kevin Cline là cách tiếp cận này.
dùng9993

3
@ user9993 bạn dường như có nó ngược. Nếu điều quan trọng đối với bạn là có nhiều bài kiểm tra hơn, cách duy nhất để đảm bảo rằng bạn không bỏ sót điều gì quan trọng là chạy phân tích bảo hiểm. Và đối với các công cụ bảo hiểm, nó hoàn toàn không thành vấn đề nếu phương thức này là riêng tư hoặc công khai hoặc bất cứ điều gì khác. Hy vọng rằng việc công khai công khai bằng cách nào đó sẽ bù đắp cho việc thiếu phân tích bảo hiểm mang lại cảm giác an toàn sai lầm mà tôi sợ
gnat

2
@gnat Nhưng tôi chưa bao giờ nói gì về việc "không có bảo hiểm"? Nhận xét của tôi về "Tôi thích nhiều bài kiểm tra hơn bài kiểm tra ít hơn" nên đã làm cho điều đó trở nên rõ ràng. Không chắc chắn chính xác những gì bạn đang nhận được, tất nhiên tôi cũng kiểm tra mã tôi đã trích xuất. Đó là toàn bộ vấn đề.
dùng9993

Câu trả lời:


52

Trong TDD, các bài kiểm tra đóng vai trò là tài liệu thực thi của thiết kế của bạn. Thiết kế của bạn đã thay đổi, vì vậy rõ ràng, tài liệu của bạn cũng phải!

Lưu ý rằng, trong TDD, cách duy nhất mà attackphương thức có thể xuất hiện, là kết quả của việc vượt qua bài kiểm tra thất bại. Có nghĩa là, attackđang được thử nghiệm bởi một số thử nghiệm khác. Điều đó có nghĩa là gián tiếp receiveAttack được bao phủ bởi attackcác thử nghiệm. Lý tưởng nhất, bất kỳ thay đổi nào receiveAttacksẽ phá vỡ ít nhất một trong attackcác thử nghiệm.

Và nếu không, thì có chức năng trong receiveAttackđó không còn cần thiết và không còn tồn tại!

Vì vậy, vì receiveAttackđã được kiểm tra thông qua attack, nên bạn có giữ bài kiểm tra của mình hay không. Nếu khung thử nghiệm của bạn giúp dễ dàng thử nghiệm các phương thức riêng tư và nếu bạn quyết định thử nghiệm các phương thức riêng tư, thì bạn có thể giữ chúng. Nhưng bạn cũng có thể xóa chúng mà không mất bảo hiểm kiểm tra và sự tự tin.


14
Đây là một câu trả lời hay, hãy lưu lại "Nếu khung kiểm tra của bạn giúp dễ dàng kiểm tra các phương thức riêng tư và nếu bạn quyết định thử nghiệm các phương thức riêng tư, thì bạn có thể giữ chúng." Phương pháp riêng là chi tiết thực hiện và không bao giờ nên được kiểm tra trực tiếp.
David Arno

20
@DavidArno: Tôi không đồng ý, bởi lý do đó không bao giờ được kiểm tra nội bộ của một mô-đun. Tuy nhiên, phần bên trong của một mô-đun có thể rất phức tạp và do đó có các bài kiểm tra đơn vị cho từng chức năng bên trong riêng lẻ có thể có giá trị. Kiểm thử đơn vị được sử dụng để kiểm tra các bất biến của một phần chức năng, nếu một phương thức riêng có bất biến (tiền điều kiện / hậu điều kiện) thì kiểm tra đơn vị có thể có giá trị.
Matthieu M.

8
" bởi lý do đó các phần bên trong của một mô-đun không bao giờ nên được kiểm tra ". Những nội bộ không bao giờ nên được kiểm tra trực tiếp . Tất cả các thử nghiệm chỉ nên kiểm tra API công khai. Nếu một yếu tố nội bộ không thể truy cập được thông qua API công khai, thì hãy xóa nó vì nó không làm gì cả.
David Arno

28
@DavidArno Theo logic đó, nếu bạn đang xây dựng một tệp thực thi (chứ không phải là thư viện) thì bạn sẽ không có bài kiểm tra đơn vị nào cả. - "Các lệnh gọi hàm không phải là một phần của API công khai! Chỉ có các đối số dòng lệnh! Nếu một chức năng bên trong của chương trình của bạn không thể truy cập được thông qua một đối số dòng lệnh, thì hãy xóa nó vì nó không làm gì cả." - Mặc dù các hàm riêng không phải là một phần của API công khai của lớp, chúng là một phần của API nội bộ của lớp. Và trong khi bạn không nhất thiết phải kiểm tra API nội bộ của một lớp, bạn có thể, sử dụng cùng một logic để kiểm tra API nội bộ của một tệp thực thi.
RM

7
@RM, nếu tôi tạo một tệp thực thi theo kiểu không mô-đun, thì tôi sẽ bị buộc phải chọn giữa các kiểm tra giòn của các phần bên trong, hoặc chỉ sử dụng các kiểm tra tích hợp bằng cách sử dụng I / O thực thi và thời gian chạy. Do đó theo logic thực tế của tôi, thay vì phiên bản ống hút của bạn, tôi sẽ tạo nó theo kiểu mô đun (ví dụ: thông qua một bộ thư viện). Các API công khai của các mô-đun đó sau đó có thể được kiểm tra theo cách không dễ vỡ.
David Arno

23

Nếu phương thức đủ phức tạp để cần thử nghiệm, thì nó sẽ được công khai trong một số lớp. Vì vậy, bạn tái cấu trúc từ:

public class X {
  private int complexity(...) {
    ...
  }
  public void somethingElse() {
    int c = complexity(...);
  }
}

đến:

public class Complexity {
  public int calculate(...) {
    ...
  }
}

public class X {
  private Complexity complexity;
  public X(Complexity complexity) { // dependency injection happiness
    this.complexity = complexity;
  }

  public void something() {
    int c = complexity.calculate(...);
  }
}

Di chuyển bài kiểm tra hiện tại về X.complexity sang ComplexityTest. Sau đó nhắn tin cho X.s Something bằng cách chế giễu Độ phức tạp.

Theo kinh nghiệm của tôi, tái cấu trúc đối với các lớp nhỏ hơn và các phương thức ngắn hơn mang lại lợi ích rất lớn. Chúng dễ hiểu hơn, dễ kiểm tra hơn và cuối cùng được sử dụng lại nhiều hơn mong đợi.


Câu trả lời của bạn giải thích rõ ràng hơn nhiều về ý tưởng mà tôi đang cố gắng giải thích trong nhận xét của mình về câu hỏi của OP. Câu trả lời tốt đẹp.
dùng9993

3
Cảm ơn câu trả lời của bạn. Trên thực tế, phương thức receiveAttack khá đơn giản ( this.health = this.health - attackDamage). Có lẽ trích xuất nó cho một lớp khác là một giải pháp áp đảo, cho thời điểm này.
Héctor

1
Đây chắc chắn là cách quá mức cần thiết cho OP - anh ấy muốn lái xe đến cửa hàng, không bay lên mặt trăng - nhưng là một giải pháp tốt trong trường hợp chung.

Nếu chức năng đơn giản như vậy, có thể nó áp đảo rằng nó thậm chí được định nghĩa là một chức năng ở vị trí đầu tiên.
David K

1
nó có thể là quá mức cần thiết ngày hôm nay nhưng trong thời gian 6 tháng khi có rất nhiều thay đổi đối với mã này, các lợi ích sẽ rõ ràng. Và trong bất kỳ IDE tốt nào ngày nay chắc chắn việc trích xuất một số mã sang một lớp riêng biệt sẽ là một vài tổ hợp phím hầu như không phải là một giải pháp được thiết kế quá mức khi xét đến việc trong nhị phân thời gian chạy, tất cả sẽ sôi sục như cũ.
Stephen Byrne

6

Nói rằng tôi có 10 phương thức kiểm tra phương thức receiveAttack. Bây giờ, tôi thêm một phương thức Character.attack (Character) (gọi phương thức receiveAttack) và sau một số chu kỳ TDD kiểm tra nó, tôi đưa ra quyết định: Character.receiveAttack (Int) sẽ ở chế độ riêng tư.

Một điều cần lưu ý ở đây là quyết định bạn đưa ra là xóa phương thức khỏi API . Các biện pháp tương thích ngược sẽ đề nghị

  1. Nếu bạn không cần xóa nó, hãy để nó trong API
  2. Nếu bạn không cần phải loại bỏ nó chưa , sau đó đánh dấu nó như là bị phản đối và nếu tài liệu có thể khi kết thúc cuộc sống sẽ xảy ra
  3. Nếu bạn cần phải loại bỏ nó, thì bạn có một thay đổi lớn về phiên bản

Các thử nghiệm được xóa / hoặc thay thế khi API của bạn không còn hỗ trợ phương thức. Tại thời điểm đó, phương thức riêng là một chi tiết triển khai mà bạn sẽ có thể cấu trúc lại.

Tại thời điểm đó, bạn quay lại câu hỏi tiêu chuẩn về việc liệu bộ thử nghiệm của bạn có nên truy cập trực tiếp vào các triển khai hay không, thay vì tương tác hoàn toàn thông qua API công khai. Một phương thức riêng tư là thứ mà chúng ta sẽ có thể thay thế mà không có bộ thử nghiệm cản trở . Vì vậy, tôi hy vọng cặp vợ chồng thử nghiệm sẽ biến mất - hoặc đã nghỉ hưu hoặc chuyển sang thực hiện thành một thành phần có thể kiểm tra riêng.


3
Khấu hao không phải lúc nào cũng là một mối quan tâm. Từ câu hỏi: "Hãy nói rằng tôi bắt đầu phát triển ..." nếu phần mềm chưa được phát hành, sự phản đối không phải là vấn đề. Hơn nữa: "một trò chơi nhập vai" ngụ ý đây không phải là một thư viện có thể tái sử dụng, mà là phần mềm nhị phân nhắm vào người dùng cuối. Mặc dù một số phần mềm người dùng cuối có API công khai (ví dụ: MS Office), hầu hết không có. Ngay cả những phần mềm mà không có một API công cộng chỉ có một phần của nó tiếp xúc với plugins, kịch bản (ví dụ như trò chơi với phần mở rộng LUA), hoặc các chức năng khác. Tuy nhiên, vẫn đáng để đưa ra ý tưởng cho trường hợp chung mà OP mô tả.
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.