2013-07-25 19 views
6

Làm cách nào tôi có thể thiết lập kiểm tra xác định để xác minh rằng các mục trong danh sách được sắp xếp?Thứ tự kiểm tra đơn vị của các mục trong danh sách

Trước tiên tôi đã làm như sau:

public void SyncListContainsSortedItems(
    [Frozen] SyncItem[] expected, 
    SyncItemList sut) 
{ 
    Assert.Equal(expected.OrderBy(x => x.Key).First(), sut.First()); 
} 

Nhưng như với tất cả các bài kiểm tra tốt, lần đầu tiên tôi nhìn cho một thất bại trước khi sửa đổi mã của tôi. Tất nhiên, điều này thành công như là may mắn sẽ có nó, và sau đó thất bại. Vì vậy, thất bại ban đầu của tôi không phải là xác định.

Thứ hai tôi đã làm như sau, suy nghĩ, 'chắc chắn điều này sẽ đảm bảo một sự thất bại':

public void SyncListContainsSortedItems(
    [Frozen] SyncItem[] seed, 
    SyncItemList sut) 
{ 
    var expected = seed.OrderByDescending(x => x.Key); 
    Assert.Equal(expected.OrderBy(x => x.Key).First(), sut.First()); 
} 

Trước sự ngạc nhiên của tôi nó cũng không đưa ra một sự thất bại xác định. Tôi nhận ra đó là bởi vì hạt giống đông lạnh có thể được tạo ra tự nhiên theo thứ tự giảm dần để bắt đầu, vì vậy tôi thực sự không cải thiện tình hình nào.

Hiện tại, việc triển khai của tôi không đặt các mục đi qua hàm tạo. Làm cách nào để tôi thiết lập đường cơ sở vững chắc cho thử nghiệm của mình?

Thông tin bổ sung Mã danh sách mục đồng bộ hóa được hiển thị bên dưới. Của nó không nhiều như nó là thiết kế tôi đang khám phá:

public class SyncItemList : List<SyncItem> 
{ 
    public SyncItemList(SyncItem[] input) 
    { 
     foreach (var item in input) { this.Add(item); } 
    } 
} 

Cập nhật Tôi đã phát triển thử nghiệm. hoạt động sau nhưng với độ dài cao.

public void SyncListContainsSortedItems(IFixture fixture, List<SyncItem> seed) 
{ 
    var seconditem = seed.OrderBy(x => x.Key).Skip(1).First(); 
    seed.Remove(seconditem); 
    seed.Insert(0, seconditem); 
    var seedArray = seed.ToArray(); 

    var ascending = seedArray.OrderBy(x => x.Key).ToArray(); 
    var descending = seedArray.OrderByDescending(x => x.Key).ToArray(); 
    Assert.NotEqual(ascending, seedArray); 
    Assert.NotEqual(descending, seedArray); 

    fixture.Inject<SyncItem[]>(seedArray); 
    var sut = fixture.Create<SyncItemList>(); 

    var expected = ascending; 
    var actual = sut.ToArray(); 
    Assert.Equal(expected, actual); 
} 

Một cách đơn giản để thay đổi thực hiện của tôi để làm cho nó vượt qua là để kế thừa từ SortedSet<SyncItem> thay vì List<SyncItem>.

+0

Hình thức 'SyncItemList' trông như thế nào? –

+0

Bạn biết điều gì sẽ thực sự tuyệt vời nếu có thuộc tính và loại yêu cầu không được sắp xếp, giống như chuỗi hữu hạn. Bạn sẽ chỉ cần 3 hoặc nhiều hơn các mặt hàng độc đáo và bạn có thể đảm bảo một chuỗi thứ tự. – cocogorilla

+0

