测试替身

本文详细介绍了在PHPUnit中如何使用测试替身,包括桩件(Stubs)的创建和设置返回值,仿件对象(Mock Objects)的使用,以及Prophecy、对trait和abstract类的模拟、对web服务和文件系统的模拟。通过这些技术,可以在测试中避免真实对象的依赖,确保测试的独立性和准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

9 测试替身

PHPUnit 提供的 createMock($type)getMockBuilder($type) 方法可以在测试中用来自动生成对象,此对象可以充当任意指定原版类型(接口或类名)的测试替身。在任何预期或要求使用原版类的实例对象的上下文中都可以使用这个测试替身对象来代替。

createMock($type) 方法直接返回指定类型(接口或类)的测试替身对象实例。此测试替身的创建使用了最佳实践的默认值(不执行原始类的 __construct()__clone() 方法,且不对传递给测试替身的方法的参数进行克隆)。如果这些默认值非你所需,可以用 getMockBuilder($type) 方法并使用流畅式接口来定制测试替身的生成过程。

在默认情况下,原版类的所有方法都会被替换为只会返回 null 的伪实现(其中不会调用原版方法)。使用诸如 will($this->returnValue()) 之类的方法可以对这些伪实现在被调用时应当返回什么值做出配置。

简单来说就是一些测试类需要调用其他的类作为参数,但是我们又不想创建那些类,所以PHPUnit提供了一种模仿机制。

桩件:Stubs

将对象替换成配置好对应返回值的替身的方法成为上桩(stubbing)

设置返回值

可以返回的数据种类有:

  • 固定值
  • 传入的参数
  • 返回桩件类自身
  • 根据传入的参数,返回不同的值
  • 设置回调函数,计算返回值
  • 设置同一方法的多次调用时,每次的返回值
  • 设置抛出指定异常

下面是demo

require_once __DIR__."/../../vendor/autoload.php";

use PHPUnit\Framework\TestCase;

class SomeClass
{
    public function doSomething()
    {
        // 随便做点什么。
        return "data";
    }
    public function callUndefinedFunction($param){
        return "你不可以调用在原始类中未定义的方法";
    }
    public function classTest(){
        return "self";
    }
    public function returnValueByParam(){

    }
    public function returnDataByCallBack(){

    }
    public function returnDataByList(){

    }
    public function throwExceptions(){

    }
}

class Mock extends TestCase{
    public function testExample(){

        $stub = $this->createMock(SomeClass::class);
        // 返回固定值
        $stub->method('doSomething')
            ->willReturn('foo');
        // 返回参数
        $stub->method("callUndefinedFunction")
            ->will($this->returnArgument(0));
        // 返回该桩类自身
        $stub->method("classTest")
            ->will($this->returnSelf());
        // 根据不同参数,返回不同的值
        $map=array(
            // 最后一个值永远是返回的结果,而前面的就是传递的参数
            array("param1","param2","param3",'result1'),
            array('param2','param3','result2'),
            array("result3")
        );
        $stub->method("returnValueByParam")
            ->will($this->returnValueMap($map));
        // 下面调用时传入多少个参数,都会自动传入这里。
        // 但是,下面调用的参数只能比这里多,不能少,否则会报错误
        $callBackFunction=function ($param1,$param2,$param3){
            print_r(array(
                $param1,
                $param2,
                $param3
            ));
        };
        $stub->method("returnDataByCallBack")
            ->will($this->returnCallback($callBackFunction));
        // 设置每一次调用的返回值
        $stub->method("returnDataByList")
            ->will($this->onConsecutiveCalls(1,2,3,4,5,6));// 注意,这里传入的不是数组!
        // 设置代码抛出异常
        $stub->method("throwExceptions")
            ->will($this->throwException(new \Exception("没事情,就是抛异常玩玩")));
        $this->assertEquals('foo', $stub->doSomething());
        $this->assertEquals("hello",$stub->callUndefinedFunction("hello"));
        $this->assertSame($stub,$stub->classTest());
        $this->assertEquals("result1",$stub->returnValueByParam("param1","param2","param3"));
        $this->assertEquals("result2",$stub->returnValueByParam('param2','param3'));
        $this->assertEquals("result3",$stub->returnValueByParam());
        // 这里调用上面设置的方法,其中的参数会自动传入回调函数中
//        $stub->returnDataByCallBack(1,2,3,4);
        $this->assertEquals(1,$stub->returnDataByList());
        $this->assertEquals(2,$stub->returnDataByList());
        try{
            $stub->throwExceptions();
        }catch (Exception $e){
            print "\r\n".$e->getMessage()."\r\n";
        }
    }
}

