2014-06-23 24 views
12

Tôi viết thử nghiệm cho một thats lớp quản lý cây của các đối tượng Tag:Testing Di phương pháp mà không một lời kêu gọi bổ sung phương thức

public class Tag 
{ 
    public virtual int Id { get; set; } 
    public virtual string Description{ get; set; } 
    private IList<Tag> children = new List<Tag>(); 
    public virtual IEnumerable<Tag> Children 
    { 
     get {return children .ToArray();} 
    } 
    public void AddChildTag(Tag child) 
    { 
     children.Add(child); 
    } 
    public void RemoveChildTag(Tag child) 
    { 
     children.Remove(child); 
    } 
} 

Như bạn có thể thấy phương thức duy nhất để thiết lập thuộc tính cha mẹ là thông qua phương pháp AddChildTag và điều này là chính xác những gì tôi muốn, vấn đề của tôi là trong thử nghiệm đơn vị: vì mọi thử nghiệm nên là nguyên tử, làm thế nào tôi có thể thử nghiệm phương pháp RemoveChildTag?

Chỉ cách tôi thấy là một cuộc gọi đến phương thức thêm và sau đó để loại bỏ, nhưng theo cách này nếu Thêm dưới dạng một số lỗi, ngay cả khi kiểm tra xóa sẽ không thành công, do đó nguyên tử bị mất.

Làm cách nào để thực hiện điều đó?

EDIT
Tôi đã gỡ bỏ tài sản cha mẹ từ đối tượng Tag, kể từ khi tôi không sử dụng nó Một số thử nghiệm theo giải pháp sử dụng NUnit và FluentAssertion

[Test] 
    public void AddChildTagAddsChildren() 
    { 
     //Arrange 
     Tag parent = new Tag(); 
     Tag child = new Tag(); 
     //Act 
     parent.AddChildTag(child); 
     //Assert 
     parent.Children.Should().Contain(child); 
    } 
    [Test] 
    public void RemoveChildTagRemovesAddedChildren() 
    { 
     //Arrange 
     Tag parent = new Tag(); 
     Tag child = new Tag(); 
     parent.AddChildTag(child); 
     //Act 
     parent.RemoveChildTag(child); 
     //Assert 
     parent.Children.Should().NotContain(child); 
    } 
    [Test] 
    public void RemoveChildTagThrowsNothingWhenNoChild() 
    { 
     //Arrange 
     Tag parent= new Tag(); 
     Tag child= new Tag(); 
     //Act 
     Action RemoveChild =() => parent.RemoveChildTag(child); 
     //Assert 
     RemoveChild.ShouldNotThrow(); 
    } 
+1

Lưu ý nhỏ: Cũng đáng để kiểm tra hành vi gọi 'Xóa' mà không gọi' Thêm' để kiểm tra xem không có ngoại lệ nào được ném hoặc ngoại lệ chính xác được ném với trạng thái chính xác. – Jonny

+0

Vì loại bỏ phụ thuộc vào 'add', nếu kiểm tra vượt qua bạn biết rằng' nếu thêm công trình, thì hãy xóa tác phẩm'. Khi 'add' được kiểm tra, bạn sẽ biết' remove' hoạt động. – Cruncher

+0

@Jonny, hãy xem bình luận của tôi về câu trả lời của Lukazoid –

Trả lời

21

Bài kiểm tra đơn vị của bạn phải phản ánh các trường hợp sử dụng thực tế của lớp học của bạn. Người tiêu dùng của bạn sẽ sử dụng phương pháp RemoveChildTag như thế nào? Điều gì có ý nghĩa hơn? Đó là cách bạn muốn sử dụng bộ sưu tập các đối tượng?

var parent = new Tag(); 
// later 
parent.RemoveChildTag(child); 

... hoặc

var parent = new Tag(); 
parent.AddChildTag(child); 
// later 
parent.RemoveChildTag(child); 

người tiêu dùng của bạn sẽ loại bỏ đối tượng họ trước thêm. Đây là trường hợp sử dụng của bạn, "Xóa các yếu tố xóa trước đây" (lưu ý rằng nó cũng tạo ra tên phương pháp thử nghiệm tuyệt vời).

AddRemove các phương pháp thường bổ sung - bạn không thể kiểm tra cái này mà không có phương pháp khác.

+0

@ gt.guybrush Tôi nghĩ rằng chỉ aswer này có thể là câu trả lời đúng, phá vỡ sự đóng gói cho mục đích thử nghiệm không phải là cách tiếp cận chính xác – InferOn

14

Có một số cách để kiểm tra phương pháp Remove:

  1. Mocking - giả lập cấu trúc dữ liệu của bạn và gọi loại bỏ, vì vậy bạn có thể thử nghiệm gọi đúng phương thức
  2. Thừa kế - tạo children được bảo vệ. Lớp kiểm tra của bạn sẽ được kế thừa từ lớp Tag. Bây giờ bạn có thể init children thành viên vì vậy chúng tôi có thể kiểm tra loại bỏ
  3. Sử dụng Add Phương pháp