Tôi có thể hỏi bạn đang sử dụng phiên bản AutoFixture nào không? [Số ngẫu nhiên] (https://github.com/AutoFixture/AutoFixture/wiki/AutoFixture-3.0-Release-Notes#numbers-are-random) trong AutoFixture 3. –

Trả lời

10

Có nhiều cách khác nhau để giải quyết vấn đề này.

phiên bản Imperative

Dưới đây là một phiên bản bắt buộc đơn giản hơn so với cái được cung cấp trong OP:

[Fact] 
public void ImperativeTest() 
{ 
    var fixture = new Fixture(); 
    var expected = fixture.CreateMany<SyncItem>(3).OrderBy(si => si.Key); 
    var unorderedItems = expected.Skip(1).Concat(expected.Take(1)).ToArray(); 
    fixture.Inject(unorderedItems); 

    var sut = fixture.Create<SyncItemList>(); 

    Assert.Equal(expected, sut); 
} 

Trong khi the default number of many items is 3, tôi nghĩ rằng nó tốt hơn để gọi nó ra một cách rõ ràng trong trường hợp thử nghiệm này. Thuật toán xáo trộn được sử dụng ở đây tận dụng thực tế là sau khi đặt hàng một chuỗi gồm ba phần tử (khác biệt), di chuyển phần tử đầu tiên về phía sau phải dẫn đến danh sách không có thứ tự.

Tuy nhiên, vấn đề với cách tiếp cận này là nó dựa vào biến đổi fixture, do đó rất khó để tái cấu trúc một cách tiếp cận khai báo hơn.

tùy chỉnh phiên bản

Trong một nỗ lực nhằm cấu trúc lại một phiên bản declarative hơn, trước tiên bạn có thể gói gọn các thuật toán xáo trộn trong a Customization:

public class UnorderedSyncItems : ICustomization 
{ 
    public void Customize(IFixture fixture) 
    { 
     fixture.Customizations.Add(new UnorderedSyncItemsGenerator()); 
    } 

    private class UnorderedSyncItemsGenerator : ISpecimenBuilder 
    { 
     public object Create(object request, ISpecimenContext context) 
     { 
      var t = request as Type; 
      if (t == null || 
       t != typeof(SyncItem[])) 
       return new NoSpecimen(request); 

      var items = ((IEnumerable)context 
       .Resolve(new FiniteSequenceRequest(typeof(SyncItem), 3))) 
       .Cast<SyncItem>(); 
      return items.Skip(1).Concat(items.Take(1)).ToArray(); 
     } 
    } 
} 

Giải quyết new FiniteSequenceRequest(typeof(SyncItem), 3)) chỉ đơn giản là một cách yếu ớt, đánh máy (không chung chung) tạo ra một chuỗi hữu hạn gồm SyncItem trường hợp; đó là những gì CreateMany<SyncItem>(3) làm sau hậu trường.

này cho phép bạn cấu trúc lại các thử nghiệm để:

[Fact] 
public void ImperativeTestWithCustomization() 
{ 
    var fixture = new Fixture().Customize(new UnorderedSyncItems()); 
    var expected = fixture.Freeze<SyncItem[]>().OrderBy(si => si.Key); 

    var sut = fixture.Create<SyncItemList>(); 

    Assert.Equal(expected, sut); 
} 

Thông báo việc sử dụng các phương pháp Freeze. Điều này là cần thiết, vì tùy chọn UnorderedSyncItems chỉ thay đổi cách thức SyncItem[] phiên bản được tạo; nó vẫn tạo ra một mảng mới mỗi khi nó nhận được một yêu cầu để làm như vậy. Freeze đảm bảo rằng cùng một mảng đang được sử dụng lại mọi lúc - cũng như khi fixture tạo phiên bản sut.

thử nghiệm Công ước dựa trên

Các thử nghiệm trên có thể được refactored để một, kiểm tra ước dựa trên khai báo, bởi sự ra đời của một [UnorderedConventions] thuộc tính:

public class UnorderedConventionsAttribute : AutoDataAttribute 
{ 
    public UnorderedConventionsAttribute() 
     : base(new Fixture().Customize(new UnorderedSyncItems())) 
    { 
    } 
} 

này chỉ đơn giản là keo declarative để áp dụng tùy chọn UnorderedSyncItems. Thử nghiệm hiện tại trở thành:

