Việc tạo các đối tượng mà bạn nghĩ rằng bạn sẽ cần ok trong thử nghiệm đầu tiên trong TDD


15

Tôi khá mới với TDD và tôi gặp khó khăn khi tạo thử nghiệm đầu tiên của mình khi nó xuất hiện trước bất kỳ mã triển khai nào. Không có bất kỳ khuôn khổ nào đối với mã triển khai, tôi có thể tự do viết bài kiểm tra đầu tiên của mình theo ý muốn nhưng dường như nó luôn bị vấy bẩn bởi cách suy nghĩ về Java / OO của tôi về vấn đề này.

Ví dụ: trong Github ConwaysGameOfLifeExample thử nghiệm đầu tiên tôi đã viết (rule1_zeroNeighbours) Tôi đã bắt đầu bằng cách tạo một đối tượng GameOfLife chưa được triển khai; được gọi là một phương thức thiết lập không tồn tại, một phương thức bước không tồn tại, một phương thức get không tồn tại và sau đó sử dụng một xác nhận.

Các bài kiểm tra đã phát triển khi tôi viết thêm các bài kiểm tra và tái cấu trúc, nhưng ban đầu nó trông giống như thế này:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

Điều này cảm thấy kỳ lạ khi tôi buộc thiết kế thực hiện dựa trên cách tôi đã quyết định ở giai đoạn đầu này để viết bài kiểm tra đầu tiên này.

Theo cách mà bạn hiểu TDD thì có ổn không? Tôi dường như tuân theo các nguyên tắc TDD / XP trong đó các thử nghiệm và triển khai của tôi đã phát triển theo thời gian với tái cấu trúc, và vì vậy nếu thiết kế ban đầu này đã chứng minh rằng nó vô dụng, nó sẽ mở ra để thay đổi, nhưng có vẻ như tôi đang buộc phải hướng đến giải pháp bằng cách bắt đầu theo cách này.

Mọi người sử dụng TDD như thế nào? Tôi có thể đã trải qua nhiều lần tái cấu trúc hơn bằng cách bắt đầu không có đối tượng GameOfLife, chỉ có các phương thức nguyên thủy và tĩnh nhưng điều đó dường như quá giả tạo.


5
TDD thay thế không lập kế hoạch cẩn thận cũng như lựa chọn mẫu thiết kế cẩn thận. Điều đó nói rằng, trước khi bạn viết bất kỳ triển khai nào để đáp ứng một vài dòng thử nghiệm đầu tiên là thời gian tốt hơn nhiều so với sau khi bạn phụ thuộc bằng văn bản để nhận ra rằng kế hoạch của bạn là ngu ngốc, rằng bạn đã chọn sai mẫu, hoặc thậm chí chỉ rằng thật khó xử hoặc khó hiểu khi gọi một lớp theo cách mà bài kiểm tra của bạn yêu cầu.
Svidgen 17/03/2015

Câu trả lời:


9

Điều này cảm thấy kỳ lạ khi tôi buộc thiết kế thực hiện dựa trên cách tôi đã quyết định ở giai đoạn đầu này để viết bài kiểm tra đầu tiên này.

Tôi nghĩ rằng đây là điểm mấu chốt trong câu hỏi của bạn, việc này có được mong muốn hay không phụ thuộc vào việc bạn có nghiêng về ý tưởng của codeninja hay không, sau đó sử dụng TDD để điền vào thực hiện, hoặc ý tưởng của Durron rằng các bài kiểm tra nên tham gia lái xe ra thiết kế cũng như thực hiện.

Tôi nghĩ bạn thích cái nào hơn (hoặc nơi bạn rơi vào giữa) là thứ bạn cần tự khám phá như một sở thích. Thật hữu ích để hiểu những ưu và nhược điểm của từng phương pháp. Có lẽ có rất nhiều, nhưng tôi muốn nói những cái chính là:

Thiết kế trả trước chuyên nghiệp

  • Tuy nhiên, một quy trình tốt là thiết kế lái xe, nó không hoàn hảo. TDing mà không có đích đến cụ thể trong tâm trí đôi khi có thể chạy xuống ngõ cụt, và ít nhất một số trong những ngõ cụt này có thể tránh được với một chút suy nghĩ thẳng thắn về nơi bạn muốn kết thúc. Bài viết trên blog này đưa ra lập luận này bằng cách sử dụng ví dụ về Chữ số La Mã kata và có một triển khai cuối cùng khá hay để hiển thị cho nó.

