0

我对单元测试 (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);

    }

我是否接近在正确的庄园进行测试?

我想在将来测试诸如不传递名称会导致错误等的事情。

4

3 回答 3

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();
}
于 2012-04-17T17:34:05.147 回答
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());
   //...

}

于 2012-04-17T17:27:00.517 回答
0

我个人会考虑为此编写一个“集成测试”,即一个命中真实(ish)数据库的测试,因为您的数据访问层不应包含任何使隔离测试值得的逻辑。

在这种情况下,您将需要一个数据库启动并运行。这可能是已经在某处设置的开发人员数据库,或者是作为测试安排的一部分启动的内存数据库。

这样做的原因是我发现 DAL 的(纯)单元测试通常最终证明您可以使用模拟框架等等,并且最终不会让您对您的代码充满信心。

如果您对单元测试完全陌生,并且手头没有大学来帮助您设置 DAL 测试所需的环境,那么我建议您暂时离开测试 DAL 并专注于业务逻辑,因为这是您需要的地方将为您带来最大的收益,并使您更容易了解测试将如何帮助您。

于 2012-04-18T12:24:58.203 回答