Thử nghiệm vs Đừng lặp lại chính mình (DRY)


11

Tại sao việc lặp lại chính mình bằng cách viết bài kiểm tra rất được khuyến khích?

Dường như các bài kiểm tra về cơ bản thể hiện điều tương tự như mã, và do đó là một bản sao (về khái niệm, không phải là triển khai) của mã. Mục tiêu cuối cùng của DRY sẽ không bao gồm loại bỏ tất cả các mã kiểm tra?

Câu trả lời:


24

Tôi tin rằng đây là một quan niệm sai lầm theo bất kỳ cách nào tôi có thể nghĩ ra.

Mã kiểm tra kiểm tra mã sản xuất hoàn toàn không giống nhau. Tôi sẽ chứng minh bằng python:

def multiply(a, b):
    """Multiply ``a`` by ``b``"""
    return a*b

Sau đó, một bài kiểm tra đơn giản sẽ là:

def test_multiply():
    assert multiply(4, 5) == 20

Cả hai chức năng có một định nghĩa tương tự nhưng cả hai đều làm những việc rất khác nhau. Không có mã trùng lặp ở đây. ;-)

Nó cũng xảy ra rằng mọi người viết các bài kiểm tra trùng lặp về cơ bản có một khẳng định cho mỗi chức năng kiểm tra. Đây là sự điên rồ và tôi đã thấy mọi người làm điều này. Đây thực hành xấu.

def test_multiply_1_and_3():
    """Assert that a multiplication of 1 and 3 is 3."""
    assert multiply(1, 3) == 3

def test_multiply_1_and_7():
    """Assert that a multiplication of 1 and 7 is 7."""
    assert multiply(1, 7) == 7

def test_multiply_3_and_4():
    """Assert that a multiplication of 3 and 4 is 12."""
    assert multiply(3, 4) == 12

Hãy tưởng tượng làm điều này cho hơn 1000 dòng mã hiệu quả. Thay vào đó, bạn kiểm tra trên cơ sở mỗi 'tính năng':

def test_multiply_positive():
    """Assert that positive numbers can be multiplied."""
    assert multiply(1, 3) == 3
    assert multiply(1, 7) == 7
    assert multiply(3, 4) == 12

def test_multiply_negative():
    """Assert that negative numbers can be multiplied."""
    assert multiply(1, -3) == -3
    assert multiply(-1, -7) == 7
    assert multiply(-3, 4) == -12

Bây giờ khi các tính năng được thêm / xóa, tôi chỉ phải xem xét thêm / xóa một chức năng kiểm tra.

Bạn có thể nhận thấy tôi đã không áp dụng forcác vòng lặp. Điều này là bởi vì lặp đi lặp lại một số điều là tốt. Khi tôi đã áp dụng các vòng lặp, mã sẽ ngắn hơn rất nhiều. Nhưng khi một xác nhận thất bại, nó có thể làm xáo trộn đầu ra hiển thị một thông điệp mơ hồ. Nếu điều này xảy ra thì các bài kiểm tra của bạn sẽ ít hữu ích hơn và bạn sẽ cần một trình gỡ lỗi để kiểm tra xem mọi thứ có vấn đề gì.


8
Một khẳng định cho mỗi thử nghiệm được khuyến nghị về mặt kỹ thuật bởi vì điều đó có nghĩa là nhiều vấn đề sẽ không hiển thị chỉ là một lỗi. Tuy nhiên, trong thực tế, tôi nghĩ rằng việc tổng hợp cẩn thận các xác nhận sẽ làm giảm số lượng mã lặp lại và tôi gần như không bao giờ dính vào một xác nhận theo hướng dẫn kiểm tra.
Nhà thờ Rob

@ Pink-diamond-vuông Tôi thấy rằng NUnit không ngừng thử nghiệm sau khi một xác nhận thất bại (điều mà tôi nghĩ là kỳ lạ). Trong trường hợp cụ thể đó thực sự tốt hơn để có một khẳng định cho mỗi bài kiểm tra. Nếu một khung kiểm thử đơn vị không dừng kiểm tra sau khi xác nhận thất bại thì nhiều xác nhận sẽ tốt hơn.
siebz0r

3
NUnit không dừng toàn bộ bộ kiểm tra, nhưng một bài kiểm tra đó sẽ dừng trừ khi bạn thực hiện các bước để ngăn chặn (bạn có thể bắt ngoại lệ mà nó ném, đôi khi rất hữu ích). Điểm tôi nghĩ họ đang làm là nếu bạn viết các bài kiểm tra bao gồm nhiều hơn một khẳng định bạn sẽ không nhận được tất cả thông tin mà bạn cần để khắc phục vấn đề. Để làm việc qua ví dụ của bạn, hãy tưởng tượng rằng hàm nhân này không giống như số 3. Trong trường hợp này, assert multiply(1,3)sẽ thất bại nhưng bạn cũng sẽ không nhận được báo cáo thử nghiệm thất bại assert multiply(3,4).
Nhà thờ Rob