Thiết kế lái thử xe chuyên nghiệp

  • Bằng cách xây dựng triển khai của bạn xung quanh một khách hàng mã của bạn (các thử nghiệm của bạn), bạn sẽ có được sự tuân thủ YAGNI khá nhiều miễn phí, miễn là bạn không bắt đầu viết các trường hợp thử nghiệm không cần thiết. Tổng quát hơn, bạn nhận được một API được thiết kế xung quanh việc sử dụng bởi người tiêu dùng, cuối cùng là thứ bạn muốn.

  • Ý tưởng vẽ một loạt các sơ đồ UML trước khi viết bất kỳ mã nào sau đó chỉ cần điền vào các khoảng trống là tốt, nhưng hiếm khi thực tế. Trong Code Complete của Steve McConnell, thiết kế được mô tả nổi tiếng là một "vấn đề xấu" - một vấn đề bạn không thể hiểu đầy đủ mà ít nhất là trước tiên giải quyết nó một phần. Kết hợp điều này với thực tế là chính vấn đề tiềm ẩn có thể thay đổi thông qua việc thay đổi các yêu cầu và mô hình thiết kế này bắt đầu cảm thấy hơi vô vọng. Lái xe thử nghiệm cho phép bạn chỉ cần cắn một khối công việc tại một thời điểm - trong thiết kế, không chỉ thực hiện - và biết rằng ít nhất là trong vòng đời chuyển từ màu đỏ sang màu xanh lá cây, nhiệm vụ đó vẫn sẽ được cập nhật và phù hợp.

Đối với ví dụ cụ thể của bạn, như Durron nói, nếu bạn đã thực hiện phương pháp điều khiển thiết kế bằng cách viết thử nghiệm đơn giản nhất, sử dụng giao diện tối thiểu có thể, bạn có thể sẽ bắt đầu với giao diện đơn giản hơn so với đoạn mã trong đoạn mã của bạn .


Các liên kết là một Ben đọc rất tốt. Cảm ơn đã chia sẻ điều đó.
RubberDuck

1
@RubberDuck Bạn được chào đón! Tôi thực sự không đồng ý với nó, nhưng tôi nghĩ rằng đó là một công việc tuyệt vời để tranh luận về quan điểm đó.
Ben Aaronson

1
Tôi cũng không chắc là tôi làm, nhưng nó làm cho trường hợp của nó tốt. Tôi nghĩ rằng câu trả lời đúng là ở đâu đó ở giữa. Bạn đã có một kế hoạch, nhưng chắc chắn nếu các bài kiểm tra của bạn cảm thấy lúng túng, thiết kế lại. Dù sao ... ++ tốt cho thấy đậu cũ.
RubberDuck

17

Để viết bài kiểm tra ở nơi đầu tiên, bạn phải thiết kế API mà sau đó bạn sẽ triển khai. Bạn đã bắt đầu sai khi viết bài kiểm tra của mình để tạo toàn bộ GameOfLife đối tượng và sử dụng điều đó để thực hiện bài kiểm tra của bạn.

Từ thử nghiệm đơn vị thực hành với JUnit và Mockito :

Lúc đầu, bạn có thể cảm thấy lúng túng khi viết một cái gì đó thậm chí không có ở đó. Nó đòi hỏi một sự thay đổi nhỏ đối với thói quen mã hóa của bạn, nhưng sau một thời gian bạn sẽ thấy đó là một cơ hội thiết kế tuyệt vời. Bằng cách viết bài kiểm tra trước, bạn có cơ hội tạo API thuận tiện cho khách hàng sử dụng. Thử nghiệm của bạn là khách hàng đầu tiên của API mới được sinh ra. Đây là những gì TDD thực sự hướng tới: thiết kế API.

Thử nghiệm của bạn không thực hiện nhiều nỗ lực để thiết kế API. Bạn đã thiết lập một hệ thống trạng thái trong đó tất cả các chức năng được chứa trong lớp bên ngoài GameOfLife.

Nếu tôi viết ứng dụng này, thay vào đó tôi sẽ nghĩ về những phần tôi muốn xây dựng. Ví dụ, tôi có thể tạo một Celllớp, viết các bài kiểm tra cho điều đó, trước khi chuyển sang ứng dụng lớn hơn. Tôi chắc chắn sẽ tạo một lớp cho cấu trúc dữ liệu "vô hạn theo mọi hướng" được yêu cầu để thực hiện đúng cách Conway và kiểm tra điều đó. Một khi tất cả điều đó đã được thực hiện, tôi sẽ nghĩ về việc viết toàn bộ lớp có một mainphương thức và vv.