注意,不能使用$stub->method()设置对应类中不存在的方法,否则会报错。

创建桩件时进行限制

public function testStubSetting(){
        $stub=$this->getMockBuilder(SomeClassSecond::class)
                    ->disableOriginalClone()// 开启设置之后,原类的 __clone 函数就不会运行了
                    ->disableOriginalConstructor()// 开启这个之后,就不会运行原类的 __construct 方法了
                    ->disableArgumentCloning()// 开启这个时候就不会在创建桩件的时候,复制原类的属性
                    ->disallowMockingUnknownTypes()// 不允许定义不存在的属性
                    ->getMock();
        $stub2= clone $stub;// 即使上面限制了 disableOriginalClone,但是那只是限制不能运行原类的 __clone,clone函数还是生效的,所以下面的断言是正确的
        $this->assertEquals($stub2,$stub,"两个类不一致");
    }

仿件对象:Mock Object

仿件对象有桩件的功能,但是比桩件更加贴合原来的类。举个例子就是桩件只能调用设置好的方法,不能调用原类的一些方法,但是仿件对象可以。

创建仿件对象

<?php
require_once __DIR__."/../../vendor/autoload.php";

use PHPUnit\Framework\TestCase;

class Subject
{
    protected $observers = [];
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function doSomething()
    {
        // 做点什么
        // ...

        // 通知观察者发生了些什么
        $this->notify('something');
    }

    public function doSomethingBad()
    {
        foreach ($this->observers as $observer) {
            $observer->reportError(42, 'Something bad happened', $this);
        }
    }

    protected function notify($argument)
    {
        foreach ($this->observers as $observer) {
            $observer->update($argument);
        }
    }

    // 其他方法。
}

class Observer
{
    public function update($argument)
    {
        // 做点什么。
        print "\r\n\{$argument}r\n";
    }

    public function reportError($errorCode, $errorMessage, Subject $subject)
    {
        // 做点什么。
    }
    public function selfFunction(){
        print "\r\nSelfFunction\r\n";
    }
    // 其他方法。
}

class MockObjectTest extends TestCase{
    public function testCopy(){
        // 为 Observer 类建立仿件对象,只模仿 update() 方法。
        $observer = $this->getMockBuilder(Observer::class)
            ->setMethods(['update'])
            ->getMock();

        // 建立预期状况:update() 方法将会被调用一次。只能调用一次,不能多不能少
        // 并且将以字符串 'something' 为参数,不能以其他字符串作为参数
        $observer->expects($this->once())
            ->method('update')
            ->with($this->equalTo('something'));// 设置参数一定是 something
				$observer->selfFunction();// 这里只是尝试调用原类的方法
        // 创建 Subject 对象,并将模仿的 Observer 对象连接其上。
        $subject = new Subject('My subject');
        $subject->attach($observer);

        // 在 $subject 对象上调用 doSomething() 方法,
        // 预期将以字符串 'something' 为参数调用
        // Observer 仿件对象的 update() 方法。
        $subject->doSomething();
    }
}

注意这里的一些细节:

  • 我们创建的仿件对象覆盖对应类的一个或者多个方法:setMethods($methods=array())
  • 我们可以设置对应覆盖方法调用一定要调用一次:$this->once()
  • 我们可以设置预期参数:$this->equalTo()
  • 我们可以将仿件对象就当作原类来使用,即使加了强制类型限制也可以通过

