2010-05-14 29 views
5

Tôi có hai thông số rất giống nhau cho hai hành động điều khiển rất giống nhau: VoteUp (int id) và VoteDown (int id). Các phương thức này cho phép người dùng bỏ phiếu một bài đăng lên hoặc xuống; giống như chức năng bỏ phiếu lên/xuống cho các câu hỏi StackOverflow. Các thông số kỹ thuật bao gồm:DRY-ing thông số kỹ thuật tương tự cho hành động điều khiển ASP.NET MVC với MSpec (hướng dẫn BDD)

VoteDown:

[Subject(typeof(SomeController))] 
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext 
{ 
    Establish context =() => 
    { 
     post = PostFakes.VanillaPost(); 
     post.Votes = 10; 

     session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post); 
     session.Setup(s => s.CommitChanges()); 
    }; 

    Because of =() => result = controller.VoteDown(1); 

    It should_decrement_the_votes_of_the_post_by_1 =() => suggestion.Votes.ShouldEqual(9); 
    It should_not_let_the_user_vote_more_than_once; 
} 

VoteUp:

[Subject(typeof(SomeController))] 
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext 
{ 
    Establish context =() => 
    { 
     post = PostFakes.VanillaPost(); 
     post.Votes = 0; 

     session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post); 
     session.Setup(s => s.CommitChanges()); 
    }; 

    Because of =() => result = controller.VoteUp(1); 

    It should_increment_the_votes_of_the_post_by_1 =() => suggestion.Votes.ShouldEqual(1); 
    It should_not_let_the_user_vote_more_than_once; 
} 

Vì vậy, tôi có hai câu hỏi:

  1. Làm thế nào tôi nên đi về DRY-ing hai thông số kỹ thuật này ? Nó thậm chí còn được khuyến khích hay tôi nên thực sự có một spec cho mỗi hành động điều khiển? Tôi biết tôi thường nên, nhưng điều này cảm thấy như lặp đi lặp lại bản thân mình rất nhiều.

  2. Có cách nào để triển khai số It thứ hai trong cùng một thông số kỹ thuật không? Lưu ý rằng It should_not_let_the_user_vote_more_than_once; yêu cầu tôi chỉ định để gọi controller.VoteDown(1) hai lần. Tôi biết dễ nhất là tạo ra một spec riêng cho nó quá, nhưng nó muốn được sao chép và dán cùng một mã một lần nữa ...

tôi vẫn nhận được hang của BDD (và MSpec) và nhiều lần không rõ tôi nên đi theo cách nào, hoặc những thực hành hay hướng dẫn tốt nhất cho BDD là gì. Bất kỳ trợ giúp sẽ được đánh giá cao.

Trả lời

8

Tôi sẽ bắt đầu với câu hỏi thứ hai của bạn: Có một tính năng trong MSpec sẽ giúp sao chép các trường It, nhưng trong trường hợp này, tôi khuyên bạn không nên sử dụng nó. Tính năng này được gọi là hành vi và đi một cái gì đó như thế này:

[Subject(typeof(SomeController))] 
public class When_user_clicks_the_vote_up_button_on_a_post : SomeControllerContext 
{ 
    // Establish and Because cut for brevity. 

    It should_increment_the_votes_of_the_post_by_1 = 
     () => suggestion.Votes.ShouldEqual(1); 

    Behaves_like<SingleVotingBehavior> a_single_vote; 
} 

[Subject(typeof(SomeController))] 
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext 
{ 
    // Establish and Because cut for brevity. 

    It should_decrement_the_votes_of_the_post_by_1 = 
     () => suggestion.Votes.ShouldEqual(9); 

    Behaves_like<SingleVotingBehavior> a_single_vote; 
} 

[Behaviors] 
public class SingleVotingBehavior 
{ 
    It should_not_let_the_user_vote_more_than_once = 
     () => true.ShouldBeTrue(); 
} 

Bất kỳ lĩnh vực bạn muốn khẳng định trên trong lớp hành vi cần phải được protected static trong cả hành vi và lớp ngữ cảnh. Mã nguồn MSpec chứa another example.

