XUnit,AutoFixture和Moq最佳实践

Mat*_*sca 8 c# unit-testing moq xunit.net autofixture

我正在阅读大量文档和示例,了解如何正确地对标题中的三个组件进行单元测试.我想出了一种针对我的业务逻辑的方法的测试方法,但它感觉非常笨重和肮脏.

我想从那些对这个主题更有经验的人那里得到一些反馈,看看我如何改进它.

这是代码,解释如下:

[Fact]
public void ShouldGetItemWithSameId()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    var facade = fixture.Freeze<Mock<IDataFacade>>();
    facade.Setup(c => c.Get(It.IsAny<int>())).Returns((int i) => new Item { Key = i });

    var sut = fixture.Create<BusinessLogic>();
    var expected = fixture.Create<int>();

    Assert.Equal(expected, sut.Get(expected).Key);
}
Run Code Online (Sandbox Code Playgroud)

我的BusinessLogic类使用一个IDataFacade构造函数参数,该参数负责在其Get(int)方法中检索具有相同Id的项目,非常基本的东西.

我冻结了IDataFacade模拟并将其设置为构造一个与Id匹配的对象It.IsAny<int>.然后我创建我的SUT并测试它.工作良好.

我想了解一下,考虑到以下方面我是否可以改进:

  • 我必须测试更复杂的方法,比如一个Query方法,它接受一个包含很多属性的类,这些属性将用作匹配被查询类型的属性的过滤器.在这种情况下,我不知道如何正确地执行模拟的"设置"部分,因为我必须初始化所有或接近所有返回类型的属性,并且在这种情况下它不是单个项目但是整个系列
  • 设置部分感觉不合适,我希望能够在更多方法中重复使用它

我有一些其他测试用Theory,AutoMoqData但我无法使用这种方法实现这个测试(我认为更复杂的测试),所以我Fact用手动实例化的夹具切换回普通.

任何帮助将非常感激.

Nik*_*nis 8

总的来说,原始测试看起来不错.以通用的方式从测试中提取Stubs和Mocks的设置是不可能也不容易的.

但是,您可以做的是最小化测试的排列阶段.这是使用AutoFixture.Xunit自己的单元测试DSL 重新编写的原始测试:

[Theory, TestConventions]
public void ShouldGetItemWithSameId(
    [Frozen]Mock<IDataFacade> facadeStub,
    BusinessLogic sut,
    int expected)
{
    facadeStub
        .Setup(c => c.Get(It.IsAny<int>()))
        .Returns((int i) => new Item { Key = i });

    var result = sut.Get(expected);
    var actual = result.Key;

    Assert.Equal(expected, actual);
}
Run Code Online (Sandbox Code Playgroud)

TestConventions属性定义为:

public class TestConventionsAttribute : AutoDataAttribute
{
    public TestConventionsAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

HTH


示例中使用的示例类型:

public class Item
{
    public int Key { get; set; }
}

public interface IDataFacade
{
    Item Get(int p);
}

public class BusinessLogic
{
    private readonly IDataFacade facade;

    public BusinessLogic(IDataFacade facade)
    {
        this.facade = facade;
    }

    public Item Get(int p)
    {
        return this.facade.Get(p);
    }
}
Run Code Online (Sandbox Code Playgroud)


Doc*_*ick 5

你的测试对我来说很好,虽然我会推荐一个改变.如果传递了预期值,则可以收紧以下行以仅返回预期值:

facade.Setup(c => c.Get(It.IsAny<int>())).Returns((int i) => new Item { Key = i });
Run Code Online (Sandbox Code Playgroud)

所有你需要做的就是移动预期变量并像这样更改Is.IsAny:

var expected = fixture.Create<int>();
facade.Setup(c => c.Get(expected)).Returns((int i) => new Item { Key = i });
Run Code Online (Sandbox Code Playgroud)

我必须测试更复杂的方法,比如一个Query方法,它接受一个包含很多属性的类,这些属性将用作匹配被查询类型的属性的过滤器.在这种情况下,我不知道如何正确地执行模拟的"设置"部分,因为我必须初始化所有或接近所有返回类型的属性,并且在这种情况下它不是单个项目但是整个系列

我认为您不需要初始化返回类型的所有值.我猜你的DataFacade返回一个对象(或在这种情况下的列表)?您需要做的就是确保返回的对象与从DataFacade返回的对象的引用相匹配,您不需要担心属性等,因为您没有测试这些对象的构造,只是它们是回.如果我误解了你并且你正在构建BusinessLogic中的对象,那么这是另一回事.就个人而言,我不会将业务逻辑依赖于数据层,但这是一个不同的讨论.:-)

设置部分感觉不合适,我希望能够在更多方法中重复使用它

您可以.将其提取到单独的方法中,或者,如果它适用于类中的每个测试,则将其放入设置方法中.我不熟悉XUnit,但我使用的每个其他测试框架都提供了进行常见设置的能力,所以我怀疑XUnit会有什么不同.

我最后的评论,就像对待你的生产代码一样对待你的测试代码,如果它看起来很乱,那就做一些让它变得更好的东西.测试非常适合描述系统的行为,但如果它们难以阅读(和维护),则会失去很多价值.

编辑,结果证明这不是我最后的评论!如果你是TDD的新手,我不确定你是谁,不要陷入测试应用程序中每个类的陷阱,这是一种普遍的模式已经变得普遍并且在我看来它贬值了TDD .我写了一篇关于我感情博客文章,Ian Cooper 对这件事给出了精彩的演讲.

  • @ robi -y`Generation <int>().首先(x => x!= 42)`来自AF适合我.新的lib一般以-100点开始. (3认同)