with 方法解释

with() 方法可以携带任何数量的参数,对应于被模仿的方法的参数数量。可以对方法的参数指定更加高等的约束而不仅是简单的匹配。

public function testErrorReported()
    {
        // 为 Observer 类建立仿件,对 reportError() 方法进行模仿
        $observer = $this->getMockBuilder(Observer::class)
            ->setMethods(['reportError'])
            ->getMock();

        $observer->expects($this->once())
            ->method('reportError')
            ->with(
                $this->greaterThan(0),
                $this->stringContains('Something'),
                $this->anything()
            );

        $subject = new Subject('My subject');
        $subject->attach($observer);

        // doSomethingBad() 方法应当会通过(observer的)reportError()方法
        // 向 observer 报告错误。
        $subject->doSomethingBad();
    }

withConsecutive 方法解释

withConsecutive() 方法可以接受任意多个数组作为参数,具体数量取决于欲测试的调用。每个数组都都是对被仿方法的相应参数的一组约束,就像 with() 中那样。

这个单词分开解释就是连续的with,简单来说就是设置该方法多次调用时,设置每次的预期。

public function testFunctionCalledTwoTimesWithSpecificArguments()
    {
        $mock = $this->getMockBuilder(stdClass::class)
            ->setMethods(['set'])
            ->getMock();

        $mock->expects($this->exactly(2))
            ->method('set')
            // consecutive 英文翻译 连续的
            ->withConsecutive(
                [$this->equalTo('foo'), $this->greaterThan(0)],
                [$this->equalTo('bar'), $this->greaterThan(0)]
            );

        $mock->set('foo', 21);
        $mock->set('bar', 48);
    }

上面我们设置了一个方法只调用一次用了$this->once(),如果我们要设置一个方法被调用N次,则需要使用$this->exactly()方法。

with的拓展

这个语言很难解释,但是看代码就可以理解了:

public function testErrorReported()
    {
        // 为 Observer 类建立仿件,模仿 reportError() 方法
        $observer = $this->getMockBuilder(Observer::class)
            ->setMethods(['reportError'])
            ->getMock();

        $observer->expects($this->once())
            ->method('reportError')
            ->with($this->greaterThan(0),
                $this->stringContains('Something'),
                // 上面将对应原本本身传入
                $this->callback(function($subject){
                    return is_callable([$subject, 'getName']) &&
                        $subject->getName() == 'My subject';
                }));

        $subject = new Subject('My subject');
        $subject->attach($observer);

        // doSomethingBad() 方法应当会通过(observer的)reportError()方法
        // 向 observer 报告错误。
        $subject->doSomethingBad();
    }
    public function testIdenticalObjectPassed()
    {
        $expectedObject = new stdClass;

        $mock = $this->getMockBuilder(stdClass::class)
            ->setMethods(['foo'])
            ->getMock();

        $mock->expects($this->once())
            ->method('foo')
            // 预期参数将与 stdClass 一样
            ->with($this->identicalTo($expectedObject));

        $mock->foo($expectedObject);
    }

调用次数函数汇总

  • any:任意次数
  • never:一次也不行
  • atLeastOnce:至少一次
  • once:只有一次
  • exactly:指定次数exactly(int $count)
  • at:指定第几次,第一次下标为0,at(int $index)

getMockBuilder

  • setMethods(array $methods):设置覆盖的方法
  • setConstructorArgs(array $args):向原类的__constructor方法传递参数
  • disableOriginalConstructor:禁用原类的__constructor方法
  • disableOriginalClone:禁用原类的__clone方法

Prophecy

这玩意是另一个测试框架,有机会学习吧。(下辈子吧)

对trait和abstract的类进行模拟

针对trait来说,就是$this->getMockForTrait(AbstractTrait::class);,剩下的就是一样的了。

getMockForAbstractClass() 方法返回一个抽象类的仿件对象。给定抽象类的所有抽象方法将都被模仿。这样就能对抽象类的具体方法进行测试。