Tôi nghĩ tùy chọn 3 là tốt nhất, nó là ok sử dụng các phương pháp khác trong kiểm tra đơn vị của bạn, nếu Add có một số lỗi, nhiều hơn thì 1 kiểm tra sẽ không thành công - bạn sẽ có thể hiểu những gì bạn cần sửa.

Ngoài ra, mỗi bài kiểm tra đơn vị nên kiểm tra một số hành vi cơ bản, ngay cả khi bạn cần chuẩn bị trước. Nếu những chuẩn bị thất bại, thất bại trong thử nghiệm với những ý kiến ​​có liên quan

Lựa chọn 1 - là tốt nhất khi bạn đang đạt đến bên thứ 3 - máy chủ khác, hệ thống tập tin hơn &. Vì bạn không muốn thực hiện các cuộc gọi đó trong thử nghiệm đơn vị - giả lập phản hồi.

Tùy chọn 2 - tốt nhất tôi có thể nói, là khi bạn muốn thử nghiệm phương pháp được bảo vệ/riêng tư, mà không cần thực hiện tất cả cuộc gọi "trên đường" mà mã của bạn thực hiện (phương pháp công khai thực hiện nhiều cuộc gọi gọi phương thức bạn muốn kiểm tra), vì bạn chỉ muốn kiểm tra một logic cụ thể. Nó cũng dễ sử dụng tùy chọn này khi lớp của bạn có một số trạng thái mà bạn muốn kiểm tra, mà không cần phải viết nhiều mã để đưa lớp của bạn đến trạng thái này.

+2

+1 đồng ý, tùy chọn 3 là tốt nhất – RvdK

+0

tôi đánh dấu jimmy_keen là giải pháp chỉ để đặt tên ví dụ về thử nghiệm phương pháp. Nếu tôi có thể đánh dấu hai câu trả lời cũng sẽ đánh dấu (tôi chỉ đơn giản là có thể thêm +1) –

3

Bạn có thể sử dụng một PrivateObject Class để Sắp xếp đối tượng của bạn được kiểm tra

Cho phép mã kiểm tra để gọi các phương thức và thuộc tính trên mã theo thử nghiệm rằng không thể tiếp cận, vì họ là không được công khai.

EDIT

sau đó bạn có thể nhận được quyền truy cập đầy đủ đến đối tượng bọc bởi PrivateObject.RealTypePrivateObject.Target Thuộc tính

EDIT

Dù sao, mỗi hệ thống mà phá vỡ sự phân chia và đóng gói của một lớp làm cho vô dụng cách tiếp cận hộp đen của bài kiểm tra đơn vị trong TDD và nên tránh như một chất độc :)

+0

Điều này có thể khiến ứng dụng của bạn hoạt động trong một luồng khác. Thông qua cách này child.parent không được thiết lập và có thể dẫn đến các lỗi khác.(có lẽ không phải trong trường hợp này, nhưng ở những người khác nó có thể) – RvdK

+0

@RvdK bạn hoàn toàn đúng, dù sao một PrivateObject cung cấp khả năng làm những gì được yêu cầu mà không cần các hoạt động mô phỏng phức tạp phản ánh – InferOn

+0

tôi thử: PrivateObject po = new PrivateObject (typeof (Tag)); po.SetProperty ("Cha mẹ", cha mẹ); nhưng bây giờ làm thế nào tôi có thể cast đối tượng này vào một thẻ cần tham số để gọi phương thức remove? –

2

Cách tiếp cận phổ biến để thử nghiệm đơn vị là mô hình Sắp xếp-Hành động-Xác thực.

Cá nhân tôi sẽ có hai bài kiểm tra cho phương pháp xóa, một phương pháp xóa một đứa trẻ chưa bao giờ được thêm vào, điều gì sẽ xảy ra? Có nên ném ngoại lệ không?

này sẽ như thế nào:

[Test] 
public void RemoveChildTagThrowsExceptionWhenNoChildren() 
{ 
    // Arrange 
    var tag = new Tag(); 
    var tagToRemove = new Tag(); 

    // Act & Assert 
    Expect(() => tag.RemoveChildTag(tagToRemove), Throws.ArgumentException); 
} 

Sau đó, tôi sẽ có một bài kiểm tra cho những gì sẽ xảy ra khi một đứa trẻ thêm được lấy ra:

[Test] 
public void RemoveChildTagRemovesPreviouslyAddedChild() 
{ 
    // Arrange 
    var tag = new Tag(); 
    var childTag = new Tag(); 
    tag.AddChildTag(childTag); 

    // Act 
    tag.RemoveChildTag(childTag); 

    // Assert 
    Expect(tag.Children.Contains(childTag), Is.False); 
} 

Nó có thể quan tâm cần lưu ý rằng khá rất nhiều triển khai .NET Remove trả về kết quả bool cho biết liệu có thực hiện bất kỳ thao tác xóa nào thực sự không. Xem here.

+0

tôi sử dụng một sintax tương tự nhưng với Nunit và FluentAssertion (sintax dễ đọc hơn: tag.Children.Should(). NotContain (childTag)). sự trở lại bool là thú vị nhưng tại sao thậm chí không thêm? Đối với trường hợp đầu tiên tôi không muốn ném ngoại lệ, chỉ đơn giản là tôi không làm gì cả –

Các vấn đề liên quan