这是我一直在使用的单元测试结构。它基于我在LosTechies上找到的Jimmy Bogard的SpecificationBase类。
好处在于每个场景都封装成自己的类。然后你读测试时听起来很自然。
这假设使用NUnit和FakeItEasy,但可以修改为MS-TEST。
[TestFixture]
public abstract class SpecificationBase
{
[SetUp]
public void SetUp()
{
Given();
When();
}
protected virtual void Given() { }
protected virtual void When() { }
}
public class ThenAttribute : TestAttribute { }
下面是实际的控制器测试
public static class DataControllerTests
{
public class WhenViewingWesternRegionLoadLookAhead : SpecificationBase
{
private DataController _sut;
private ViewResult _result;
private IProvideeDataFeedData _eDataProvider;
protected override void Given()
{
_eDataProvider = A.Fake<IProvideeDataFeedData>();
A.CallTo(() => _eDataProvider.GetAllDayAheadLoad()).Returns(new collectionActualValueData
{
timestamp = new DateTime(2015, 5, 5),
timestampSpecified = true,
actualValueData = new[]
{
new actualValueData {value = 0.1f, name = "Western Region", timestamp = new DateTime(2015, 5, 5)},
new actualValueData {value = 0.1f, name = "some region", timestamp = new DateTime(2015, 5, 5)}
}
});
_sut = new DataController(_eDataProvider);
}
protected override void When()
{
_result = (ViewResult)_sut.Index();
}
[Then]
public void ViewNameShouldBeCorrect()
{
Assert.That(_result.ViewName, Is.EqualTo(""));
}
[Then]
public void ModelShouldBeCorrectType()
{
Assert.That(_result.Model.GetType(), Is.EqualTo(typeof(IndexModel)));
}
[Then]
public void GetAllDayAheadLoadShouldBeCalledOnce()
{
A.CallTo(() => _eDataProvider.GetAllDayAheadLoad()).MustHaveHappened(Repeated.Exactly.Once);
}
}
public class WhenViewingWesternRegionLoadLookAheadAndValuesAreUnder50000 : SpecificationBase
{
private DataController _sut;
private ViewResult _result;
private IndexModel _expectedData;
private IProvideeDataFeedData _eDataProvider;
protected override void Given()
{
_expectedData = new IndexModel
{
Message = "Everything is cool",
Region = "Western Region",
Values = new Dictionary<DateTime, float>
{
{new DateTime(2015, 5, 5), 0.1f}
}
};
_eDataProvider = A.Fake<IProvideeDataFeedData>();
A.CallTo(() => _eDataProvider.GetAllDayAheadLoad()).Returns(new collectionActualValueData
{
timestamp = new DateTime(2015, 5, 5),
timestampSpecified = true,
actualValueData = new[]
{
new actualValueData {value = 0.1f, name = "Western Region", timestamp = new DateTime(2015, 5, 5)},
new actualValueData {value = 0.1f, name = "some region", timestamp = new DateTime(2015, 5, 5)}
}
});
_sut = new DataController(_eDataProvider);
}
protected override void When()
{
_result = (ViewResult)_sut.Index();
}
[Then]
public void ModelDataShouldBeCorrect()
{
var model = (IndexModel)_result.Model;
Assert.That(model.Message, Is.EqualTo(_expectedData.Message));
Assert.That(model.Region, Is.EqualTo(_expectedData.Region));
Assert.That(model.Values, Is.EquivalentTo(_expectedData.Values));
}
}
public class WhenViewingWesternRegionLoadLookAheadAndValuesAreOver50000 : SpecificationBase
{
private DataController _sut;
private ViewResult _result;
private IndexModel _expectedData;
private IProvideeDataFeedData _eDataProvider;
protected override void Given()
{
_expectedData = new IndexModel
{
Message = "Heavy Load",
Region = "Western Region",
Values = new Dictionary<DateTime, float>
{
{new DateTime(2015, 5, 5), 51000f}
}
};
_eDataProvider = A.Fake<IProvideeDataFeedData>();
A.CallTo(() => _eDataProvider.GetAllDayAheadLoad()).Returns(new collectionActualValueData
{
timestamp = new DateTime(2015, 5, 5),
timestampSpecified = true,
actualValueData = new[]
{
new actualValueData {value = 51000f, name = "Western Region", timestamp = new DateTime(2015, 5, 5)},
new actualValueData {value = 0.1f, name = "some region", timestamp = new DateTime(2015, 5, 5)}
}
});
_sut = new DataController(_eDataProvider);
}
protected override void When()
{
_result = (ViewResult)_sut.Index();
}
[Then]
public void ModelDataShouldBeCorrect()
{
var model = (IndexModel) _result.Model;
Assert.That(model.Message, Is.EqualTo(_expectedData.Message));
Assert.That(model.Region, Is.EqualTo(_expectedData.Region));
Assert.That(model.Values, Is.EquivalentTo(_expectedData.Values));
}
}
}
这里是正在测试的控制器
public class DataController : Controller
{
private readonly IProvideeDataFeedData _eDataFeedDataProvider;
public DataController(IProvideeDataFeedData eDataFeedDataProvider)
{
_eDataFeedDataProvider = eDataFeedDataProvider;
}
public ActionResult Index()
{
var values = _eDataFeedDataProvider.GetAllDayAheadLoad().actualValueData
.Where(a => a.name == "Western Region")
.ToDictionary(a => a.timestamp, a => a.value);
var model = new IndexModel
{
Region = "Western Region",
Message = values.Any(v => v.Value > 50000) ? "Heavy Load" : "Everything is cool",
Values = values
};
return View(model);
}
}