Thật dễ dàng để vượt qua bước "viết một bài kiểm tra thất bại". Nhưng viết bài kiểm tra thất bại hoạt động theo cách bạn muốn là cốt lõi của TDD.


1
Xét một tế bào sẽ chỉ là một wrapper cho một boolean, thiết kế chắc chắn sẽ là tồi tệ hơn cho hiệu suất. Trừ khi nó cần được mở rộng trong tương lai sang các máy tự động di động khác có nhiều hơn hai trạng thái?
user253751 17/03/2015

2
@immibis Đó là ngụy biện về chi tiết. Bạn có thể bắt đầu với một lớp đại diện cho bộ sưu tập các ô. Bạn cũng có thể di chuyển / hợp nhất lớp ô và các kiểm tra của nó với một lớp đại diện cho một tập hợp các ô sau này nếu hiệu suất là một vấn đề.
Eric

@immibis Số lượng hàng xóm sống có thể được lưu trữ vì lý do hiệu suất. Số lượng bọ ve của tế bào còn sống, vì lý do tô màu ..
Blorgbeard

@immibis tối ưu hóa sớm là xấu xa ... Hơn nữa, tránh nỗi ám ảnh nguyên thủy là cách tuyệt vời để viết mã chất lượng tốt, bất kể có bao nhiêu trạng thái hỗ trợ. Hãy xem tại: jamesshore.com/Blog/PrimitiveObsession.html
Paul

0

Có trường phái khác nhau về suy nghĩ này.

Một số người nói: kiểm tra không biên dịch là lỗi - hãy sửa ghi mã sản xuất có sẵn nhỏ nhất.

Một số người nói: bạn có thể viết bài kiểm tra trước nếu kiểm tra xem nó có hút (hoặc không) không, sau đó tạo các lớp / phương thức bị thiếu

Với cách tiếp cận đầu tiên, bạn thực sự đang trong một chu kỳ tái cấu trúc màu đỏ-xanh. Với thứ hai, bạn có một cái nhìn tổng quan rộng hơn một chút về những gì bạn muốn đạt được.

Tùy thuộc vào bạn để chọn cách bạn làm việc. IMHO cả hai cách tiếp cận đều hợp lệ.


0

Ngay cả khi tôi thực hiện một cái gì đó theo cách "hack nó cùng nhau", tôi vẫn nghĩ đến các lớp và các bước sẽ tham gia vào toàn bộ chương trình. Vì vậy, bạn đã nghĩ đến điều này và viết những suy nghĩ thiết kế này xuống dưới dạng thử nghiệm trước tiên - thật tuyệt!

Bây giờ tiếp tục lặp lại thông qua cả hai thực hiện để hoàn thành thử nghiệm ban đầu này, và sau đó thêm nhiều thử nghiệm để cải thiện và mở rộng thiết kế.

Những gì có thể giúp bạn là sử dụng Cucumber hoặc tương tự để viết bài kiểm tra của bạn.


0

Trước khi bạn bắt đầu viết bài kiểm tra của mình, bạn nên suy nghĩ về cách thiết kế hệ thống của mình. Bạn nên dành một lượng thời gian đáng kể trong giai đoạn thiết kế của bạn. Nếu bạn đã làm điều đó, bạn sẽ không nhận được sự nhầm lẫn này trên TDD.

TDD chỉ là một liên kết tiếp cận phát triển : TDD
1. Thêm một bài kiểm tra
2. Chạy tất cả các bài kiểm tra và xem nếu bài kiểm tra mới thất bại
3. Viết một số mã
4. Chạy thử nghiệm
5. Mã tái cấu trúc
6. Lặp lại

TDD giúp bạn bao quát tất cả các tính năng cần thiết những gì bạn đã lên kế hoạch trước khi bắt đầu phát triển phần mềm của mình. liên kết: Lợi ích


0

Tôi không thích các bài kiểm tra cấp hệ thống được viết bằng java hoặc C # vì lý do đó. Hãy xem SpecFlow cho c # hoặc một trong các khung kiểm tra dựa trên Cucumber cho java (có thể là JBehave). Sau đó, các bài kiểm tra của bạn có thể trông giống như thế này.

nhập mô tả hình ảnh ở đây

Và bạn có thể thay đổi thiết kế đối tượng của mình mà không phải thay đổi tất cả các kiểm tra hệ thống.

(Các bài kiểm tra đơn vị bình thường của Nhật Bản rất tuyệt khi kiểm tra các lớp đơn.)

Sự khác biệt giữa các khung công tác BDD cho Java là gì?

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.