[Theory, UnorderedConventions] 
public void ConventionBasedTest(
    [Frozen]SyncItem[] unorderedItems, 
    SyncItemList sut) 
{ 
    var expected = unorderedItems.OrderBy(si => si.Key); 
    Assert.Equal(expected, sut); 
} 

Lưu ý việc sử dụng thuộc tính [UnorderedSyncItems][Frozen].

Bài kiểm tra này rất ngắn gọn, nhưng có thể không phải là những gì bạn đang theo dõi. Vấn đề là sự thay đổi của hành vi bây giờ được ẩn đi trong thuộc tính [UnorderedSyncItems], do đó, nó khá tiềm ẩn những gì đang xảy ra. Tôi thích sử dụng cùng một Tùy chỉnh như một tập hợp các quy ước cho toàn bộ bộ thử nghiệm, vì vậy tôi không muốn giới thiệu các biến thể của trường hợp thử nghiệm ở cấp độ này. Tuy nhiên, nếu quy ước của bạn nêu rõ rằng SyncItem[] trường hợp nên luôn là không có thứ tự, thì quy ước này là tốt.

Tuy nhiên, nếu bạn chỉ muốn sử dụng mảng không theo thứ tự cho một số trường hợp thử nghiệm, việc sử dụng thuộc tính [AutoData] không phải là cách tiếp cận tối ưu nhất.

test Declarative

Nó muốn được tốt đẹp nếu bạn chỉ đơn giản có thể áp dụng một thuộc tính tham số cấp, giống như thuộc tính [Frozen] - có lẽ kết hợp chúng, như [Unordered][Frozen]. Tuy nhiên, cách tiếp cận này không hiệu quả.

Lưu ý từ các ví dụ trước rằng các yêu cầu đơn hàng. Bạn phải áp dụng UnorderedSyncItems trước khi đóng băng, vì nếu không, mảng đang được cố định có thể không được đảm bảo không có thứ tự.

Sự cố với thuộc tính mức tham số [Unordered][Frozen] là, trong khi biên dịch. Khuôn khổ .NET không đảm bảo thứ tự các thuộc tính khi thư viện kết dính Tự động xUnit.net đọc và áp dụng các thuộc tính.

Thay vào đó, bạn có thể định nghĩa một thuộc tính duy nhất để áp dụng, như thế này:

public class UnorderedFrozenAttribute : CustomizeAttribute 
{ 
    public override ICustomization GetCustomization(ParameterInfo parameter) 
    { 
     return new CompositeCustomization(    
      new UnorderedSyncItems(), 
      new FreezingCustomization(parameter.ParameterType)); 
    } 
} 

(FreezingCustomization cung cấp việc thực hiện cơ bản của thuộc tính [Frozen].)

này cho phép bạn viết bài kiểm tra này:

[Theory, AutoData] 
public void DeclarativeTest(
    [UnorderedFrozen]SyncItem[] unorderedItems, 
    SyncItemList sut) 
{ 
    var expected = unorderedItems.OrderBy(si => si.Key); 
    Assert.Equal(expected, sut); 
} 

Lưu ý rằng thử nghiệm khai báo này sử dụng thuộc tính mặc định [AutoData] không có bất kỳ Customi nào zations, bởi vì sự xáo trộn hiện đang được áp dụng bởi thuộc tính [UnorderedFrozen] ở cấp tham số.

Điều này cũng sẽ cho phép bạn sử dụng một tập hợp (các) quy ước toàn bộ thử nghiệm khác được đóng gói trong thuộc tính [AutoData] -derived và vẫn sử dụng [UnorderedFrozen] làm cơ chế chọn tham gia.

+2

Wow, Mark, tôi đánh giá cao sự đầu tư vào câu trả lời này. Sự tiến triển thực sự có giá trị đối với tôi trong việc hiểu các lớp để tự động kết hợp và làm thế nào tôi có thể bắt đầu lột chúng lại để thực sự có được những gì tôi muốn trong từng loại kịch bản mà tôi xử lý. Kudo và cảm ơn bạn! – cocogorilla

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