【问题标题】:What is the right way to unit-test a function that calls another public function on the same object?对在同一对象上调用另一个公共函数的函数进行单元测试的正确方法是什么?
【发布时间】:2016-04-01 18:47:03
【问题描述】:

虽然我知道这个问题/答案 (Unit testing a method that calls another method),但仍然不确定对在同一类上调用另一个公共方法的方法进行单元测试的最佳方法是什么?

我做了一个示例代码(也可以在这里看到:dotnetfiddle.net/I07RMg

public class MyEntity
{
    public int ID { get; set;}
    public string Title { get; set;}
}

public interface IMyService
{
    MyEntity GetEntity(int entityId);
    IEnumerable<MyEntity> GetAllEntities();
}

public sealed class MyService : IMyService
{
    IRepository _repository;

    public MyService(IRepository repository)
    {
        _repository = repository;
    }

    public MyEntity GetEntity(int entityId)
    {
        var entities = GetAllEntities();
        var entity = entities.SingleOrDefault(e => e.ID == entityId);

        if (entity == null)
        {
            entity = new MyEntity { ID = -1, Title = "New Entity" };
        }

        return entity;
    }

    public IEnumerable<MyEntity> GetAllEntities()
    {
        var entities = _repository.Get();

        //Some rules and logics here, like:

        entities = entities.Where(e => !string.IsNullOrEmpty(e.Title));
        return entities; // To broke the test: return new List<MyEntity>();
    }
}

public interface IRepository : IDisposable
{
    IEnumerable<MyEntity> Get();
}

所以问题是如何编写一个只测试MyService.GetEntity(int)内部逻辑的单元测试? (虽然GetEntity 内部调用GetAllEntities() 但我没有兴趣测试后者)。

【问题讨论】:

    标签: c# unit-testing design-patterns


    【解决方案1】:

    我真的相信单元测试并不是要模拟同一类的方法。

    当我们谈论单位时,我们应该指的是您的软件的部分。也就是说,您想测试GetEntity,而实际上它在后台也调用GetAllEntities 只是一个实现细节。

    您真正需要的是确保,当您测试您的服务时,任何注入的依赖项(存储库、域中协作的其他服务......)都应该被替换为假货,以确保您只是在测试您的服务。服务,否则您将实施集成测试

    OP 在一些评论中说...

    我明白了。唯一需要注意的是,如果里面的逻辑 GetAllEntities() 失败,它也可能会破坏单元测试 获取实体()。然后很难确定真正的错误在哪里 谎言。

    正如我之前在这个答案中所说,GetEntity 调用GetAllEntities 的事实只是一个实现细节。就像您要在GetEntity 中重新实现(啊,复制粘贴编程...)GetAllEntities 的逻辑。谁在乎呢。

    实际上,如果GetEntity 因为GetAllEntities 而失败,那么GetAllEntities 本身的测试也会失败。您将首先尝试解决什么测试?我怀疑一旦你意识到GetEntities 因为GetAllEntities 而失败并且GetAllEntities 测试失败,你会直接去修复GetAllEntities 测试,不是吗?

    总而言之,按照您描述问题的方式,错误将出现在 GetAllEntities 上,并且绝对可以预期任何其他依赖于 GetAllEntities 的方法也可能失败。

    【讨论】:

    • 我明白了。唯一需要注意的是,如果GetAllEntities() 中的逻辑失败,它也可能会破坏GetEntity() 的单元测试。那么要确定真正的错误所在就有点困难了。
    • Matias Fidermaizer - 请看一下 JamesR 的回答。我确实更喜欢它,但不幸的是,据我所知,TypeMock 是唯一支持它的模拟框架。
    • @Tohid 这取决于你,我的拙见是你不应该那么偏执,最终,你会嘲笑太多,想想什么是你真的在测试吗?如果某个方法调用了另一个方法,而调用者有例如 3 行代码,那么该测试实际上将毫无用处。
    • @Tohid 如果您觉得其他答案所建议的整个级别的模拟在您的场景中效果更好,那没问题,StackOverflow 为您和其他人提供了正确的答案,这对您也很有用未来的访客
    【解决方案2】:

    您可以使用模拟框架(我使用Typemock Isolator)模拟函数GetAllEntities() 来测试它。

    这是一个简单的例子:

    [TestMethod, Isolated]
    public void TestGetCreatesNewEntity()
    {
        //Assert
        IRepository someRepository = new MyRepository();
        MyService service = new MyService(someRepository);
    
        List<MyEntity> entities = new List<MyEntity>();
        Isolate.WhenCalled(() => service.GetAllEntities()).WillReturn(entities.AsQueryable());
    
        //Act
        MyEntity result = service.GetEntity(1);
    
        //Assert
        Assert.AreEqual(-1, result.ID);
        Assert.AreEqual("New Entity", result.Title);
    }
    

    希望对你有帮助。

    【讨论】:

    • JamesR - 嗯。我使用Moq [github.com/moq/moq4]。我应该看看我的模拟框架是否可以做到这一点。
    • JamesR - 起订量似乎没有此功能,但还有另一种方法 (.CallBase()) 可以绕过它:stackoverflow.com/a/2462672/538387 我将对其进行测试并让您知道它是否有效.
    • JamesR - 结果证明 TypeMock 是唯一支持模拟具体 sealed 类的知名框架。起订量无法做到这一点。还是非常感谢。 +1 向上。
    • 这就是我使用它的原因。不客气,希望你也会喜欢它
    【解决方案3】:

    您可以通过创建虚拟 GetAllEntities 方法来增加您的设计和可测试性,使用 MyTesteableService 子类化您的服务:

    public class MyTesteableService : MyService
    {
        public override IEnumerable<MyEntity> GetAllEntities()
        {
            return something;
        }
    }
    

    现在您可以在不使用 GetAllEntities 逻辑的情况下测试新的可测试服务。 但是,您必须测试 GetEntity 的行为并验证对 GetAllEntities 的调用。

    因此,以另一种方式,您可以将您的服务视为具有虚拟 GetEntity 和抽象 GetAllEntities 的抽象类。我使用 RhinoMock,我可以使用 PartialMock (http://ayende.com/wiki/Rhino+Mocks+Partial+Mocks.ashx?AspxAutoDetectCookieSupport=1)

    【讨论】:

    • 你知道MyService 是一个密封类,对吗?
    • 是的,我的例子是解释设计。如果你真的需要让你的服务密封,然后像我的第二个概念一样创建一个带有抽象 GetAllEntities 的 baseService:“以另一种方式,你可以将你的服务视为一个抽象类......”
    猜你喜欢
    • 2018-11-20
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    相关资源
    最近更新 更多