Bạn có nên mã cứng dữ liệu của bạn trên tất cả các bài kiểm tra đơn vị?


33

Hầu hết các hướng dẫn / ví dụ kiểm thử đơn vị ngoài kia thường liên quan đến việc xác định dữ liệu sẽ được kiểm tra cho từng thử nghiệm riêng lẻ. Tôi đoán đây là một phần của lý thuyết "mọi thứ nên được kiểm tra trong sự cô lập".

Tuy nhiên, tôi nhận thấy rằng khi xử lý các ứng dụng đa nhiệm với nhiều DI , mã cần thiết để thiết lập mỗi thử nghiệm sẽ rất dài. Thay vào đó, tôi đã xây dựng một số lớp cơ sở thử nghiệm mà bây giờ tôi có thể kế thừa, có rất nhiều giàn giáo thử nghiệm được xây dựng trước.

Là một phần của việc này, tôi cũng đang xây dựng các bộ dữ liệu giả đại diện cho cơ sở dữ liệu của một ứng dụng đang chạy, mặc dù thường chỉ có một hoặc hai hàng trong mỗi "bảng".

Đây có phải là một thực tiễn được chấp nhận để xác định trước, nếu không phải là tất cả, thì phần lớn dữ liệu thử nghiệm trên tất cả các thử nghiệm đơn vị?

Cập nhật

Từ các bình luận bên dưới, có cảm giác như tôi đang tích hợp nhiều hơn thử nghiệm đơn vị.

Dự án hiện tại của tôi là ASP.NET MVC, sử dụng Đơn vị công việc trên Entity Framework Code First và Moq để thử nghiệm. Tôi đã chế nhạo UoW và các kho lưu trữ, nhưng tôi đang sử dụng các lớp logic kinh doanh thực tế và kiểm tra các hành động của bộ điều khiển. Các bài kiểm tra thường sẽ kiểm tra xem UoW đã được cam kết chưa, ví dụ:

[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
  [TestMethod]
  public void UserInvite_ExistingUser_DoesntInsertNewUser() {
    // Arrange
    var model = new Mandy.App.Models.Setup.UserInvite() {
      Email = userData.First().Email
    };

    // Act
    setupController.UserInvite(model);

    // Assert
    mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
    mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
  }
}

SetupControllerTestBaseđang xây dựng UoW giả, và bắt đầu userLogic.

Rất nhiều thử nghiệm yêu cầu phải có người dùng hoặc sản phẩm hiện có trong cơ sở dữ liệu, vì vậy tôi đã điền trước những gì mà UoW giả trả về, trong ví dụ này userData, chỉ là IList<User>một bản ghi người dùng.


4
Vấn đề với hướng dẫn / ví dụ là chúng cần đơn giản, nhưng bạn không thể hiển thị giải pháp cho một vấn đề phức tạp trên một ví dụ đơn giản. Chúng nên được kèm theo "nghiên cứu trường hợp" mô tả cách sử dụng công cụ này trong các dự án thực tế có kích thước hợp lý, nhưng chúng hiếm khi.
Jan Hudec

Có lẽ bạn có thể thêm một số ví dụ nhỏ về mã mà bạn không hoàn toàn hài lòng.
Luc Franken

Nếu bạn cần nhiều mã thiết lập để chạy thử nghiệm, bạn có nguy cơ chạy thử nghiệm chức năng. Nếu thử nghiệm thất bại khi bạn thay đổi mã nhưng không có gì sai với mã. Đó chắc chắn là một thử nghiệm chức năng.
Phản ứng

Cuốn sách "xUnit Test Forms" tạo ra một trường hợp mạnh mẽ cho đồ đạc và người trợ giúp có thể tái sử dụng. Mã kiểm tra nên được duy trì như bất kỳ mã nào khác.
Chuck Krutsinger

Bài viết này có thể hữu ích: yegor256.com/2015/05/25/unit-test-scaffolding.html
yegor256

Câu trả lời:


25

Cuối cùng, bạn muốn viết càng ít mã càng tốt để có được kết quả càng nhiều càng tốt. Có nhiều mã giống nhau trong nhiều thử nghiệm a) có xu hướng dẫn đến mã hóa sao chép và b) có nghĩa là nếu chữ ký phương thức thay đổi, cuối cùng bạn có thể phải sửa rất nhiều thử nghiệm bị hỏng.

