2011-04-19 112 views
2

下面是我在我的测试,到目前为止:如何在单元测试中模拟字符串响应?

[TestFixture] 
public class IndividualMovieTests 
{ 
    [Test] 
    public void WebClient_Should_Download_From_Correct_Endpoint() 
    { 
     const string correctEndpoint = "http://api.rottentomatoes.com/api/public/v1.0/movies/{movie-id}.json?apikey={your-api-key}"; 
     ApiEndpoints.Endpoints["IndividualMovie"].ShouldEqual(correctEndpoint); 
    } 

    [Test] 
    public void Movie_Information_Is_Loaded_Correctly() 
    { 
     Tomato tomato = new Tomato("t4qpkcsek5h6vgbsy8k4etxdd"); 
     var movie = tomato.FindMovieById(9818); 
     movie.Title.ShouldEqual("Gone With The Wind"); 
    } 
} 

我FIndMovieById方法上线并获取一个JSON的结果,这意味着它像是打破原则背后单元测试。我有一种感觉,我必须嘲笑这个字符串响应,但我不知道如何处理这个问题。

你会怎么做这个特定的单元测试?

+2

您是否考虑过使用模拟框架?如果您要为进行外部API调用的方法进行大量的单元测试,这可能会非常有用。 – 2011-04-19 03:34:27

回答

3

在您的第二个[Test]中,除非您真的想要测试您的给定输入应始终导致“飘”,否则我建议不要关注来自您的FindMovieById方法的特定返回值。你所拥有的测试似乎是一个非常具体的测试案例,其中一个特定的输入数字会导致一个特定的输出,这个输出在你的实际数据库上运行时可能会也可能不会改变。另外,由于您不会对实际的Web服务进行测试,因此进行这种验证基本上是自我服务的 - 您并未真正测试任何内容。相反,请着重测试Tomato类如何处理参数验证(如果有的话),并且Tomato类实际调用服务以获取返回值。测试具体的输入和输出,而不是测试具体的输入和输出,以便测试该类的行为,以便将来如果有人改变它,测试应该中断以提醒他们他们可能已经破坏了工作功能。

例如,如果您有输入验证,则可以测试您的Tomato类是否检测到无效输入时引发异常。

假设您的Tomato类具有用于请求和检索结果的某种Web客户端功能,您可以插入一些实际Web代码的存根实现,或者模拟实现来确保Tomato实际上调用适当的Web客户端代码来请求和处理响应。

2

首先,你可能不需要模拟测试你的代码。例如,如果您只是测试您可以将JSON反序列化为Movie对象,那么可以通过在Movie类上测试公开或内部ParseJSON秒来执行此操作。

但是,既然您在问关于嘲笑,下面是您可以使用模拟来编写此测试的一种方法的快速概述。正如它所写,Movie_Information_Is_Loaded_Correctly()看起来像一个集成测试。为了把它变成一个单元测试,你可以模拟出Tomato类所做的web请求。一种方法是创建一个ITomatoWebRequester接口,并将其作为参数传递给构造函数中的Tomato类。然后,您可以模拟ITomatoWebRequester以返回您期望的网络响应,然后您可以测试Tomato类正确分析该响应。

的代码可能是这个样子:

public class Tomato 
{ 
    private readonly ITomatoWebRequester _webRequester; 
    public Tomato(string uniqueID, ITomatoWebRequester webRequester) 
    { 
     _webRequester = webRequester; 
    } 

    public Movie FindMovieById(int movieID) 
    { 
     var responseJSON = _webRequester.GetMovieJSONByID(movieID); 
     //The next line is what we want to unit test 
     return Movie.Parse(responseJSON); 
    } 
} 

public interface ITomatoWebRequester 
{ 
    string GetMovieJSONByID(int movieID); 
} 

为了测试,你可以使用像起订量嘲弄的框架来创建一个ITomatoWebRequester将返回你所期望的结果。要做到这一点与起订量下面的代码应该工作:

[Test] 
public void Movie_Information_Is_Loaded_Correctly() 
{ 
    var mockWebRequester = new Moq.Mock<ITomatoWebRequester>(); 
    var myJson = "enter json response you want to use to test with here"; 
    mockWebRequester.Setup(a => a.GetMovieJSONByID(It.IsAny<int>()) 
     .Returns(myJson); 

    Tomato tomato = new Tomato("t4qpkcsek5h6vgbsy8k4etxdd", 
     mockWebRequester.Object); 
    var movie = tomato.FindMovieById(9818); 
    movie.Title.ShouldEqual("Gone With The Wind"); 
} 

约在这种情况下,模拟很酷的事情是,你不必担心所有的箍实际ITomatoWebRequester具有通过跳跃来返回JSON它应该返回,你可以在你的测试中创建一个模拟权,它可以返回你想要的结果。希望这个答案可以作为嘲笑的体面介绍。我肯定会建议阅读嘲笑的框架,以更好地感受过程如何工作。

1

使用Rhino.Mocks库并在任何适当的地方调用Expectations。以下是嘲笑你的电影对象的示例。

using System; 
using NUnit.Framework; 
using Rhino.Mocks; 
namespace ConsoleApplication1 
{ 
    public class Tomato 
    { 
     public Tomato(string t4qpkcsek5h6vgbsy8k4etxdd) 
     { 
      // 
     } 

     public virtual Movie FindMovieById(int i) 
     { 
      return null; 
     } 
    } 

    public class Movie 
    { 
     public string Title; 

     public Movie() 
     { 

     } 

     public void FindMovieById(int i) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

    [TestFixture] 
    public class IndividualMovieTests 
    { 
     [Test] 
     public void Movie_Information_Is_Loaded_Correctly() 
     { 

      //Create Mock. 
      Tomato tomato = MockRepository.GenerateStub<Tomato>("t4qpkcsek5h6vgbsy8k4etxdd"); 

      //Put expectations. 
      tomato.Expect(t=>t.FindMovieById(0)).IgnoreArguments().Return(new Movie(){Title ="Gone With The Wind"}); 

      //Test logic. 
      Movie movie = tomato.FindMovieById(9818); 

      //Do Assertions. 
      Assert.AreEqual("Gone With The Wind", movie.Title); 

      //Verify expectations. 
      tomato.VerifyAllExpectations(); 
     } 
    } 
} 
相关问题