require_once __DIR__."/../../vendor/autoload.php";

use PHPUnit\Framework\TestCase;

trait AbstractTrait
{
    public function concreteMethod()
    {
        return $this->abstractMethod();
    }

    public abstract function abstractMethod();
}

class classTest extends TestCase{
    public function testConcreteMethod()
    {
        $mock = $this->getMockForTrait(AbstractTrait::class);

        $mock->expects($this->any())
            ->method('abstractMethod')
            ->will($this->returnValue(true));

        $this->assertTrue($mock->concreteMethod());
    }
}

针对abstract来说也是一样:$this->getMockForAbstractClass(AbstractClass::class);

require_once __DIR__."/../../vendor/autoload.php";

use PHPUnit\Framework\TestCase;

abstract class AbstractClass
{
    public function concreteMethod()
    {
        return $this->abstractMethod();
    }

    public abstract function abstractMethod();
}

class classTest extends TestCase{
    public function testConcreteMethod()
    {
        $stub = $this->getMockForAbstractClass(AbstractClass::class);

        $stub->expects($this->any())
            ->method('abstractMethod')
            ->will($this->returnValue(true));

        $this->assertTrue($stub->concreteMethod());
    }
}

对web服务进行上桩或模仿

拓展知识点太多了,以后有机会再说吧。

wsdl介绍

这里贴一下我找到的例子中的GoogleSearch.wsdl的文件内容:

<?xml version="1.0"?>

<!-- WSDL description of the Google Web APIs.
     The Google Web APIs are in beta release. All interfaces are subject to
     change as we refine and extend our APIs. Please see the terms of use
     for more information. -->

<!-- Revision 2002-08-16 -->