Tôi chỉ nghĩ rằng tôi sẽ nâng nó lên bởi vì một khẳng định duy nhất cho mỗi bài kiểm tra là, từ những gì tôi đã đọc trong thế giới .net, "thực hành tốt" và nhiều khẳng định là "sử dụng thực dụng". Nó trông hơi khác trong tài liệu Python trong đó ví dụ def test_shufflethực hiện hai xác nhận.
Nhà thờ Rob

Tôi đồng ý và không đồng ý: D Có sự lặp lại rõ ràng ở đây: assert multiply(*, *) == *vì vậy bạn có thể định nghĩa một assert_multiplyhàm. Trong kịch bản hiện tại, nó không quan trọng bằng số lượng hàng và khả năng đọc, nhưng bằng các thử nghiệm dài hơn, bạn có thể sử dụng lại các xác nhận phức tạp, đồ đạc, mã tạo lịch thi đấu, v.v ... Tôi không biết liệu đây có phải là cách tốt nhất không, nhưng tôi thường làm điều này.
inf3rno

10

Có vẻ như các bài kiểm tra về cơ bản thể hiện điều tương tự như mã, và do đó là một bản sao

Không, đây không phải là sự thật.

Các thử nghiệm có một mục đích khác với việc thực hiện của bạn:

  • Kiểm tra đảm bảo rằng việc thực hiện của bạn hoạt động.
  • Chúng phục vụ như một tài liệu: Bằng cách xem xét các thử nghiệm, bạn sẽ thấy các hợp đồng mà mã của bạn phải thực hiện, tức là đầu vào nào trả về đầu ra nào, các trường hợp đặc biệt, v.v.
  • Ngoài ra, các thử nghiệm của bạn đảm bảo rằng khi bạn thêm các tính năng mới, chức năng hiện tại của bạn không bị hỏng.

4

Không. DRY là về việc viết mã chỉ một lần để thực hiện một tác vụ cụ thể, kiểm tra là xác thực rằng tác vụ đang được thực hiện chính xác. Nó hơi giống với một thuật toán bỏ phiếu, trong đó rõ ràng sử dụng cùng một mã sẽ là vô ích.


2

Mục tiêu cuối cùng của DRY sẽ không bao gồm loại bỏ tất cả các mã kiểm tra?

Không, mục tiêu cuối cùng của DRY thực sự có nghĩa là loại bỏ tất cả các mã sản xuất .

Nếu các thử nghiệm của chúng tôi có thể là thông số kỹ thuật hoàn hảo về những gì chúng tôi muốn hệ thống thực hiện, chúng tôi sẽ phải tự động tạo mã sản xuất (hoặc nhị phân), loại bỏ hiệu quả cơ sở mã sản xuất mỗi se.

Đây thực sự là những gì các phương pháp tiếp cận như kiến ​​trúc hướng mô hình đòi hỏi phải đạt được - một nguồn sự thật do con người thiết kế từ đó mọi thứ bắt nguồn từ tính toán.

Tôi không nghĩ rằng điều ngược lại (loại bỏ tất cả các bài kiểm tra) là mong muốn bởi vì:

  • Bạn phải giải quyết sự không phù hợp trở kháng giữa thực hiện và đặc điểm kỹ thuật. Mã sản xuất có thể truyền đạt ý định đến một mức độ, nhưng nó sẽ không bao giờ dễ dàng để lý giải về các thử nghiệm được thể hiện tốt. Con người chúng ta cần cái nhìn cao hơn về lý do tại sao chúng ta xây dựng mọi thứ. Ngay cả khi bạn không thực hiện các bài kiểm tra vì DRY, các thông số kỹ thuật có thể sẽ phải được ghi lại trong tài liệu, đây là một con thú nguy hiểm hơn về mặt không khớp trở kháng và đồng bộ hóa mã nếu bạn hỏi tôi.
  • Mặc dù mã sản xuất có thể dễ dàng lấy được từ các thông số kỹ thuật thực thi chính xác (giả sử đủ thời gian), một bộ kiểm tra khó khăn hơn nhiều để khôi phục từ mã cuối cùng của chương trình. Thông số kỹ thuật không xuất hiện rõ ràng chỉ nhìn vào mã, bởi vì các tương tác giữa các đơn vị mã trong thời gian chạy rất khó thực hiện. Đây là lý do tại sao chúng tôi có một thời gian khó khăn như vậy đối phó với các ứng dụng kế thừa thử nghiệm. Nói cách khác: nếu bạn muốn ứng dụng của mình tồn tại được hơn một vài tháng, có lẽ bạn sẽ mất đi ổ cứng lưu trữ cơ sở mã sản xuất của mình hơn là nơi có bộ kiểm tra của bạn.
  • Việc giới thiệu một lỗi do tai nạn trong mã sản xuất dễ dàng hơn nhiều so với mã kiểm tra. Và vì mã sản xuất không tự xác minh (mặc dù điều này có thể được tiếp cận với Thiết kế theo Hợp đồng hoặc các hệ thống loại phong phú hơn), chúng tôi vẫn cần một số chương trình bên ngoài để kiểm tra và cảnh báo chúng tôi nếu xảy ra hồi quy.