Tôi khuyên bạn không nên sử dụng các hành vi vì ví dụ của bạn thực sự chứa bốn ngữ cảnh. Khi tôi nghĩ về những gì bạn đang cố gắng để thể hiện với mã về "ý nghĩa kinh doanh", bốn trường hợp khác nhau xuất hiện:

  • tài phiếu lên cho lần đầu tiên
  • tài phiếu xuống cho lần đầu tiên
  • tài phiếu lên lần thứ hai
  • tài phiếu xuống lần thứ hai

đối với mỗi trong bốn kịch bản khác nhau tôi sẽ tạo ra một bối cảnh riêng biệt mà mô tả chặt chẽ cách hệ thống nên behav e. Bốn lớp ngữ cảnh là rất nhiều mã trùng lặp, đưa chúng ta đến câu hỏi đầu tiên của bạn.

Trong "mẫu" bên dưới có một lớp cơ sở với các phương thức có tên mô tả về những gì sẽ xảy ra khi bạn gọi cho chúng. Vì vậy, thay vì dựa vào thực tế là MSpec sẽ tự động gọi các trường "được thừa kế" Because, bạn đặt thông tin về những gì quan trọng đối với ngữ cảnh ngay trong Establish. Từ kinh nghiệm của tôi điều này sẽ giúp bạn rất nhiều sau này khi bạn đọc một spec trong trường hợp nó không thành công. Thay vì điều hướng phân cấp lớp, bạn ngay lập tức có được cảm giác cho quá trình thiết lập diễn ra.

Trên một lưu ý liên quan, lợi thế thứ hai là bạn chỉ cần một lớp cơ sở, bất kể có bao nhiêu bối cảnh khác nhau với thiết lập cụ thể bạn lấy được.

public abstract class VotingSpecs 
{ 
    protected static Post CreatePostWithNumberOfVotes(int votes) 
    { 
     var post = PostFakes.VanillaPost(); 
     post.Votes = votes; 
     return post; 
    } 

    protected static Controller CreateVotingController() 
    { 
     // ... 
    } 

    protected static void TheCurrentUserVotedUpFor(Post post) 
    { 
     // ... 
    } 
} 

[Subject(typeof(SomeController), "upvoting")] 
public class When_a_user_clicks_the_vote_up_button_on_a_post : VotingSpecs 
{ 
    static Post Post; 
    static Controller Controller; 
    static Result Result ; 

    Establish context =() => 
    { 
     Post = CreatePostWithNumberOfVotes(0); 

     Controller = CreateVotingController(); 
    }; 

    Because of =() => { Result = Controller.VoteUp(1); }; 

    It should_increment_the_votes_of_the_post_by_1 = 
     () => Post.Votes.ShouldEqual(1); 
} 


[Subject(typeof(SomeController), "upvoting")] 
public class When_a_user_repeatedly_clicks_the_vote_up_button_on_a_post : VotingSpecs 
{ 
    static Post Post; 
    static Controller Controller; 
    static Result Result ; 

    Establish context =() => 
    { 
     Post = CreatePostWithNumberOfVotes(1); 
     TheCurrentUserVotedUpFor(Post); 

     Controller = CreateVotingController(); 
    }; 

    Because of =() => { Result = Controller.VoteUp(1); }; 

    It should_not_increment_the_votes_of_the_post_by_1 = 
     () => Post.Votes.ShouldEqual(1); 
} 

// Repeat for VoteDown(). 
+0

Cảm ơn một lần nữa. Tôi biết về các hành vi (từ ví dụ mã nguồn MSpec de thực sự), nhưng nó cảm thấy như tôi sẽ phải shoehorn nó vào kịch bản của tôi; nó không cảm thấy tự nhiên. Câu trả lời rực rỡ, cảm ơn một triệu. –

0

Có thể bạn có thể tạo ra phần lớn sự lặp lại bằng cách chỉ bao thanh toán số thiết lập của các thử nghiệm. Không có lý do thực sự tại sao đặc tả upvote nên đi từ 0 đến 1 bỏ phiếu thay vì 10 đến 11, vì vậy bạn có thể rất tốt có một thói quen thiết lập duy nhất. Điều đó một mình sẽ để lại cả hai thử nghiệm tại 3 dòng mã (hoặc 4, nếu bạn cần phải gọi phương pháp thiết lập bằng tay ...).

