在单元测试中处理多个模拟和断言 [英] Handling Multiple Mocks and Asserts in Unit Tests

查看:58
本文介绍了在单元测试中处理多个模拟和断言的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前有一个使用实体框架进行 CRUD 操作的存储库.

I currently have a repository that is using Entity Framework for my CRUD operations.

这被注入到我的需要使用这个 repo 的服务中.

This is injected into my service that needs to use this repo.

使用 AutoMapper,我将实体模型投影到 Poco 模型上,然后服务返回 poco.

Using AutoMapper, I project the entity Model onto a Poco model and the poco gets returned by the service.

如果我的对象有多个属性,设置然后断言我的属性的正确方法是什么?

如果我的服务有多个 repo 依赖项,那么设置所有模拟的正确方法是什么?* - 一个类 [setup],其中为这些测试装置配置了所有模拟和对象?*****

If my service has multiple repo dependencies what is the correct way to setup all my mocks? * - A class [setup] where all the mocks and objects are configured for these test fixtures?*****

我想避免有 10 个测试,每个测试有 50 个属性断言和数十个模拟设置.这使得可维护性和可读性变得困难.

I want to avoid having 10 tests and each test has 50 asserts on properties and dozens on mocks set-up for each test. This makes maintainability and readability difficult.

我已经阅读了单元测试的艺术,但没有发现任何关于如何处理这种情况的建议.

I have read Art of Unit Testing and did not discover any suggestions how to handle this case.

我使用的工具是 Rhino Mocks 和 NUnit.

The tooling I am using is Rhino Mocks and NUnit.

我也在 SO 上找到了这个,但它没有回答我的问题:Correctly Unit测试服务/存储库交互

I also found this on SO but it doesn't answer my question: Correctly Unit Test Service / Repository Interaction

这是一个表达我所描述内容的示例:

Here is a sample that expresses what I am describing:

public void Save_ReturnSavedDocument()
{
    //Simulate DB object
    var repoResult = new EntityModel.Document()
        {
            DocumentId = 2,
            Message = "TestMessage1",
            Name = "Name1",
            Email = "Email1",
            Comment = "Comment1"
        };

    //Create mocks of Repo Methods - Might have many dependencies
    var documentRepository = MockRepository.GenerateStub<IDocumentRepository>();
    documentRepository.Stub(m => m.Get()).IgnoreArguments().Return(new List<EntityModel.Document>()
        {
           repoResult
        }.AsQueryable());

    documentRepository.Stub(a => a.Save(null, null)).IgnoreArguments().Return(repoResult);

    //instantiate service and inject repo
    var documentService = new DocumentService(documentRepository);
    var savedDocument = documentService.Save(new Models.Document()
        {
            ID = 0,
            DocumentTypeId = 1,
            Message = "TestMessage1"
        });

    //Assert that properties are correctly mapped after save
    Assert.AreEqual(repoResult.Message, savedDocument.Message);
    Assert.AreEqual(repoResult.DocumentId, savedDocument.DocumentId);
    Assert.AreEqual(repoResult.Name, savedDocument.Name);
    Assert.AreEqual(repoResult.Email, savedDocument.Email);
    Assert.AreEqual(repoResult.Comment, savedDocument.Comment);
    //Many More properties here
}

推荐答案

首先,每个测试应该只有一个断言(除非另一个验证了真实的断言)如果您想断言列表的所有元素都是不同的,您可能希望首先断言该列表不为空.否则,您可能会得到误报.在其他情况下,每个测试应该只有一个断言.为什么?如果测试失败,它的名称会准确地告诉您出了什么问题.如果您有多个断言并且第一个失败,您不知道其余的是否正常.您只知道出了点问题".

First of all, each test should only have one assertion (unless the other validates the real one) e.q. if you want to assert that all elements of a list are distinct, you may want to assert first that the list is not empty. Otherwise you may get a false positive. In other cases there should only be one assert for each test. Why? If the test fails, it's name tells you exactly what is wrong. If you have multiple asserts and the first one fails you don't know if the rest was ok. All you know than is "something went wrong".