Tôi sử dụng cách tiếp cận để có các lớp TestHelper tiêu chuẩn cung cấp cho tôi rất nhiều kiểu dữ liệu mà tôi thường xuyên sử dụng, vì vậy tôi có thể tạo các tập hợp các lớp thực thể hoặc DTO tiêu chuẩn cho các bài kiểm tra của mình để truy vấn và biết chính xác những gì tôi sẽ nhận được mỗi lần. Vì vậy, tôi có thể gọi TestHelper.GetFooRange( 0, 100 )để có được một phạm vi 100 đối tượng Foo với tất cả các lớp / trường phụ thuộc của chúng được đặt.

Đặc biệt khi có các mối quan hệ phức tạp được cấu hình trong hệ thống loại ORM cần có mặt để mọi thứ chạy chính xác, nhưng không nhất thiết có ý nghĩa đối với thử nghiệm này có thể tiết kiệm nhiều thời gian.

Trong các tình huống khi tôi kiểm tra gần mức dữ liệu, đôi khi tôi tạo một phiên bản thử nghiệm của lớp kho lưu trữ của mình có thể được truy vấn theo cách tương tự (một lần nữa, đây là trong môi trường loại ORM và nó sẽ không liên quan đến cơ sở dữ liệu thực tế), bởi vì việc loại bỏ các phản hồi chính xác cho các truy vấn là rất nhiều công việc và thường chỉ cung cấp các lợi ích nhỏ.

Có một số điều cần cẩn thận, mặc dù trong các bài kiểm tra đơn vị:

  • Hãy chắc chắn rằng mocks của bạn mocks . Các lớp thực hiện các hoạt động xung quanh lớp đang được kiểm tra phải là các đối tượng giả nếu bạn đang thực hiện kiểm thử đơn vị. Các lớp loại DTO / thực thể của bạn có thể là thực tế, nhưng nếu các lớp đang thực hiện các hoạt động, bạn cần phải chế giễu chúng - nếu không khi mã hỗ trợ thay đổi và các thử nghiệm của bạn bắt đầu thất bại, bạn phải tìm kiếm lâu hơn để tìm ra thay đổi nào thực sự gây ra vấn đề
  • Hãy chắc chắn rằng bạn đang kiểm tra các lớp học của bạn . Đôi khi, nếu người ta xem qua một bộ các bài kiểm tra đơn vị thì rõ ràng là một nửa số bài kiểm tra đang thực sự kiểm tra khung mô phỏng nhiều hơn mã thực tế mà họ được cho là đang kiểm tra.
  • Không sử dụng lại các đối tượng giả / hỗ trợ Đây là một vấn đề lớn - khi người ta bắt đầu cố gắng thông minh với các bài kiểm tra đơn vị hỗ trợ mã, thật dễ dàng vô tình tạo ra các đối tượng tồn tại giữa các bài kiểm tra, có thể có tác dụng không thể đoán trước. Ví dụ, hôm qua tôi có một bài kiểm tra đã vượt qua khi tự chạy, đã vượt qua khi tất cả các bài kiểm tra trong lớp được chạy, nhưng không thành công khi toàn bộ bộ kiểm tra đã chạy. Hóa ra, có một đối tượng tĩnh lén lút tắt trong một trình trợ giúp kiểm tra mà khi tôi tạo ra nó, chắc chắn sẽ không bao giờ gây ra sự cố. Chỉ cần nhớ: Khi bắt đầu thử nghiệm, mọi thứ được tạo ra, vào cuối thử nghiệm, mọi thứ đều bị phá hủy.

10

Bất cứ điều gì làm cho ý định kiểm tra của bạn dễ đọc hơn.

Như một quy tắc chung của ngón tay cái:

Nếu dữ liệu là một phần của thử nghiệm (ví dụ: Không nên in các hàng có trạng thái 7) thì hãy mã hóa nó trong thử nghiệm, để nó rõ ràng những gì tác giả dự định sẽ xảy ra.

Nếu dữ liệu chỉ là phụ để đảm bảo rằng nó có một cái gì đó để làm việc (ví dụ: Không nên đánh dấu bản ghi là hoàn thành nếu dịch vụ xử lý ném ngoại lệ) thì bằng mọi cách, có một phương thức BuildDummyData hoặc một lớp kiểm tra giúp loại bỏ dữ liệu không liên quan khỏi bài kiểm tra .

Nhưng lưu ý rằng tôi đang đấu tranh để nghĩ về một ví dụ tốt về sau này. Nếu bạn có nhiều trong số này trong một vật cố kiểm tra đơn vị, có lẽ bạn có một vấn đề khác để giải quyết ... có lẽ phương pháp được kiểm tra là quá phức tạp.


