Helpex - Trao đổi & giúp đỡ Đăng nhập

Giải phẫu của thử nghiệm đơn vị mạnh mẽ (Với các ví dụ trong C #)

Tôi nghĩ về các bài kiểm tra đơn vị như một phần mở rộng cho mã của tôi. Quá trình kiểm tra kỹ lưỡng mang lại sự an tâm rằng khi cấu trúc lại mã hoặc thực hiện cải tiến hiệu suất, các thiết bị vẫn hoạt động như mong đợi. Nó cũng có thể tìm ra lỗi và các trường hợp cạnh và tránh hồi quy trong quá trình tái cấu trúc.

Tôi đến từ nền tảng .NET / C # và đã biên soạn bộ sưu tập những suy nghĩ và mẩu tin nhỏ này mà tôi thấy hữu ích khi viết bài kiểm tra.

Tại sao phải viết bài kiểm tra đơn vị?

Tôi thấy rằng việc viết các bài kiểm tra đơn vị và viết mã có thể bảo trì mạnh mẽ thường đi đôi với nhau. Nếu logic bạn đang kiểm tra được trộn lẫn với các thành phần UI, bạn có thể thấy rằng các thành phần cần được tải (có thể trong một chuỗi UI) để các bài kiểm tra có quyền truy cập vào logic. Nếu nhiều lớp được kết hợp chặt chẽ với nhau, thì thiết lập cho một bài kiểm tra đơn vị có thể bị phức tạp. Những ví dụ này và nhiều ví dụ khác về việc kiểm tra đơn vị trở nên khó khăn là một dấu hiệu cho thấy mã có thể được hưởng lợi từ một số cấu trúc lại tốt.

Phương pháp tiếp cận kiểm thử đơn vị

Kiểm tra hộp đen là nơi bạn kiểm tra một đơn vị mà không có quyền truy cập vào mã nguồn của đơn vị hoặc bằng cách từ chối xem xét nó. Để thực hiện kiểm tra hộp đen, bạn có thể biết, được thông báo hoặc thử nghiệm với những gì được mong đợi ở thiết bị.

Kiểm thử hộp trắng là nơi bạn kiểm tra mã nguồn của đơn vị được kiểm tra để hỗ trợ viết các bài kiểm tra đơn vị. Bằng cách kiểm tra mã nguồn, bạn sẽ có thể đảm bảo rằng các bài kiểm tra của bạn bao gồm chung tất cả các đường dẫn mã trong mỗi hành động. Điều này bao gồm việc cung cấp cho các giới hạn của bất kỳ vòng lặp nào và tuân theo tất cả các điều kiện logic. Chắc chắn cũng có thể đạt được mức độ phủ mã này với kiểm tra hộp đen, chẳng hạn như nếu thiết bị đủ đơn giản để thử tất cả các đầu vào hoặc kết hợp trạng thái có thể. Kiểm tra hộp trắng có thể giúp nắm bắt các tình huống khó hiểu mà bạn có thể không nghĩ đến nếu không nhìn thấy mã.

Trạng thái thử nghiệm bao gồm việc thực hiện một hành động trên một thiết bị và sau đó kiểm tra xem kết quả mong đợi đã được trả về hay trạng thái của thiết bị đã được cập nhật như mong đợi. Trạng thái thử nghiệm có thể đạt được bất kể bạn đang thử nghiệm hộp trắng hay hộp đen.

Triển khai thử nghiệm là một phần mở rộng cho thử nghiệm hộp trắng trong đó bạn kiểm tra xem các phương pháp nhất định có được gọi hay không trong khi thực hiện một hành động. Ở đây, bạn không khẳng định bất kỳ trạng thái nào, nhưng thay vào đó hãy xác minh rằng hành vi nội bộ đang làm những gì được mong đợi.

Giải phẫu của một bài kiểm tra đơn vị

Một thử nghiệm đơn vị duy nhất xác nhận trạng thái thường được tạo thành từ ba giai đoạn sau:

  1. 'Sắp xếp' chuẩn bị mọi thứ sẵn sàng để thực hiện kiểm tra. Điều này có thể là khai báo các biến, xây dựng các đối tượng được yêu cầu hoặc thiết lập trạng thái của một đơn vị dựa trên các trường hợp chúng ta muốn kiểm tra. Một số hoặc tất cả bước này có thể diễn ra trong phương pháp SetUp của bộ cố định kiểm tra đơn vị hiện tại. Đối với các thử nghiệm đơn vị đơn giản, bước này có thể không cần thiết.
  2. 'Hành động' thực hiện hành động mà chúng tôi đang thử nghiệm trên thiết bị.
  3. Kiểm tra 'Khẳng định' để xem rằng hành động được thực hiện chính xác. Chúng ta muốn kiểm tra xem giá trị trả về của một cuộc gọi phương thức có được mong đợi hay trạng thái của một đối tượng là như mong đợi.

Một ví dụ là:

[Test]
public void GetMinimum_UnsortedIntegerArray_ReturnsSmallestValue()
{
  var unsortedArray = new int[] {7,4,9,2,5}; // Arrange
 
  var minimum = Statistics.GetMinimum(unsortedArray); // Act
 
  Assert.AreEqual(2, minimum); // Assert
}

Hướng dẫn cấu trúc một bài kiểm tra đơn vị

Dưới đây là một số nguyên tắc bạn có thể làm theo khi viết bài kiểm tra đơn vị:

  • Giữ số lượng xác nhận cho mỗi đơn vị thử nghiệm ở mức tối thiểu. Một bài kiểm tra đơn vị là kiểm tra một thứ. Nhiều xác nhận trong một thử nghiệm duy nhất là tốt, nhưng nếu chia các xác nhận thành các thử nghiệm riêng biệt là hợp lý, thì tốt nhất bạn nên làm như vậy.
  • Tránh các thử nghiệm ít xác nhận. Đây là những bài kiểm tra không chứa bất kỳ xác nhận nào (hoặc xác minh trong trường hợp kiểm tra triển khai) và được sử dụng để kiểm tra xem một thứ gì đó hoạt động mà không đưa ra ngoại lệ. Tôi thích các bài kiểm tra đơn vị của mình để luôn kiểm tra xem có điều gì đó hoạt động hay không.
  • Không lặp lại các xác nhận đã được đề cập trong các thử nghiệm hiện có. Nếu một kiểm thử đơn vị khẳng định rằng một kết quả không phải là rỗng hoặc một tập hợp có chính xác một mục, thì kiểm thử đơn vị tiếp theo không cần lặp lại các khẳng định như vậy trước khi khẳng định trạng thái bổ sung.
  • Các xác nhận nên được đặt trong các bài kiểm tra đơn vị phù hợp, thay vì được đưa ra trong các phương pháp trợ giúp. Nếu việc kiểm tra trạng thái hơi phức tạp và phổ biến trong nhiều bài kiểm tra, thì tốt hơn là bạn nên viết một phương thức trợ giúp để kiểm tra trạng thái. Sau đó, thật dễ dàng để đặt các xác nhận bên trong phương thức trợ giúp đó, mặc dù tôi thấy các bài kiểm tra đơn vị dễ đọc hơn nếu chúng chứa các xác nhận có thể kiểm tra boolean được phương thức trợ giúp trả về.
  • Mã để sắp xếp, hành động và khẳng định phải nằm trên các dòng riêng của chúng, lý tưởng nhất là có một dòng mới giữa mỗi dòng. Nếu bạn đang xác nhận rằng một phương thức trả về true, bạn có thể thực hiện lệnh gọi phương thức đó ngay bên trong câu lệnh khẳng định. Tôi thấy các bài kiểm tra đơn vị rõ ràng hơn khi chúng được giữ riêng biệt.

Kiểm tra đôi

Kiểm thử đơn vị chỉ liên quan đến việc kiểm tra một đơn vị duy nhất, chứ không phải là liệu nhiều đơn vị có hoạt động chính xác với nhau hay không. Một số người xem một đơn vị là một lớp, những người khác như một phương thức. Dù bằng cách nào, một đơn vị đơn lẻ thường yêu cầu các đối tượng khác để hoạt động chính xác. Để biên dịch các bài kiểm tra, chúng tôi có thể xây dựng các đối tượng thực và cung cấp chúng cho đơn vị đang kiểm tra theo cách giống như cách chúng tôi làm khi sử dụng đơn vị trong sản xuất. Tuy nhiên, bằng cách làm điều này, chúng tôi bắt đầu rời khỏi việc thực sự chỉ thử nghiệm một đơn vị duy nhất và ghép mã thử nghiệm với nhiều đơn vị hơn những gì nó liên quan. Thậm chí có thể không thực tế khi sử dụng các đối tượng thực nếu chúng kết nối với các công nghệ bên ngoài như dịch vụ web của bên thứ ba, hệ thống xếp hàng hoặc kho dữ liệu.

Đây là nơi các bộ đôi thử nghiệm xuất hiện. Có nhiều bộ đôi thử nghiệm khác nhau mà chúng ta có thể sử dụng để thay thế cho các đối tượng thực tế. Mục đích của việc này chỉ là để đáp ứng các yêu cầu của việc sử dụng thiết bị đang được thử nghiệm. Sau đây là những bộ đôi thử nghiệm mà tôi biết và đã sử dụng.

Hình nộm

Hình nộm thường là các giá trị không quan trọng nhưng cần thiết để gọi một phương thức mà chúng tôi đang thử nghiệm. Giá trị có thể không liên quan vì nó không ảnh hưởng đến thử nghiệm nhưng cần tồn tại để gọi phương thức. Các tham số của chúng tôi là một ví dụ phổ biến về điều này:

[Test]
public void GetOccurrences_NewDateTimePattern_HasZeroOccurrences()
{
  var pattern = new DateTimePattern();
  var dummy;
 
  var count = pattern.GetOccurrences(out dummy);
 
  Assert.AreEqual(0, count);
}

Stubs

Stub là các phương thức được mã hóa cứng trả về một câu trả lời mong đợi và không quan tâm đến các đối số của phương thức hoặc trạng thái của bất kỳ đối tượng nào, vì vậy không hoạt động bình thường. Chúng có thể là các hàm ẩn danh được chuyển trực tiếp đến một phương thức trên đơn vị mà chúng tôi đang thử nghiệm, trong trường hợp đó, chúng tôi đang kiểm tra xem đơn vị đó có hoạt động chính xác hay không khi hàm trả về kết quả được mã hóa cứng đó. Sơ khai có thể là một phương thức được triển khai như một yêu cầu của một giao diện mà đơn vị được kiểm tra cần gọi. Ví dụ, một sơ khai kiểm tra sự tồn tại của một tệp có thể luôn trả về true nếu chúng tôi không thực sự sử dụng hệ thống tệp trong khi kiểm tra đơn vị:

public bool FileExists(string path)
{
  return true;
}

Đồ giả

Giả mạo là các đối tượng hoàn toàn có chức năng thường triển khai một giao diện hoặc ít nhất là mở rộng một lớp trừu tượng mà đơn vị được kiểm tra cần. Giả mạo là những triển khai nhanh chóng và bẩn thỉu sẽ không được sử dụng ngoài vai trò là một bài kiểm tra đơn vị kép. Có rất nhiều ví dụ điển hình về hàng giả, gần đây tôi đã sử dụng hàng giả để triển khai giao diện IRedisClient (kho lưu trữ cấu trúc dữ liệu). Thay vì thực sự chạy Redis, dữ liệu được lưu trữ trong cấu trúc dữ liệu C # theo cách rất đơn giản. Các đơn vị đang được kiểm tra yêu cầu IRedisClient để hoạt động có thể được cung cấp một trường hợp giả mạo đó thay vì dựa vào Redis để chạy:

public class FakeRedisClient : IRedisClient
{
  private Dictionary<string, object> _redis = new Dictionary<string,object>();
 
  // and so on
 
  public void AddItemToSet(string setId, string item)
  {
    object obj;
    _redis.TryGetValue(setId, out obj);
    HashSet set = (HashSet)obj;
    if (set == null)
    {
      set = new HashSet();
      _redis[setId] = set;
    }
    set.Add(item);
  }
 
  // and so forth
}

Mocks

Mocks được sử dụng để kiểm tra hành vi hoặc việc thực hiện nội bộ, thay vì trạng thái. Bạn sử dụng chúng để xác minh, ví dụ, một số chức năng nhất định đã được gọi hoặc không được gọi do gọi phương thức bạn đang thử nghiệm. Chế độ giả thường đạt được với sự trợ giúp của các khuôn khổ chế tạo như Moq cho .NET. Bạn mô phỏng một đối tượng từ một giao diện cung cấp cho bạn một đối tượng cụ thể để làm việc. Là một phần của thiết lập, bạn có thể đính kèm các bit logic hoặc các giá trị được mã hóa cứng thay cho các phương thức hoặc thuộc tính. Điều này được thực hiện để đối tượng được chế tạo hoạt động chính xác khi đơn vị được kiểm tra sử dụng. Một đối tượng giả mạo có thể giống về mặt chức năng với đồ giả và có khả năng được sử dụng để kiểm tra trạng thái. Tôi nghiêm túc sử dụng mocks chỉ trong một vài trường hợp tôi viết các bài kiểm tra triển khai.

[SetUp]
public void SetUp()
{
  _customer = new Mock();
  _customer.Setup(c => c.PaymentID).Returns(1);
}
 
[Test]
public void CreateSubscription_NewCustomer_ExistingSubscriptionsAreChecked()
{
  var service = CreatePaymentSubscriptionService();
 
  var subscription = service.CreateSubscription(_customer);
 
  _customer.Verify(c => c.GetSubscriptions());
}

Vì vậy, cá nhân tôi đứng ở đâu?

Tôi chủ yếu viết các bài kiểm tra cho mã mà tôi viết và điền vào các bài kiểm tra còn thiếu cho mã mà tôi có quyền truy cập. Do đó, tôi chắc chắn là một người thử nghiệm hộp trắng. Đối với việc triển khai trạng thái và trạng thái, tôi chủ yếu tập trung vào trạng thái thử nghiệm. Tôi muốn đảm bảo rằng một đơn vị đang hoạt động chính xác như được quan sát bên ngoài mà không có giả định nào về cách nó hoàn thành công việc. Đây là cách các đơn vị khác trong một chương trình đang chạy sẽ nhìn thấy và tương tác với nó. Điều này nghe có vẻ giống như kết quả của kiểm tra hộp đen sẽ đạt được. Kiểm tra hộp trắng mang lại cho tôi lợi ích bổ sung là đảm bảo tất cả các đường dẫn mã và bất kỳ trường hợp cạnh nào bị che khuất đều được xác nhận.

Bạn có thể thấy rằng việc thử nghiệm chủ yếu theo tiểu bang khiến việc triển khai tự do thay đổi nhiều hơn. Bất kỳ phần nào của quá trình triển khai không được kết hợp với API chẳng hạn như cấu trúc dữ liệu được sử dụng, cách dữ liệu được định dạng, các khóa được sử dụng để lưu trữ dữ liệu, v.v. thì sẽ không đề cập đến những điều này trong các bài kiểm tra trạng thái của tôi. Bằng cách cập nhật quá trình triển khai nội bộ mà không ảnh hưởng đến API hoặc kết quả mong đợi, bạn sẽ có thể chạy các bài kiểm tra đơn vị để tìm thấy đơn vị vẫn hoạt động như bình thường.

Nếu chủ yếu kiểm tra bằng cách triển khai, các bài kiểm tra đơn vị trở nên kết hợp chặt chẽ với việc triển khai nội bộ đó có thể làm cho các bài kiểm tra khá giòn. Việc thay đổi cách triển khai sẽ phá vỡ các bài kiểm tra và yêu cầu viết lại chúng.

Điều đó nói rằng, một vài thử nghiệm triển khai ở đây và ở đó có thể hữu ích. Ví dụ: có một đơn vị chứa cấu trúc dữ liệu được sử dụng làm bộ đệm ẩn bên trong để giải quyết các vấn đề về hiệu suất. Đây hoàn toàn là một quyết định thực hiện mà bên ngoài đơn vị không cần biết. API của đơn vị không tiết lộ bất kỳ điều gì liên quan đến trạng thái của bộ nhớ cache nội bộ này, vì vậy không có cách nào để khẳng định điều đó. Để yên tâm, bạn có thể kiểm tra xem bộ nhớ đệm này có được quản lý tốt hay không, chẳng hạn như xóa các mục khi thích hợp. Để làm điều này, có thể tốt hơn là sử dụng thử nghiệm triển khai, nếu bạn không muốn phơi bày sự tồn tại của bộ nhớ đệm theo một cách nào đó để kiểm tra trạng thái của nó.

Tôi không nghiêm khắc buộc bản thân chỉ kiểm tra theo trạng thái và bỏ qua kiểm tra triển khai. Tôi nghĩ rằng thật tốt khi biết những cách tiếp cận nào có sẵn và chọn công cụ phù hợp cho công việc.

Bạn có bất kỳ ví dụ kiểm tra đơn vị hoặc nguyên tắc cá nhân nào mà bạn muốn chia sẻ không? Cho chúng tôi biết trong các ý kiến ​​dưới đây.

3 hữu ích 0 bình luận 8.1k xem chia sẻ

Có thể bạn quan tâm

loading