我正在接受单元测试(TDD)。我有一个基本的存储库模式,我正在测试,我不确定我正在做的事情。在这个阶段,我正在测试我的域名,而不是担心控制器和视图。为了简单起见,这是一个演示项目。
类
public class Person
{
public int PersonID { get; set; }
public string Name{ get; set; }
}
接口
public interface IPersonRepository
{
int Add(Person person);
}
混凝土
public class PersonnRepository : IPersonRepository
{
DBContext ctx = new DBContext();
public int Add(Person person)
{
// New entity
ctx.People.Add(person);
ctx.SaveChanges();
return person.id;
}
}
我已将NUnit和MOQ添加到我的测试项目中,并想知道如何正确测试功能。
我不确定它是对的,但在阅读了一些博客之后,我最终创建了一个FakeRepository,但是如果我根据这个进行测试,那么如何验证我的实际界面呢?
public class FakePersonRepository
{
Dictionary<int, Person> People = new Dictionary<int, Person>();
public int Add(Person person)
{
int id = People.Count + 1;
People.Add(id, person);
return id;
}
}
然后用
进行测试 [Test]
public void Creating_A_Person_Should_Return_The_ID ()
{
FakePersonRepository repository = new FakePersonRepository();
int id = repository.Add(new Person { Name = "Some Name" });
Assert.IsNotNull(id);
}
我是否接近在正确的庄园中进行测试?
我想测试未来传递名称导致错误等事情。
答案 0 :(得分:4)
我是否接近在正确的庄园中进行测试?
我担心你不是。拥有接口的想法是,它允许您解耦使用存储库(如控制器)的其他代码,并能够单独对其进行单元测试。因此,假设您有以下要进行单元测试的控制器:
public class PersonController : Controller
{
private readonly IPersonRepository _repo;
public PersonController(IPersonRepository repo)
{
_repo = repo;
}
[HttpPost]
public ActionResult Create(Person p)
{
if (!ModelState.IsValid)
{
return View(p);
}
var id = _repo.Add(p);
return Json(new { id = id });
}
}
请注意控制器如何不依赖于特定的存储库实现。所有需求是此存储库实现给定的合同。现在我们可以在单元测试中使用模拟框架(如Moq)来提供虚假存储库并使其按照我们的喜好运行,以便在Create
操作中测试2个可能的路径:
[TestMethod]
public void PersonsController_Create_Action_Should_Return_View_And_Not_Call_Repository_If_ModelState_Is_Invalid()
{
// arrange
var fakeRepo = new Mock<IPersonRepository>();
var sut = new PersonController(fakeRepo.Object);
var p = new Person();
sut.ModelState.AddModelError("Name", "The name cannot be empty");
fakeRepo.Setup(x => x.Add(p)).Throws(new Exception("Shouldn't be called."));
// act
var actual = sut.Create(p);
// assert
Assert.IsInstanceOfType(actual, typeof(ViewResult));
}
[TestMethod]
public void PersonsController_Create_Action_Call_Repository()
{
// arrange
var fakeRepo = new Mock<IPersonRepository>();
var sut = new PersonController(fakeRepo.Object);
var p = new Person();
fakeRepo.Setup(x => x.Add(p)).Returns(5).Verifiable();
// act
var actual = sut.Create(p);
// assert
Assert.IsInstanceOfType(actual, typeof(JsonResult));
var jsonResult = (JsonResult)actual;
var data = new RouteValueDictionary(jsonResult.Data);
Assert.AreEqual(5, data["id"]);
fakeRepo.Verify();
}
答案 1 :(得分:0)
您需要通过为其提取接口来使您的DBContext可注入:
public interface IDBContext{
IList<Person> People {get;} // I'm guessing at the types
void SaveChanges();
// etc.
}
然后将其注入您的具体课程:
public class PersonRepository : IPersonRepository
{
IDBContext ctx;
public PersonRepository(IDBContext db) {
ctx = db;
}
public int Add(Person person)
{
// New entity
ctx.People.Add(person);
ctx.SaveChanges();
return person.id;
}
}
您的测试将如下所示:
[Test]
public void Creating_A_Person_Should_Return_The_ID ()
{
Mock<IDBContext> mockDbContext = new Mock<IDBContext>();
// Setup whatever mock values/callbacks you need
PersonRepository repository = new PersonRepository(mockDbContext.Object);
int id = repository.Add(new Person { Name = "Some Name" });
Assert.IsNotNull(id);
// verify that expected calls are made against your mock
mockDbContext.Verify( db => db.SaveChanges(), Times.Once());
//...
}
答案 2 :(得分:0)
我个人会考虑为此编写一个“集成测试”,即一个能够访问真实(ish)数据库的测试,因为您的数据访问层不应包含任何使测试处于隔离状态的逻辑。
在这种情况下,您需要启动并运行数据库。这可能是已在某处设置的开发人员数据库,也可能是作为测试安排的一部分启动的内存数据库。
这样做的原因是我发现DAL的(纯)单元测试通常最终证明你可以使用模拟框架而且不会让你对代码充满信心。
如果您对单元测试完全不熟悉,并且没有大学可以帮助设置DAL测试所需的环境,那么我建议您暂时离开测试DAL并专注于业务逻辑,因为这样是您获得最大收益的地方,并且可以让您更轻松地了解测试将如何帮助您。