<definitions name="GoogleSearch"
             targetNamespace="urn:GoogleSearch"
             xmlns:typens="urn:GoogleSearch"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
             xmlns="http://schemas.xmlsoap.org/wsdl/">

    <!-- Types for search - result elements, directory categories -->

    <types>
        <xsd:schema xmlns="http://www.w3.org/2001/XMLSchema"
                    targetNamespace="urn:GoogleSearch">

            <xsd:complexType name="GoogleSearchResult">
                <xsd:all>
                    <xsd:element name="documentFiltering"           type="xsd:boolean"/>
                    <xsd:element name="searchComments"              type="xsd:string"/>
                    <xsd:element name="estimatedTotalResultsCount"  type="xsd:int"/>
                    <xsd:element name="estimateIsExact"             type="xsd:boolean"/>
                    <xsd:element name="resultElements"              type="typens:ResultElementArray"/>
                    <xsd:element name="searchQuery"                 type="xsd:string"/>
                    <xsd:element name="startIndex"                  type="xsd:int"/>
                    <xsd:element name="endIndex"                    type="xsd:int"/>
                    <xsd:element name="searchTips"                  type="xsd:string"/>
                    <xsd:element name="directoryCategories"         type="typens:DirectoryCategoryArray"/>
                    <xsd:element name="searchTime"                  type="xsd:double"/>
                </xsd:all>
            </xsd:complexType>

            <xsd:complexType name="ResultElement">
                <xsd:all>
                    <xsd:element name="summary" type="xsd:string"/>
                    <xsd:element name="URL" type="xsd:string"/>
                    <xsd:element name="snippet" type="xsd:string"/>
                    <xsd:element name="title" type="xsd:string"/>
                    <xsd:element name="cachedSize" type="xsd:string"/>
                    <xsd:element name="relatedInformationPresent" type="xsd:boolean"/>
                    <xsd:element name="hostName" type="xsd:string"/>
                    <xsd:element name="directoryCategory" type="typens:DirectoryCategory"/>
                    <xsd:element name="directoryTitle" type="xsd:string"/>
                </xsd:all>
            </xsd:complexType>

            <xsd:complexType name="ResultElementArray">
                <xsd:complexContent>
                    <xsd:restriction base="soapenc:Array">
                        <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="typens:ResultElement[]"/>
                    </xsd:restriction>
                </xsd:complexContent>
            </xsd:complexType>

            <xsd:complexType name="DirectoryCategoryArray">
                <xsd:complexContent>
                    <xsd:restriction base="soapenc:Array">
                        <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="typens:DirectoryCategory[]"/>
                    </xsd:restriction>
                </xsd:complexContent>
            </xsd:complexType>

            <xsd:complexType name="DirectoryCategory">
                <xsd:all>
                    <xsd:element name="fullViewableName" type="xsd:string"/>
                    <xsd:element name="specialEncoding" type="xsd:string"/>
                </xsd:all>
            </xsd:complexType>

        </xsd:schema>
    </types>

    <!-- Messages for Google Web APIs - cached page, search, spelling. -->

    <message name="doGetCachedPage">
        <part name="key"            type="xsd:string"/>
        <part name="url"            type="xsd:string"/>
    </message>

    <message name="doGetCachedPageResponse">
        <part name="return"         type="xsd:base64Binary"/>
    </message>

    <message name="doSpellingSuggestion">
        <part name="key"            type="xsd:string"/>
        <part name="phrase"         type="xsd:string"/>
    </message>

    <message name="doSpellingSuggestionResponse">
        <part name="return"         type="xsd:string"/>
    </message>

    <!-- note, ie and oe are ignored by server; all traffic is UTF-8. -->

    <message name="doGoogleSearch">
        <part name="key"            type="xsd:string"/>
        <part name="q"              type="xsd:string"/>
        <part name="start"          type="xsd:int"/>
        <part name="maxResults"     type="xsd:int"/>
        <part name="filter"         type="xsd:boolean"/>
        <part name="restrict"       type="xsd:string"/>
        <part name="safeSearch"     type="xsd:boolean"/>
        <part name="lr"             type="xsd:string"/>
        <part name="ie"             type="xsd:string"/>
        <part name="oe"             type="xsd:string"/>
    </message>

    <message name="doGoogleSearchResponse">
        <part name="return"         type="typens:GoogleSearchResult"/>
    </message>

    <!-- Port for Google Web APIs, "GoogleSearch" -->

    <portType name="GoogleSearchPort">

        <operation name="doGetCachedPage">
            <input message="typens:doGetCachedPage"/>
            <output message="typens:doGetCachedPageResponse"/>
        </operation>

        <operation name="doSpellingSuggestion">
            <input message="typens:doSpellingSuggestion"/>
            <output message="typens:doSpellingSuggestionResponse"/>
        </operation>

        <operation name="doGoogleSearch">
            <input message="typens:doGoogleSearch"/>
            <output message="typens:doGoogleSearchResponse"/>
        </operation>

    </portType>


    <!-- Binding for Google Web APIs - RPC, SOAP over HTTP -->

    <binding name="GoogleSearchBinding" type="typens:GoogleSearchPort">
        <soap:binding style="rpc"
                      transport="http://schemas.xmlsoap.org/soap/http"/>

        <operation name="doGetCachedPage">
            <soap:operation soapAction="urn:GoogleSearchAction"/>
            <input>
                <soap:body use="encoded"
                           namespace="urn:GoogleSearch"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>
            <output>
                <soap:body use="encoded"
                           namespace="urn:GoogleSearch"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>

        <operation name="doSpellingSuggestion">
            <soap:operation soapAction="urn:GoogleSearchAction"/>
            <input>
                <soap:body use="encoded"
                           namespace="urn:GoogleSearch"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>
            <output>
                <soap:body use="encoded"
                           namespace="urn:GoogleSearch"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>

        <operation name="doGoogleSearch">
            <soap:operation soapAction="urn:GoogleSearchAction"/>
            <input>
                <soap:body use="encoded"
                           namespace="urn:GoogleSearch"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>
            <output>
                <soap:body use="encoded"
                           namespace="urn:GoogleSearch"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>
    </binding>

    <!-- Endpoint for Google Web APIs -->
    <service name="GoogleSearchService">
        <port name="GoogleSearchPort" binding="typens:GoogleSearchBinding">
            <soap:address location="http://api.google.com/search/beta2"/>
        </port>
    </service>

