如何在单元测试中模拟字符串响应?
下面是我在我的测试,到目前为止:如何在单元测试中模拟字符串响应?
[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的结果,这意味着它像是打破原则背后单元测试。我有一种感觉,我必须嘲笑这个字符串响应,但我不知道如何处理这个问题。
你会怎么做这个特定的单元测试?
在您的第二个[Test]
中,除非您真的想要测试您的给定输入应始终导致“飘”,否则我建议不要关注来自您的FindMovieById
方法的特定返回值。你所拥有的测试似乎是一个非常具体的测试案例,其中一个特定的输入数字会导致一个特定的输出,这个输出在你的实际数据库上运行时可能会也可能不会改变。另外,由于您不会对实际的Web服务进行测试,因此进行这种验证基本上是自我服务的 - 您并未真正测试任何内容。相反,请着重测试Tomato类如何处理参数验证(如果有的话),并且Tomato类实际调用服务以获取返回值。测试具体的输入和输出,而不是测试具体的输入和输出,以便测试该类的行为,以便将来如果有人改变它,测试应该中断以提醒他们他们可能已经破坏了工作功能。
例如,如果您有输入验证,则可以测试您的Tomato类是否检测到无效输入时引发异常。
假设您的Tomato类具有用于请求和检索结果的某种Web客户端功能,您可以插入一些实际Web代码的存根实现,或者模拟实现来确保Tomato实际上调用适当的Web客户端代码来请求和处理响应。
首先,你可能不需要模拟测试你的代码。例如,如果您只是测试您可以将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它应该返回,你可以在你的测试中创建一个模拟权,它可以返回你想要的结果。希望这个答案可以作为嘲笑的体面介绍。我肯定会建议阅读嘲笑的框架,以更好地感受过程如何工作。
使用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();
}
}
}
您是否考虑过使用模拟框架?如果您要为进行外部API调用的方法进行大量的单元测试,这可能会非常有用。 – 2011-04-19 03:34:27