代码之家  ›  专栏  ›  技术社区  ›  anonymous

状态/交互测试和混合(或滥用)它们的混淆

  •  6
  • anonymous  · 技术社区  · 16 年前

    我认为理解基于状态/交互的测试的定义(阅读Fowler的东西等)。我发现我开始基于状态,但一直在做更多的基于交互的工作,我对如何测试某些东西有点困惑。

    我在MVC中有一个控制器,一个操作调用一个服务来拒绝包:

    public ActionResult Deny(int id)
    {
        service.DenyPackage(id);
    
        return RedirectToAction("List");
    }
    

    我觉得这很清楚。提供模拟服务,验证是否正确调用,完成。

    现在,我有了一个视图的操作,该视图允许用户将证书与包相关联:

    public ActionResult Upload(int id)
    {
        var package = packageRepository.GetPackage(id);
        var certificates = certificateRepository.GetAllCertificates();
    
        var view = new PackageUploadViewModel(package, certificates);
    
        return View(view);
    }
    

    这个我有点受不了。我正在做规范风格的测试(可能不正确),所以为了测试这个方法,我有一个类,然后进行两个测试:验证包存储库是否被调用,验证证书存储库是否被调用。实际上,我想让第三个测试来验证是否调用了构造函数,但不知道如何这样做!我觉得这是完全错误的。

    因此,对于基于状态的测试,我将传入ID,然后测试actionResult的视图。好吧,这是有道理的。但是,我不是要对PackageUploadViewModel构造函数进行测试吗?因此,如果我对构造函数进行了测试,那么我的一部分只想确认我调用了构造函数,并且操作返回与构造函数返回的内容匹配。

    现在,我可以想到的另一个选项是,我有一个PackageUploadViewModelBuilder(或者同样名字很模糊的东西),它依赖于两个存储库,然后我只需将ID传递给一个CreateViewModel方法或其他东西。然后我可以模仿这个对象,验证所有的东西,并且快乐。但是…好。。。看起来很奢侈。我在做一些简单的事情…不简单。另外,controller.action(id)返回builder.create(id)似乎是无故添加层(控制器负责构建视图模型)。对吗?)

    我不知道…我认为更多的基于状态的测试是必要的,但是我担心如果我开始测试返回值,那么如果方法A可以在8种不同的上下文中被调用,我会有一个测试爆炸式的重复。我一直在使用基于交互的测试将一些上下文传递给方法B,这样我所要做的就是验证方法A(称为方法B),并且我测试了方法B,这样方法A就可以信任这些上下文是被处理的。因此,基于交互的测试正在构建这种测试层次结构,但是基于状态的测试将使它变得更平坦一些。

    我不知道这是否有意义。

    哇,这太长了…

    2 回复  |  直到 16 年前
        1
  •  5
  •   Mark Seemann    16 年前

    我认为RoyOsherove最近发现,作为一个经验法则,你的测试应该基于95%的状态和5%的交互。我同意。

    最重要的是你的API做了你想做的,并且 那个 是你需要测试的。如果你测试它如何实现它需要做的工作的机制,你很可能会以过度指定的测试而告终,当涉及到可维护性时,测试会咬你一口。

    在大多数情况下,您可以设计您的API,以便基于状态的测试是自然的选择,因为这非常容易。

    检查上传示例:调用getpackage和getallcertificates有关系吗?这真的是上传方法的预期结果吗?

    我想不会。我的猜测是,上传方法的目的——这是存在的原因——是为了填充并提供正确的视图。

    因此,基于状态的测试将检查返回的viewresult及其viewModel,并验证它是否具有所有正确的值。

    当然,正如现在的代码所示,您需要为PackageRepository和CertificateRepository提供双重测试,因为否则会抛出异常,但看起来调用存储库方法本身并不重要。

    如果您对存储库使用存根而不是模拟,那么您的测试就不再与内部实现细节联系在一起了。如果您稍后决定将upload方法的实现更改为使用包的缓存实例(或其他),则不会调用存根,但这没关系,因为它无论如何都不重要-什么 重要的是返回的视图包含预期的数据。

    这比测试中断更可取,即使所有返回的数据都是正常的。

    有趣的是,您的deny示例看起来像一个基本示例,其中基于交互的测试仍然是有必要的,因为只有通过检查间接输出,您才能验证该方法是否执行了正确的操作(denyPackage方法返回void)。

    所有这些,以及更多的,都在这本好书中解释得很好。 xUnit Test Patterns .

        2
  •  1
  •   Steve Freeman    16 年前

    要问的问题是,“如果这段代码有效,我怎么知道?”这可能意味着测试一些交互或某种状态,这取决于什么是重要的。

    在第一次测试中, Deny 改变目标类之外的世界。它需要来自服务的协作,因此测试交互是有意义的。在第二个测试中,您对邻居进行查询(不更改目标类之外的任何内容),因此删除它们更有意义。

    这就是为什么我们在 http://www.mockobjects.com/book

    推荐文章