1

Bởi vì đôi khi lặp lại chính mình là được. Không có nguyên tắc nào trong số này có nghĩa là được thực hiện trong mọi tình huống mà không có câu hỏi hoặc bối cảnh. Đôi khi tôi có các bài kiểm tra viết đối với phiên bản ngây thơ (và chậm) của thuật toán, đây là một vi phạm khá rõ ràng về DRY, nhưng chắc chắn có lợi.


1

Vì kiểm thử đơn vị là về việc làm cho những thay đổi không chủ ý trở nên khó khăn hơn, đôi khi nó cũng có thể làm cho những thay đổi có chủ ý trở nên khó khăn hơn. Thực tế này thực sự liên quan đến nguyên tắc DRY.

Ví dụ: nếu bạn có một hàm MyFunctionđược gọi trong mã sản xuất chỉ ở một nơi và bạn viết 20 bài kiểm tra đơn vị cho nó, bạn có thể dễ dàng có 21 vị trí trong mã của mình nơi hàm đó được gọi. Bây giờ, khi bạn phải thay đổi chữ ký của MyFunction, hoặc ngữ nghĩa hoặc cả hai (vì một số yêu cầu thay đổi), bạn có 21 địa điểm để thay đổi thay vì chỉ một. Và lý do thực sự là vi phạm nguyên tắc DRY: bạn lặp lại (ít nhất) cùng một chức năng gọi đến MyFunction21 lần.

Cách tiếp cận đúng cho trường hợp như vậy là áp dụng nguyên tắc DRY cho mã kiểm tra của bạn: khi viết 20 bài kiểm tra đơn vị, đóng gói các cuộc gọi đến MyFunctiontrong bài kiểm tra đơn vị của bạn chỉ trong một vài chức năng của trình trợ giúp (lý tưởng chỉ là một), được sử dụng bởi 20 bài kiểm tra đơn vị. Lý tưởng nhất, bạn kết thúc chỉ với hai vị trí trong cuộc gọi mã của bạn MyFunction: một từ mã sản xuất của bạn và một từ các bài kiểm tra đơn vị của bạn. Vì vậy, khi bạn phải thay đổi chữ ký MyFunctionsau này, bạn sẽ chỉ có một vài nơi để thay đổi trong các bài kiểm tra của mình.

"Một vài nơi" vẫn còn nhiều hơn "một nơi" (những gì bạn nhận được mà không có bài kiểm tra đơn vị nào ), nhưng lợi thế của việc kiểm tra đơn vị sẽ vượt xa lợi thế của việc thay đổi ít mã hơn (nếu không bạn hoàn thành kiểm tra đơn vị Sai lầm).


0

Một trong những thách thức lớn nhất để xây dựng phần mềm là nắm bắt các yêu cầu; đó là trả lời câu hỏi "phần mềm này nên làm gì?" Phần mềm cần các yêu cầu chính xác để xác định chính xác những gì hệ thống cần làm, nhưng những người xác định nhu cầu cho các hệ thống và dự án phần mềm thường bao gồm những người không có nền tảng phần mềm hoặc chính thức (toán học). Việc thiếu sự nghiêm ngặt trong định nghĩa yêu cầu buộc phải phát triển phần mềm để tìm cách xác nhận phần mềm theo yêu cầu.

Nhóm phát triển thấy mình dịch các mô tả thông tục cho một dự án thành các yêu cầu khắt khe hơn. Kỷ luật kiểm thử đã kết hợp thành điểm kiểm tra để phát triển phần mềm, để thu hẹp khoảng cách giữa những gì khách hàng nói họ muốn và phần mềm hiểu họ muốn gì. Cả nhà phát triển phần mềm và nhóm kiểm tra chất lượng / hình thức đều hiểu về đặc tả (không chính thức) và từng phần mềm (độc lập) viết phần mềm hoặc kiểm tra để đảm bảo rằng sự hiểu biết của họ phù hợp. Thêm một người khác để hiểu các yêu cầu (không chính xác) đã thêm các câu hỏi và quan điểm khác nhau để tăng thêm độ chính xác của các yêu cầu.