Tôi đồng ý. Điều này có mùi giống như những gì anh ta đang thử nghiệm là kết hợp chặt chẽ để thử nghiệm đơn vị.
Phản ứng

5

Phương pháp kiểm tra khác nhau

Đầu tiên xác định những gì bạn đang làm: Kiểm tra đơn vị hoặc kiểm tra tích hợp . Số lượng các lớp không liên quan để kiểm tra đơn vị vì bạn chỉ có thể kiểm tra một lớp. Phần còn lại bạn chế nhạo. Để kiểm thử tích hợp, không thể tránh khỏi việc bạn kiểm tra nhiều lớp. Nếu bạn có các bài kiểm tra đơn vị tốt, mẹo là làm cho các bài kiểm tra tích hợp không quá phức tạp.

Nếu kiểm tra đơn vị của bạn tốt, bạn không phải lặp lại kiểm tra tất cả các chi tiết khi thực hiện kiểm tra tích hợp.

Các thuật ngữ chúng tôi sử dụng, chúng phụ thuộc vào một chút nền tảng, nhưng bạn có thể tìm thấy chúng trong hầu hết các nền tảng thử nghiệm / phát triển:

Ứng dụng ví dụ

Tùy thuộc vào công nghệ bạn sử dụng tên có thể khác nhau, nhưng tôi sẽ sử dụng tên này làm ví dụ:

Nếu bạn có một ứng dụng CRUD đơn giản với mô hình Sản phẩm, Bộ điều khiển Sản phẩm và chế độ xem chỉ mục tạo bảng HTML với các sản phẩm:

Kết quả cuối cùng của ứng dụng là hiển thị bảng HTML với danh sách tất cả các sản phẩm đang hoạt động.

Kiểm tra đơn vị

Mô hình

Mô hình bạn có thể kiểm tra khá dễ dàng. Có nhiều phương pháp khác nhau cho nó; chúng tôi sử dụng đồ đạc. Tôi nghĩ đó là những gì bạn gọi là "bộ dữ liệu giả". Vì vậy, trước khi mỗi bài kiểm tra được chạy, chúng ta tạo bảng và đưa vào dữ liệu gốc. Hầu hết các nền tảng có phương pháp cho việc này. Ví dụ, trong lớp thử nghiệm của bạn, một phương thức setUp () được chạy trước mỗi thử nghiệm.

Sau đó, chúng tôi chạy thử nghiệm của chúng tôi, ví dụ: các sản phẩm testGetAllActive .

Vì vậy, chúng tôi kiểm tra trực tiếp đến một cơ sở dữ liệu thử nghiệm. Chúng tôi không chế nhạo nguồn dữ liệu; chúng tôi làm cho nó luôn luôn giống nhau. Điều này cho phép chúng tôi lấy ví dụ để kiểm tra phiên bản mới của cơ sở dữ liệu và mọi vấn đề truy vấn sẽ xuất hiện.

Trong thế giới thực, bạn không thể luôn tuân theo trách nhiệm 100% . Nếu bạn muốn làm điều này tốt hơn nữa, bạn có thể sử dụng một nguồn dữ liệu mà bạn giả định. Đối với chúng tôi (chúng tôi sử dụng ORM) có cảm giác như đang thử nghiệm công nghệ hiện có. Ngoài ra các bài kiểm tra trở nên phức tạp hơn nhiều và chúng không thực sự kiểm tra các truy vấn. Vì vậy, chúng tôi giữ nó theo cách này.

Dữ liệu được mã hóa cứng được lưu trữ riêng trong đồ đạc. Vì vậy, vật cố giống như một tệp SQL với câu lệnh tạo bảng và chèn cho các bản ghi chúng ta sử dụng. Chúng tôi giữ chúng nhỏ trừ khi có nhu cầu thực sự để kiểm tra với nhiều hồ sơ.

class ProductModel {
  public function getAllActive() {
    return $this->find('all', array('conditions' => array('active' => 1)));
  }
}

Bộ điều khiển

Bộ điều khiển cần nhiều công việc hơn, vì chúng tôi không muốn thử nghiệm mô hình với nó. Vì vậy, những gì chúng tôi làm là chế nhạo mô hình. Điều đó có nghĩa là: Chúng tôi kiểm tra: phương thức index () sẽ trả về một danh sách các bản ghi.