</definitions>

对文件系统进行模拟

安装依赖

composer require mikey179/vfsStream

示例1

示例1主要演示的就是没有使用文件测试前,测试文件相关时的例子。

require_once __DIR__."/../../vendor/autoload.php";

use PHPUnit\Framework\TestCase;

class Example
{
    protected $id;
    protected $directory;

    public function __construct($id)
    {
        $this->id = $id;
    }

    public function setDirectory($directory)
    {
        $this->directory = $directory . DIRECTORY_SEPARATOR . $this->id;

        if (!file_exists($this->directory)) {
            mkdir($this->directory, 0700, true);
        }
    }
}

class ExampleTest extends TestCase
{
    protected function setUp():void
    {
        if (file_exists(dirname(__FILE__) . '/id')) {
            rmdir(dirname(__FILE__) . '/id');
        }
    }

    public function testDirectoryIsCreated()
    {
        $example = new Example('id');
        $this->assertFalse(file_exists(dirname(__FILE__) . '/id'));

        $example->setDirectory(dirname(__FILE__));
        $this->assertTrue(file_exists(dirname(__FILE__) . '/id'));
    }

    protected function tearDown():void
    {
        if (file_exists(dirname(__FILE__) . '/id')) {
            rmdir(dirname(__FILE__) . '/id');
        }
    }
}

这个麻烦就麻烦在文件创建完之后要写代码清除,如果清除不干净,则会对下次测试造成影响。比如在这里我们就使用了setUptearDown,来清除文件。

示例2

require_once __DIR__."/../../vendor/autoload.php";

use PHPUnit\Framework\TestCase;

class Example
{
    protected $id;
    protected $directory;

    public function __construct($id)
    {
        $this->id = $id;
    }

    public function setDirectory($directory)
    {
        $this->directory = $directory . DIRECTORY_SEPARATOR . $this->id;

        if (!file_exists($this->directory)) {
            mkdir($this->directory, 0700, true);
        }
    }
}

class ExampleTest extends TestCase
{
    public function setUp():void
    {
        \org\bovigo\vfs\vfsStreamWrapper::register();
        \org\bovigo\vfs\vfsStreamWrapper::setRoot(new \org\bovigo\vfs\vfsStreamDirectory('exampleDir'));

    }

    public function testDirectoryIsCreated()
    {
        $example = new Example('id');
        $this->assertFalse(\org\bovigo\vfs\vfsStreamWrapper::getRoot()->hasChild('id'));

        $example->setDirectory(\org\bovigo\vfs\vfsStream::url('exampleDir'));
        $this->assertTrue(\org\bovigo\vfs\vfsStreamWrapper::getRoot()->hasChild('id'));
    }
}

关于mikey179/vfsStream这个拓展类的更多介绍,这里我就不深究了,以后用到再说吧。