Vì luôn có thử nghiệm chấp nhận, nên việc mở rộng vai trò thử nghiệm để viết thử nghiệm tự động và đơn vị là điều đương nhiên. Vấn đề là điều đó có nghĩa là thuê các lập trình viên thực hiện kiểm thử, và do đó bạn thu hẹp quan điểm từ đảm bảo chất lượng sang lập trình viên thực hiện kiểm thử.

Như đã nói, có lẽ bạn đang kiểm tra sai nếu các bài kiểm tra của bạn khác với các chương trình thực tế. Đề nghị của Msdy sẽ tập trung nhiều hơn vào những gì trong các bài kiểm tra, và ít hơn về cách thức.

Điều trớ trêu là thay vì nắm bắt một đặc tả chính thức của các yêu cầu từ mô tả thông tục, ngành công nghiệp đã chọn thực hiện các bài kiểm tra điểm dưới dạng mã để tự động kiểm tra. Thay vì đưa ra các yêu cầu chính thức mà phần mềm có thể được xây dựng để trả lời, cách tiếp cận được thực hiện là kiểm tra một vài điểm, thay vì tiếp cận phần mềm xây dựng bằng logic chính thức. Đây là một sự thỏa hiệp, nhưng đã khá hiệu quả và tương đối thành công.


0

Nếu bạn nghĩ rằng mã kiểm tra của bạn quá giống với mã triển khai của bạn, thì đây có thể là một dấu hiệu cho thấy bạn đang sử dụng quá mức một khung mô phỏng. Thử nghiệm dựa trên giả ở mức quá thấp có thể kết thúc với thiết lập thử nghiệm trông rất giống phương pháp đang được thử nghiệm. Cố gắng viết các bài kiểm tra cấp cao hơn mà ít có khả năng phá vỡ hơn nếu bạn thay đổi triển khai của mình (tôi biết điều này có thể khó, nhưng nếu bạn có thể quản lý nó, bạn sẽ có một bộ kiểm tra hữu ích hơn).


0

Các thử nghiệm đơn vị không nên bao gồm một bản sao của mã được thử nghiệm, như đã được lưu ý.

Tuy nhiên, tôi sẽ thêm rằng các thử nghiệm đơn vị đó thường không phải là mã DRY như mã "sản xuất", bởi vì thiết lập có xu hướng tương tự (nhưng không giống nhau) trong các thử nghiệm ... đặc biệt là nếu bạn có một số lượng phụ thuộc đáng kể mà bạn đang chế giễu / giả mạo.
Tất nhiên có thể cấu trúc lại thứ này thành một phương thức thiết lập chung (hoặc tập hợp các phương thức thiết lập) ... nhưng tôi thấy rằng các phương thức thiết lập đó có xu hướng có danh sách tham số dài và khá dễ vỡ.

Vì vậy, hãy thực dụng. Nếu bạn có thể hợp nhất mã thiết lập mà không ảnh hưởng đến khả năng bảo trì, bằng mọi cách hãy làm điều đó. Nhưng nếu giải pháp thay thế là một tập hợp các phương thức thiết lập phức tạp và dễ vỡ, một chút lặp lại trong các phương thức kiểm tra của bạn là OK.

Một nhà truyền giáo TDD / BDD địa phương nói theo cách này:
"Mã sản xuất của bạn phải là DRY. Nhưng thử nghiệm của bạn là 'ẩm'.


0

Dường như các bài kiểm tra về cơ bản thể hiện điều tương tự như mã, và do đó là một bản sao (về khái niệm, không phải là triển khai) của mã.

Điều này không đúng, các kiểm tra mô tả các trường hợp sử dụng, trong khi mã mô tả một thuật toán vượt qua các trường hợp sử dụng, vì vậy, tổng quát hơn. Bằng TDD, bạn bắt đầu bằng cách viết các ca sử dụng (có thể dựa trên câu chuyện của người dùng) và sau đó bạn triển khai mã cần thiết để vượt qua các ca sử dụng này. Vì vậy, bạn viết một bài kiểm tra nhỏ, một đoạn mã nhỏ và sau đó bạn cấu trúc lại nếu cần thiết để thoát khỏi sự lặp lại. Đó là cách nó hoạt động.

Bằng các bài kiểm tra có thể có sự lặp lại là tốt. Ví dụ: bạn có thể sử dụng lại đồ đạc, mã tạo lịch thi đấu, xác nhận phức tạp, v.v ... Tôi thường làm điều này, để ngăn ngừa lỗi trong các bài kiểm tra, nhưng tôi thường quên kiểm tra trước xem thử nghiệm có thực sự thất bại không và nó có thực sự phá hỏng ngày không , khi bạn đang tìm kiếm lỗi trong mã trong nửa giờ và thử nghiệm sai ... xD

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.