Vì vậy, chúng tôi mô phỏng phương thức mô hình getAllActive () và thêm dữ liệu cố định trong đó (ví dụ hai bản ghi). Bây giờ chúng tôi kiểm tra dữ liệu mà bộ điều khiển gửi đến chế độ xem và chúng tôi so sánh nếu chúng tôi thực sự lấy lại được hai bản ghi đó.

function testProductIndexLoggedIn() {
  $this->setLoggedIn();
  $this->ProductsController->mock('ProductModel', 'index', function(return array(your records) ));
  $result=$this->ProductsController->index();
  $this->assertEquals(2, count($result['products']));
}

Thế là đủ rồi. Chúng tôi cố gắng thêm ít chức năng vào bộ điều khiển vì điều đó làm cho việc kiểm tra khó khăn. Nhưng tất nhiên luôn có một số mã trong đó. Ví dụ: chúng tôi kiểm tra các yêu cầu như: Chỉ hiển thị hai bản ghi đó nếu bạn đã đăng nhập.

Vì vậy, bộ điều khiển cần một giả bình thường và một phần nhỏ dữ liệu được mã hóa cứng. Đối với một hệ thống đăng nhập có thể một cái khác. Trong thử nghiệm của chúng tôi, chúng tôi có một phương thức trợ giúp cho nó: setLoggedIn (). Điều đó làm cho nó đơn giản để kiểm tra với đăng nhập hoặc không có đăng nhập.

class ProductsController {
  public function index() {
    if($this->loggedIn()) {
      $this->set('products', $this->ProductModel->getAllActive());
    }
  }
}

Lượt xem

Kiểm tra lượt xem là khó. Đầu tiên chúng tôi tách ra logic mà lặp lại. Chúng tôi đưa nó vào Helpers và kiểm tra các lớp đó một cách nghiêm ngặt. Chúng tôi hy vọng luôn luôn cùng một đầu ra. Ví dụ: createdHtmlTableFromArray ().

Sau đó, chúng tôi có một số quan điểm cụ thể của dự án. Chúng tôi không kiểm tra những cái đó. Nó không thực sự mong muốn để kiểm tra đơn vị những người. Chúng tôi giữ chúng cho các bài kiểm tra tích hợp. Bởi vì chúng tôi đã đưa ra rất nhiều mã vào các lượt xem nên chúng tôi có rủi ro thấp hơn ở đây.

Nếu bạn bắt đầu thử nghiệm những thứ bạn có thể cần thay đổi thử nghiệm của mình mỗi khi bạn thay đổi một đoạn HTML không hữu ích cho hầu hết các dự án.

echo $this->tableHelper->generateHtmlTableFromArray($products);

Thử nghiệm hội nhập

Tùy thuộc vào nền tảng của bạn ở đây, bạn có thể làm việc với các câu chuyện của người dùng, v.v. Nó có thể được dựa trên web như Selenium hoặc các giải pháp tương đương khác.

Nói chung, chúng tôi chỉ tải cơ sở dữ liệu với các đồ đạc và xác nhận dữ liệu nào sẽ có sẵn. Để kiểm tra tích hợp đầy đủ, chúng tôi thường sử dụng các yêu cầu rất toàn cầu. Vì vậy: Đặt sản phẩm thành hoạt động và sau đó kiểm tra xem sản phẩm có khả dụng không.

Chúng tôi không kiểm tra mọi thứ một lần nữa, như liệu các trường bên phải có sẵn hay không. Chúng tôi kiểm tra các yêu cầu lớn hơn ở đây. Vì chúng tôi không muốn sao chép các thử nghiệm của mình từ bộ điều khiển hoặc chế độ xem. Nếu một cái gì đó thực sự là phần chính / cốt lõi của ứng dụng của bạn hoặc vì lý do bảo mật (kiểm tra mật khẩu KHÔNG khả dụng) thì chúng tôi sẽ thêm chúng để đảm bảo đúng.

Dữ liệu được mã hóa cứng được lưu trữ trong đồ đạc.

function testIntegrationProductIndexLoggedIn() {
  $this->setLoggedIn();
  $result=$this->request('products/index');

  $expected='<table';
  $this->assertContains($expected, $result);

  // Some content from the fixture record
  $expected='<td>Product 1 name</td>';
  $this->assertContains($expected, $result);
}

Đây là một câu trả lời tuyệt vời, cho một câu hỏi hoàn toàn khác.
pdr

