我读过各种关于测试中模仿和存根的文章,包括Martin Fowler的《Mocks Aren't Stubs》,但我仍然不理解其中的区别。


当前回答

加上有用的答案,其中最强大的一点使用模拟比潜艇

如果合作者(主代码所依赖的合作者)不在我们的控制之下(例如来自第三方库), 在这种情况下,stub比mock更难编写。

其他回答

存根不会让你考试不及格,mock可以。

在我的回答中,我使用了python示例来说明差异。

Stub - Stubbing is a software development technique used to implement methods of classes early in the development life-cycle. They are used commonly as placeholders for implementation of a known interface, where the interface is finalized or known but the implementation is not yet known or finalized. You begin with stubs, which simply means that you only write the definition of a function down and leave the actual code for later. The advantage is that you won't forget methods and you can continue to think about your design while seeing it in code. You can also have your stub return a static response so that the response can be used by other parts of your code immediately. Stub objects provide a valid response, but it's static no matter what input you pass in, you'll always get the same response:

class Foo(object):
    def bar1(self):
        pass

    def bar2(self):
        #or ...
        raise NotImplementedError

    def bar3(self):
        #or return dummy data
        return "Dummy Data"

模拟对象用于模拟测试用例,它们验证在这些对象上调用了某些方法。模拟对象是以可控的方式模拟真实对象行为的模拟对象。您通常创建一个模拟对象来测试其他对象的行为。mock让我们模拟对于单元测试来说不可用或太笨重的资源。

mymodule.py:

import os
import os.path

def rm(filename):
    if os.path.isfile(filename):
        os.remove(filename)

test.py:

from mymodule import rm
import mock
import unittest

class RmTestCase(unittest.TestCase):
    @mock.patch('mymodule.os')
    def test_rm(self, mock_os):
        rm("any path")
        # test that rm called os.remove with the right parameters
        mock_os.remove.assert_called_with("any path")

if __name__ == '__main__':
    unittest.main()

这是一个非常基本的示例,它只运行rm并断言调用它的参数。您可以对对象使用mock,而不仅仅是这里所示的函数,您还可以返回一个值,这样模拟对象就可以用来替换存根进行测试。

更多关于unittest的信息。模拟,注意python 2。X mock不包含在unittest中,但它是一个可下载的模块,可以通过PIP (PIP install mock)下载。

我还读过Roy Osherove写的《单元测试的艺术》,我认为如果有一本类似的书是用Python和Python示例编写的,那就太棒了。如果有人知道这样的书,请分享。欢呼:)

在codeschool.com的课程《Rails僵尸测试》中,他们给出了这些术语的定义:

Stub

用于将方法替换为返回指定结果的代码。

Mock

带有调用方法的断言的存根。

因此,正如Sean Copenhaver在他的回答中所描述的那样,不同之处在于mock设置了期望(即做出断言,关于是否或如何调用它们)。

参见下面使用c#和Moq框架的mock和存根的例子。Moq对于存根没有特殊的关键字,但是您也可以使用Mock对象来创建存根。

namespace UnitTestProject2
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Moq;
    [TestClass]
    public class UnitTest1
    {
        /// <summary>
        /// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero
        /// </summary>
        [TestMethod]
        public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
        {
            // Arrange 
            var mockEntityRepository = new Mock<IEntityRepository>();
            mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));

            var entity = new EntityClass(mockEntityRepository.Object);
            // Act 
            var name = entity.GetNameWithPrefix(12);
            // Assert
            mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once);
        }
        /// <summary>
        /// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero
        /// </summary>
        [TestMethod]
        public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
        {
            // Arrange 
            var mockEntityRepository = new Mock<IEntityRepository>();
            mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
            var entity = new EntityClass(mockEntityRepository.Object);
            // Act 
            var name = entity.GetNameWithPrefix(0);
            // Assert
            mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never);
        }
        /// <summary>
        /// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix
        /// </summary>
        [TestMethod]
        public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
        {
            // Arrange 
            var stubEntityRepository = new Mock<IEntityRepository>();
            stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
                .Returns("Stub");
            const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
            var entity = new EntityClass(stubEntityRepository.Object);
            // Act 
            var name = entity.GetNameWithPrefix(12);
            // Assert
            Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
        }
    }
    public class EntityClass
    {
        private IEntityRepository _entityRepository;
        public EntityClass(IEntityRepository entityRepository)
        {
            this._entityRepository = entityRepository;
        }
        public string Name { get; set; }
        public string GetNameWithPrefix(int id)
        {
            string name = string.Empty;
            if (id > 0)
            {
                name = this._entityRepository.GetName(id);
            }
            return "Mr. " + name;
        }
    }
    public interface IEntityRepository
    {
        string GetName(int id);
    }
    public class EntityRepository:IEntityRepository
    {
        public string GetName(int id)
        {
            // Code to connect to DB and get name based on Id
            return "NameFromDb";
        }
    }
}

Stub

我相信最大的区别是您已经编写了带有预定行为的存根。所以你会有一个实现依赖的类(很可能是抽象类或接口),你是为了测试目的而伪造的,方法只是用设置的响应来存根。它们不会做任何花哨的事情,而且您应该已经在测试之外为它编写了存根代码。

Mock

模拟是测试的一部分,你必须根据你的期望来设置。mock并不是预先设定好的,所以您可以在测试中使用它。模拟在某种程度上是在运行时确定的,因为设置期望的代码必须在它们执行任何操作之前运行。

mock和stub的区别

使用模拟编写的测试通常遵循初始化->设置期望->练习->验证模式进行测试。而预先编写的存根将遵循初始化->练习->验证。

mock和stub之间的相似性

这两种方法的目的都是为了消除对类或函数的所有依赖项的测试,从而使您的测试在试图证明的内容方面更加集中和简单。