您说您不想在 10 个测试中设置所有模拟/存根.这就是为什么大多数框架为您提供在每次测试之前运行的 Setup 方法.在这里,您可以将大部分模拟配置放在一个地方并重复使用.在 NUnit 中,您只需创建一个方法并使用 [SetUp] 属性对其进行修饰.

You say you don't want to setup all mocks/stubs in 10 tests. This is why most frameworks offer you a Setup method which runs before each test. This is where you can put most of your mocks configuration in one place and reuse it. In NUnit you just create a method and decorate it with a [SetUp] attribute.

如果要测试具有不同参数值的方法,可以使用 NUnit 的 [TestCase] 属性.这非常优雅,您不必创建多个相同的测试.

If you want to test a method with different values of a parameter you can use NUnit's [TestCase] attributes. This is very elegant and you don't have to create multiple identical tests.

现在让我们谈谈有用的工具.

Now lets talk about the useful tools.

AutoFixture 这是一个了不起且非常强大的工具,它允许您创建一个类的对象需要多个依赖.它使用虚拟模拟自动设置依赖项,并允许您仅手动设置特定测试中需要的依赖项.假设您需要为 UnitOfWork 创建一个模拟,它将 10 个存储库作为依赖项.在您的测试中,您只需要设置其中之一.Autofixture 允许您创建那个 UnitOfWork,设置一个特定的存储库模拟(或更多,如果您需要).其余的依赖项将使用虚拟模拟自动设置.这为您节省了大量无用的代码.它有点像用于测试的 IOC 容器.

AutoFixture this is an amazing and very powerful tool that allows you to create an object of a class which requires multiple dependencies. It setups the dependencies with dummy mocks automatically, and allows you to manually setup only the ones you need in a particular test. Say you need to create a mock for UnitOfWork which takes 10 repositories as dependencies. In your test you only need to setup one of them. Autofixture allows you to create that UnitOfWork, setup that one particular repository mock (or more if you need). The rest of the dependencies will be set up automatically with dummy mocks. This saves you a huge amount of useless code. It is a little bit like an IOC container for your test.

它还可以为您生成带有随机数据的假对象.所以等式EntityModel.Document 的整个初始化就只有一行

It can also generate fake objects with random data for you. So e.q. the whole initialization of EntityModel.Document would be just one line

var repoResult = _fixture.Create<EntityModel.Document>();

特别看看:

  • 创建
  • 冻结
  • AutoMock 自定义

在这里你会找到我的回答,解释如何使用 AutoFixture.

Here you will find my answer explaining how to use AutoFixture.

语义比较 教程 这将帮助您在比较不同类型对象的属性时避免多个断言.如果属性具有相同的名称,它几乎会自动使用.如果没有,您可以定义映射.它还会准确地告诉您哪些属性不匹配并显示它们的值.

SemanticComparison Tutorial This is what will help you to avoid multiple assertions while comparing properties of objects of different types. If the properties have the same names it will to it almost automatically. If not, you can define the mappings. It will also tell you exactly which properties do not match and show their values.

Fluent assertions 这只是为您提供了一种更好的断言方式.而不是

Fluent assertions This just provides you a nicer way to assert stuff. Instead of

Assert.AreEqual(repoResult.Message, savedDocument.Message);

你可以做到

repoResult.Message.Should().Be(savedDocument.Message);

总结一下.这些工具将帮助您用更少的代码创建测试,并使它们更具可读性.深入了解他们需要时间.尤其是 AutoFixture,但是当您这样做时,它们将成为您添加到测试项目中的第一件事 - 相信我:).顺便说一句,它们都可以从 Nuget 获得.

To sum up. These tools will help you create your test with much less code and will make them much more readable. It takes time to get to know them well. Especially AutoFixture, but when you do, they become first things you add to your test projects - believe me :). Btw, they are all available from Nuget.

还有一个提示.如果您在测试类时遇到问题,通常表明架构不好.解决方案通常是从有问题的类中提取较小的类.(Single Responsibility Principal) 比你可以轻松测试业务逻辑的小类.并轻松测试原始类与它们的交互.

One more tip. If you have problems with testing a class it usually indicates a bad architecture. The solution usually is to extract smaller classes from the problematic class. (Single Responsibility Principal) Than you can easily test the small classes for business logic. And easily test the original class for interactions with them.

这篇关于在单元测试中处理多个模拟和断言的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