Cảm ơn vì bạn đã phản hồi. Bạn có thể đúng rằng tôi đã không đề cập đến nó quá cụ thể. Lý do của câu trả lời dài dòng là vì tôi thấy một trong những điều khó khăn nhất khi kiểm tra trong câu hỏi được hỏi. Tổng quan về cách kiểm tra trong sự cô lập phù hợp với các loại thử nghiệm khác nhau. Đó là lý do tại sao tôi đã thêm vào mỗi phần cách xử lý dữ liệu (hoặc tách ra). Sẽ xem xét nếu tôi có thể làm cho nó rõ ràng hơn.
Luc Franken

Câu trả lời đã được cập nhật với một số ví dụ mã để giải thích cách kiểm tra mà không gọi tất cả các loại lớp khác.
Luc Franken

4

Nếu bạn đang viết các bài kiểm tra liên quan đến nhiều DI và nối dây, cho đến khi sử dụng các nguồn dữ liệu "thực", có lẽ bạn đã rời khỏi khu vực kiểm tra đơn vị đơn giản và bước vào miền kiểm thử tích hợp.

Đối với các bài kiểm tra tích hợp, tôi nghĩ, việc logic thiết lập dữ liệu chung không phải là ý kiến ​​tồi. Mục tiêu chính của các thử nghiệm như vậy là để chứng minh rằng mọi thứ đều được cấu hình chính xác. Điều này khá độc lập với dữ liệu cụ thể được gửi qua hệ thống của bạn.

Mặt khác, đối với các bài kiểm tra Đơn vị, tôi khuyên bạn nên giữ mục tiêu của lớp kiểm tra là một lớp "thực" duy nhất và chế nhạo mọi thứ khác. Sau đó, bạn thực sự nên mã hóa dữ liệu thử nghiệm để đảm bảo rằng bạn đã bao quát càng nhiều đường dẫn lỗi đặc biệt / trước đó càng tốt.

Để thêm một yếu tố bán mã hóa / ngẫu nhiên vào các thử nghiệm, tôi muốn giới thiệu các nhà máy mô hình ngẫu nhiên. Trong một thử nghiệm sử dụng một thể hiện của mô hình của tôi, sau đó tôi sử dụng các nhà máy này để tạo ra một đối tượng mô hình hợp lệ, nhưng hoàn toàn ngẫu nhiên và sau đó mã cứng chỉ các thuộc tính được quan tâm cho thử nghiệm trong tay. Bằng cách này, bạn chỉ định tất cả dữ liệu có liên quan trực tiếp trong thử nghiệm của mình, đồng thời tiết kiệm cho bạn nhu cầu cũng chỉ định tất cả dữ liệu không liên quan và thử nghiệm (ở một mức độ nhất định) rằng không có sự phụ thuộc ngoài ý muốn vào các trường mô hình khác.


-1

Tôi nghĩ rằng nó là khá phổ biến đối với mã cứng hầu hết các dữ liệu cho các bài kiểm tra của bạn.

Xem xét một tình huống đơn giản trong đó một tập dữ liệu cụ thể gây ra lỗi. Bạn có thể đặc biệt tạo một bài kiểm tra đơn vị cho dữ liệu đó để thực hiện sửa lỗi và đảm bảo rằng lỗi không quay trở lại. Theo thời gian, các bài kiểm tra của bạn sẽ có một bộ dữ liệu bao gồm một số trường hợp kiểm tra.

Dữ liệu kiểm tra được xác định trước cũng cho phép bạn xây dựng một tập hợp dữ liệu bao gồm một phạm vi rộng và các tình huống đã biết.

Điều đó nói rằng, tôi nghĩ cũng có giá trị trong việc có một số dữ liệu ngẫu nhiên trong các thử nghiệm của bạn.


Bạn đã thực sự đọc câu hỏi và không chỉ tiêu đề?
Jakob

giá trị trong việc có một số dữ liệu ngẫu nhiên trong các thử nghiệm của bạn - Vâng, vì không có gì giống như cố gắng tìm hiểu điều gì đã xảy ra trong một thử nghiệm mỗi lần nó thất bại mỗi tuần.
pdr

Có giá trị trong việc có dữ liệu ngẫu nhiên trong các thử nghiệm của bạn cho các thử nghiệm haze / fuzzing / đầu vào. Nhưng không phải trong bài kiểm tra đơn vị của bạn, đó sẽ là một cơn ác mộng.
glenatron
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.