这是我到目前为止测试中的内容:
[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结果,这意味着它打破了背后单元测试的原则。我有一种感觉,我必须模拟这个字符串响应,但我真的不知道如何处理这个。
你如何接近这个特定的单元测试?
答案 0 :(得分:3)
在您的第二个[Test]
中,我建议您不要关注FindMovieById
方法的特定返回值,除非您真的想要测试您的给定输入应始终导致“飘” ”。您所拥有的测试似乎是一个非常具体的测试用例,其中特定的输入数字会产生特定的输出,这在针对您的实际数据库运行时可能会也可能不会发生变化。此外,由于您不打算对实际的Web服务进行测试,因此进行此类验证基本上是自助服务 - 您实际上并未测试任何内容。相反,专注于测试Tomato类如何处理参数的验证(如果有的话),并且Tomato类实际调用服务来获取返回值。而不是测试特定的输入和输出,测试类的行为,以便如果有人在将来更改它,测试应该中断以提醒他们他们可能已经破坏了工作功能。
例如,如果您有输入验证,则可以测试如果检测到无效输入,您的Tomato类会抛出异常。
假设您的Tomato类具有某种用于请求和检索结果的Web客户端功能,您可以插入实际Web代码的一些存根实现,或模拟实现,以确保Tomato实际上调用适当的Web客户端用于请求和处理响应的代码。
答案 1 :(得分:2)
首先,您可能不必模拟测试代码。例如,如果您只是测试可以将JSON反序列化为Movie
对象,则可以通过在ParseJSON
类上测试公共或内部Movie
秒来实现。
但是,既然你在询问模拟,那么这里有一个快速概述你可以使用模拟编写这个测试的方法。如上所述,Movie_Information_Is_Loaded_Correctly()
看起来像集成测试。要将其转换为单元测试,您可以模拟Tomato
类所做的Web请求。一种方法是创建一个ITomatoWebRequester
接口,并将其作为参数传递给构造函数中的Tomato
类。然后,您可以模拟ITomatoWebRequester
以返回您期望的Web响应,然后您可以测试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);
}
要进行测试,您可以使用像Moq这样的模拟框架来创建一个ITomatoWebRequester,它将返回您期望的结果。要使用Moq执行此操作,以下代码应该有效:
[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,你可以创建一个在你的测试中模拟正确返回你想要的东西。希望这个答案可以作为一个体面的嘲弄介绍。我肯定会建议阅读模拟框架,以便更好地了解该过程的工作原理。
答案 2 :(得分: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();
}
}
}