【问题标题】:should i extract some of my unit test code?我应该提取一些单元测试代码吗?
【发布时间】:2013-06-16 14:28:42
【问题描述】:

我只是在用 mvc 4 研究 tdd。

我有一个订单控制器,它在其构造函数中采用一个工作单元接口:

public OrdersController(IUnitOfWork db)
{
this.db = db;           
}

//
// GET: /Orders/
public ActionResult Index()
{                      
    return View(db.Orders.GetAll());
}

我对此 Index() 进行了几个测试。

[TestClass]
    public class when_the_order_controller_index_action_executes
    {
        [TestMethod]
        public void it_should_render_the_default_view()
        {
            var uow = new Mock<IUnitOfWork>();
            var db = uow.Object;

            var orders = new List<Order>()
            {
                new Order{CreatedDate = DateTime.Now.AddMonths( -3),OrderID = Guid.NewGuid()},
                new Order{CreatedDate = DateTime.Now,OrderID = Guid.NewGuid()}
            };

            uow.Setup(r => r.Orders.GetAll())
                .Returns(orders);

            //arrange
            var controller = new OrdersController(db);

            //act
            var result = controller.Index() as ViewResult;

            //assert
            Assert.AreEqual("", result.ViewName);
        }

        [TestMethod]
        public void it_should_pass_orders_as_the_model()
        {
             var uow = new Mock<IUnitOfWork>();
            var db = uow.Object;

            var orders = new List<Order>()
            {
                new Order{CreatedDate = DateTime.Now.AddMonths( -3),OrderID = Guid.NewGuid()},
                new Order{CreatedDate = DateTime.Now,OrderID = Guid.NewGuid()}
            };


            uow.Setup(r => r.Orders.GetAll())
                .Returns(orders);

            //arrange
            var controller = new OrdersController(db);

            //act
            var model = ((ViewResult)controller.Index()).ViewData.Model as IEnumerable<Order>;

            //assert
            Assert.IsTrue(orders.Equals(model));
        }
    }

您会注意到,在用于创建 GetAll 可以返回的列表的两个测试中存在大量代码重复...

说一个可重用的函数来返回两个测试都可以调用的列表是好还是坏?

任何关于我编写的测试的一般建议也将不胜感激,因为我现在才接受 tdd 的爱!

【问题讨论】:

  • 我现在也在学习 TDD,我只是在看你的代码,想知道我是否错过了模拟/测试的概念你为什么要麻烦实际把在您的第一次测试中将数据放入您的 OUW.db 模拟中?由于您需要将测试设置为模型的所有内容都是空的,因为您在第一种方法中测试的只是索引视图已返回给您;然后,您将涵盖 Index 操作在第二次测试中正确设置模型的测试。根本不想变得聪明,只是想知道我是否错过了一些概念?

标签: asp.net-mvc unit-testing c#-4.0 asp.net-mvc-4 tdd


【解决方案1】:

是的,通常认为在单元测试的[TestInitialize] 阶段重构所有常见代码初始化是一种很好的做法。您还可以将您觉得重复的单元测试的所有常见初始化/断言部分外部化为可重用方法。

所以在你的具体例子中:

[TestClass]
public class when_the_order_controller_index_action_executes
{
    private IUnitOfWork db;
    private OrdersController sut;

    [TestInitialize]
    public void TestInitialize()
    {
        var uow = new Mock<IUnitOfWork>();
        this.db = uow.Object;

        var orders = new List<Order>()
        {
            new Order{ CreatedDate = DateTime.Now.AddMonths(-3),OrderID = Guid.NewGuid() },
            new Order{ CreatedDate = DateTime.Now,OrderID = Guid.NewGuid() }
        };

        uow.Setup(r => r.Orders.GetAll()).Returns(orders);

        this.sut = new OrdersController(db);
    }

    [TestMethod]
    public void it_should_render_the_default_view()
    {
        //act
        var result = this.sut.Index() as ViewResult;

        //assert
        Assert.AreEqual("", result.ViewName);
    }

    [TestMethod]
    public void it_should_pass_orders_as_the_model()
    {
        //act
        var model = ((ViewResult)this.sut.Index()).ViewData.Model as IEnumerable<Order>;

        //assert
        Assert.IsTrue(orders.Equals(model));
    }
}

但通常uow.Setup(r =&gt; r.Orders.GetAll()).Returns(orders); 是每个单元测试的//arrange 阶段的一部分,因为它不同。这是您定义期望的地方。在您的特定示例中,我还将两个测试合并为一个:

[TestClass]
public class when_the_order_controller_index_action_executes
{
    private IUnitOfWork db;
    private OrdersController sut;

    [TestInitialize]
    public void TestInitialize()
    {
        var uow = new Mock<IUnitOfWork>();
        this.db = uow.Object;

        var orders = new List<Order>()
        {
            new Order{ CreatedDate = DateTime.Now.AddMonths(-3),OrderID = Guid.NewGuid() },
            new Order{ CreatedDate = DateTime.Now,OrderID = Guid.NewGuid() }
        };

        uow.Setup(r => r.Orders.GetAll()).Returns(orders);

        this.sut = new OrdersController(db);
    }

    [TestMethod]
    public void it_should_render_the_default_view_and_pass_the_expected_view_model_to_it()
    {
        //act
        var actual = this.sut.Index();

        //assert
        Assert.IsInstanceOfType(actual, typeof(ViewModel));
        var viewResult = (ViewResult)actual;
        Assert.AreEqual(model, viewResult.Model);
    }
}

【讨论】:

  • 感谢您的详细回复。但是,每个测试都有一个断言不是最佳做法吗?
  • @Simon:“一个断言”应该理解为“一个逻辑概念”,而不是Assert的一行代码。
  • 请注意,对于某些测试库,例如 xUnit.net ,初始化代码在测试类构造函数中,而清理是通过使测试类实现 IDisposable 来完成的。顺便说一句,您也应该尝试一下 FluentAssertions 库
猜你喜欢
  • 2014-03-25
  • 1970-01-01
  • 2021-08-08
  • 1970-01-01
  • 1970-01-01
  • 2016-07-23
  • 2020-05-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多