<think>嗯,用户想了解如何绘制关于软件测试替身的图表或思维导图,需要思路和方法。首先,我需要回忆软件测试替身的基本概念和分类,确保自己理解正确。测试替身主要包括Dummy、Stub、Spy、Mock、Fake等,对吧?这些类型各有不同的用途和特点,比如Dummy只是占位符,而Mock可以验证交互行为。 接下来,用户需要的是绘制图表或思维导图的步骤。可能需要先确定核心概念,然后分支展开。比如,中心节点是“软件测试替身”,然后分为类型、用途、区别、应用场景、工具支持等部分。每个类型下再详细说明定义和示例,这样结构清晰。 然后要考虑如何组织信息。可能需要比较不同替身的区别,比如Mock和Stub的区别在于Mock会验证方法的调用,而Stub只是提供预设的响应。这部分用表格或对比图可能更直观。另外,应用场景方面,单元测试中常用Mock和Stub,集成测试可能用Fake,这些也需要在图表中体现。 用户可能还希望知道实际绘制的工具,比如XMind、MindMaster或者在线工具如ProcessOn。是否需要推荐具体的工具呢?可能用户对工具不熟悉,提供几个选项会有帮助。另外,绘制方法上,可能需要分步骤:确定主题、分类整理、选择图表类型、绘制结构、添加示例和对比,最后美化调整。 还要注意引用相关的资料,比如测试方法中的黑盒和白盒测试,虽然测试替身更多属于单元测试的范畴,但可能和这些方法有关联。比如,测试替身常用于隔离被测组件,属于白盒测试的实现细节。不过用户提供的引用中提到黑盒和灰盒测试,可能需要适当关联。 另外,用户的问题是否涉及如何选择测试替身?比如在什么情况下用Mock而不是Stub,这也是图表中可以包含的内容。可能需要给出选择建议,比如根据是否需要验证交互来选择Mock,或者只需返回数据用Stub。 最后,检查是否有遗漏的部分,比如测试替身的优缺点,或者实际代码示例。虽然用户主要问的是图表绘制,但提供示例可能帮助理解,比如用代码片段说明Mock和Stub的使用区别,这可以作为图表中的示例部分。 总结思路:确定图表结构,分类展示测试替身类型,对比区别,说明应用场景和工具,提供绘制步骤和工具推荐,并加入示例和选择建议。确保信息准确,结构清晰,符合用户需求。</think>### 软件测试替身图表绘制思路与方法 #### 一、核心内容梳理 1. **测试替身分类** - Dummy:占位符对象,无实际功能 - Stub:提供预设响应的简单实现 - Spy:记录调用信息的增强型Stub - Mock:预设预期并验证交互行为 - Fake:简化但可运行的业务逻辑实现[^1] 2. **关键维度对比** | 类型 | 用途 | 交互验证 | 实现复杂度 | 典型场景 | |--------|------------------------|----------|------------|------------------| | Dummy | 参数占位 | 否 | 低 | 依赖项占位 | | Stub | 返回预设数据 | 否 | 中 | 数据隔离 | | Spy | 记录调用过程 | 部分 | 中 | 调用追踪 | | Mock | 验证方法调用与参数 | 是 | 高 | 行为验证 | | Fake | 替代真实组件 | 否 | 高 | 数据库/服务替代 | #### 二、图表绘制步骤 1. **确定结构类型** - **树状图**:以测试替身为根节点,展开类型、特点、应用示例 - **对比矩阵**:横向维度为替身类型,纵向为功能属性 - **流程图**:展示替身选择逻辑(如根据测试目标选择类型) 2. **内容组织建议** ```mermaid graph TD A[测试替身] --> B[类型] A --> C[区别] A --> D[应用场景] B --> B1(Dummy) B --> B2(Stub) B --> B3(Spy) B --> B4(Mock) B --> B5(Fake) D --> D1(单元测试隔离) D --> D2(外部依赖模拟) D --> D3(行为验证) ``` 3. **工具选择** - **思维导图工具**:XMind/MindMaster(结构化展示) - **UML工具**:PlantUML/Draw.io(绘制类图与序列图) - **代码关联工具**:结合Minium框架示例展示替身实现[^2] #### 三、进阶绘制技巧 1. **添加代码映射** ```python # Mock示例(Python) from unittest.mock import Mock user_service = Mock() user_service.get_name.return_value = "TestUser" print(user_service.get_name()) # 输出 TestUser ``` 2. **标注选择逻辑** ```plaintext ┌──────────────┐ │ 需要验证交互? │──是─→ 使用Mock └──────┬───────┘ │否 ┌──────▼──────┐ │ 需要持久化? │──是─→ 使用Fake └──────┬──────┘ │否 ┌──────▼───────┐ │只需简单响应? │──是─→ 使用Stub └──────────────┘ ``` 3. **引用标准关联** 在测试策略标注中关联黑盒/白盒方法,例如Mock属于白盒测试技术[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值