Đột nhiên, các thử nghiệm của bạn chỉ bao gồm việc thực hiện hành động và xác minh kết quả. Và nó có cảm thấy lặp đi lặp lại hay không, tôi khuyên bạn nên kiểm tra một điều cho mỗi bài kiểm tra, đơn giản vì bạn muốn biết chính xác lý do tại sao một bài kiểm tra thất bại khi bạn refactor một cái gì đó trong một tháng và chạy tất cả các bài kiểm tra trong giải pháp.

CẬP NHẬT (xem ý kiến ​​để biết chi tiết)

private WhateverTheTypeNeedsToBe vote_count_context =() => 
{ 
    post = PostFakes.VanillaPost(); 
    post.Votes = 10; 

    session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post); 
    session.Setup(s => s.CommitChanges()); 
}; 

Và trong đặc tả của bạn:

Establish context = vote_count_context; 
... 

thể làm việc này?

+0

Điểm tốt. Vấn đề là, các thông số này kế thừa từ 'SomeControllerContext', trong đó thiết lập chính xảy ra trong (trong một' '' '' '' '=' {} '' lớn và 'session.Setup (...)' xảy ra trong cả hai thông số kỹ thuật không thể được đặt trong thiết lập chính bởi vì nó sẽ can thiệp vào các thông số kỹ thuật khác kế thừa từ SomeControllerContext. Tôi đoán những gì bạn đề nghị sẽ làm việc nếu tôi đã làm một cái gì đó như: 'public class VoteSetup: SomeControllerContext {Establish ...}' và sau đó 'public class When_user_clicks_the_vote_down_button_on_a_post: VoteSetup {// spec}'. Tất cả các thành lập trong chuỗi sẽ được thực hiện? –

+0

@Spapaseit, tôi không phải là chuyên gia về MSpec (tôi chưa bao giờ thực sự sử dụng nó, nhưng tôi càng ngày càng quan tâm ...) nhưng dường như với tôi rằng bạn có thể xác định một trường riêng (tĩnh nếu cần) với giá trị của biểu thức lambda. Tôi sẽ chỉnh sửa một ví dụ mã vào bài viết của tôi ... –

1

@Tomas Lycken,

Tôi không có guru MSpec một trong hai, nhưng tôi (như được nêu ra hạn chế) Trải nghiệm thực tế với nó dẫn tôi nhiều hơn đối với một cái gì đó như thế này:

public abstract class SomeControllerContext 
{ 
    protected static SomeController controller; 
    protected static User user; 
    protected static ActionResult result; 
    protected static Mock<ISession> session; 
    protected static Post post; 

    Establish context =() => 
    { 
     session = new Mock<ISession>(); 
      // some more code 
    } 
} 

/* many other specs based on SomeControllerContext here */ 

[Subject(typeof(SomeController))] 
public abstract class VoteSetup : SomeControllerContext 
{ 
    Establish context =() => 
    { 
     post= PostFakes.VanillaPost(); 

     session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post); 
     session.Setup(s => s.CommitChanges()); 
    }; 
} 

[Subject(typeof(SomeController))] 
public class When_user_clicks_the_vote_up_button_on_a_post : VoteSetup 
{ 
    Because of =() => result = controller.VoteUp(1); 

    It should_increment_the_votes_of_the_post_by_1 =() => post.Votes.ShouldEqual(11); 
    It should_not_let_the_user_vote_more_than_once; 
} 

[Subject(typeof(SomeController))] 
public class When_user_clicks_the_vote_down_button_on_a_post : VoteSetup 
{ 
    Because of =() => result = controller.VoteDown(1); 

    It should_decrement_the_votes_of_the_post_by_1 =() => post.Votes.ShouldEqual(9); 
    It should_not_let_the_user_vote_more_than_once; 
} 

Đó là về cơ bản những gì tôi đã có nhưng thêm các thay đổi dựa trên câu trả lời của bạn (tôi không có lớp VoteSetup.)

Câu trả lời của bạn đã dẫn tôi đi đúng hướng. Tôi vẫn hy vọng sẽ có thêm một số câu trả lời để thu thập các quan điểm khác về chủ đề này ...:)

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