0

我正在尝试使用 MSTest 和 Moq 为将 json 从表单发布到数据库的实时系统设置单元测试。系统本身工作得很好,但我的任务是尝试为它构建一些测试。我正在使用的视图中的 ajax 调用遵循控制器之一的 HttpPost 方法:

[HttpPost]
public ActionResult Add(Request model)
{
    return ProcessRequest(model, UserAction.Create);
}

这导致 WebAPI 控制器:

public int Post([FromBody]Request value)
    {
        try
        {
            var id = myRepository.AddRequest(value);

            foreach (var day in value.Days)
            {
                day.RequestId = id;
                myRepository.AddRequestDay(day);
            }

            return id;
        }
        catch
        {
            return -1;
        }
    }

通过我的测试,我认为使用 TransactionScope 是一个好主意,所以我实际上并没有在数据库中保存任何数据。如果有更好的方法,请赐教:

[TestMethod]
public void API_Request_Post()
{
    using (TransactionScope ts = new TransactionScope())
    {
        var jsonObject = //some json scraped from a test post
        var request = new Mock<HttpRequestBase>();
        //This is where I'm stuck. I can't find anything in Setup that lets me prep the Post body for when the controller gets to it.
        //request.Setup(x => x.InputStream).Returns(jsonObject);
        RequestController controller = new RequestController();
        //This is another point that I don't understand. I make the call for post happy with a reference to the model instead of the actual json?
        var result = controller.Post(new Models.Request() );
        Assert.IsTrue(result > -1);
    }
}

任何试图确定我需要将 json 提供给 HttpRequest 的哪一部分的帮助将不胜感激(并且帮助我理解 Post 只是锦上添花)。

4

1 回答 1

3

在旁边

我希望我没有告诉你一些你已经知道的事情,但看起来你可能在质疑从哪里开始?这是测试中最难的部分...

只是为了确保我们在同一页面上,知道要测试什么的关键是描述场景,而单元测试该场景的关键是隔离。

这意味着您要对“被测”类进行隔离。

此外,如果您先编写测试然后编写代码以使其通过,则代码更容易测试。您不在那种情况下,这意味着您拥有的代码可能无法在不更改的情况下进行测试。

最后,给定任何外部/第 3 方系统,除非您正在进行“探索性测试” ,否则您不想测试第 3 方的东西,即 http 发布/获取。相反,您想测试您的代码及其执行方式。

假设你知道这一切都是有道理的,那么,这部分也将是显而易见的。

Moq 或任何其他模拟框架旨在代表您的被测类与之协作的对象/服务,以帮助隔离。给定两个类,ClassA 和 ClassB,其中 ClassA 作用于 ClassB,您希望在将 ClassA 提供给 ClassA 时伪造/模拟 Class B,以便您可以断言/验证 ClassA 在给定场景中的行为是否符合您的预期。起初这似乎很幼稚,但考虑到您也会对 ClassB 做同样的事情,然后您就有了一套测试来隔离他们正在测试的内容,这为您提供了很好的覆盖范围。

隔离的关键是注入,确保如果 ClassA 作用于 ClassB,你将 ClassB 传递给 ClassA 的构造函数,这样你就可以给它一个假的 B 类。 B 类不应该做任何事情,除了回应你所说的它应该做出响应,这样您就可以证明 ClassA 在这种情况下表现得恰当。

有些人不赞成更改代码以使其可测试,但我的论点是,如果您首先编写可测试的代码,则不必更改它,因此请尝试重构而不是重新设计。

测试什么

因此,这意味着您将需要几个不同的场景,每个测试都与您关心的内容隔离开来。

好的测试的关键是弄清楚你想要测试什么,然后安排你的测试,以便清楚你在做什么。

  • 测试类名称不需要在其中包含“测试”;那是多余的。解释一下场景是什么;谁参与等等。

  • 测试方法应该说明你关心测试的动作是什么;你在什么州,等等。

  • ** 在方法内部** 现在遵循“Arrange, Act, Assert” (又名 Given、When、Then)方法:

  • 安排:在此处设置所有模拟或您需要的任何变量,包括您正在测试的一个类,例如您的真实 Controller 但假 myRepository 和假value

  • Act:做实际的动作,比如Post()

  • 断言:证明你期望的行为发生了,比如当你给它一个value四天的时间,那么你期望:

    • myRepository 被告知添加值
    • myRepository 被告知要添加一天四次

一个例子

由于我不完全确定测试的意图是什么,而且我不知道所有代码,我将给出一个我认为会很好关联的示例,并希望也能展示如何设置嘲笑(理想情况下你为什么会这样做!)

此外,如果这确实是一个单元测试,您通常会为每个断言/验证争取一个测试,这样您就不必调试测试,只需调试失败的代码,但为了“简单”,我在这里放了三个。

在这个测试中,你会看到我:

  • 关心在 POST 中测试逻辑
  • 所以我创建了一个模拟存储库,仅用于验证它被调用,
  • 以及一个设置为在调用时适当响应的模拟请求,
  • 我将模拟存储库传递给控制器​​的构造函数(通过注入进行隔离)
  • POST然后我使用模拟的协作者(存储库和请求)在实时控制器上执行我关心的操作,
  • 然后我验证POST执行/行为符合预期。

    [TestClass]
    public class GivenAValidRequestAndRepository(){
    
      [TestMethod]
      public void WhenWeReceiveAPostRequest(){
    
        //Arrange / Given
        var repository = new Mock<IRepository>();
        var request = new Mock<IRequest>();
        request.Setup ( rq => rq.ToString() )
               .Returns ( "This is valid json ;-)" );
        request.Setup ( rq => rq.Days )
               .Returns ( new List<IDay> {
                 "Monday",
                 "Tuesday",
                } );
       var controller = new RequestController( repository.Object );
    
    
    
       //Act / When
       int actual = controller.Post( request.Object );
    
    
       //Assert / Verify
       // - then we add the request to the repository
       repository.Verify( 
         repo => repo.AddRequest( request, Times.Once() );
       // - then we add the two days (from above setup) in the request to the repository
       repository.Verify( 
         repo => repo.AddRequestDays( It.IsAny<IDay>(), Times.Exactly( 2 ));
       // - then we receive a count indicating we successfully processed the request
       Assert.NotEqual( -1, actual );
      }
    }
    

结束

你的目标不应该是让你的老板高兴你写了一个测试。相反,努力进行有价值和富有表现力的测试,你将能够在未来保持这种状态。你不会让它完美(你也不应该尝试)只是确保你正在测试的东西增加了价值。涵盖如果他们要在代码中更改,您的测试将失败,表明您有错误的事情。

我希望这会有所帮助,请回复评论/问题。

于 2013-05-31T06:06:26.207 回答