V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
corboy
V2EX  ›  程序员

如何写单元测试

  •  
  •   corboy · 2016-06-09 17:48:53 +08:00 · 3387 次点击
    这是一个创建于 3090 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近想学写单元测试,但是无从下手。有什么写的好的单元测试可以参考吗?

    PHP

    11 条回复    2016-06-10 10:37:18 +08:00
    murmur
        1
    murmur  
       2016-06-09 17:55:14 +08:00
    单元测试就是软件测试教程讲那些 正常值 边界值 异常值 不符合规定的输入 都来一套
    不过话说回来有保证全周期覆盖单元测试的么来回一下。。军工的不算
    iyaozhen
        2
    iyaozhen  
       2016-06-09 18:23:52 +08:00   ❤️ 1
    建议先看着 PHPUnit 的入门文档,讲得还比较清楚。

    然后我再无耻的“ SEO ”之前在公司小组内分享的《 PHP 单元测试-mock 和数据库测试》 https://iyaozhen.com/php-unit-test-mock-and-db-test.html

    个人感觉写单元测试和调试时自测差多不,主要是一些规范化的套路。
    julyclyde
        3
    julyclyde  
       2016-06-09 20:24:45 +08:00
    哈,很容易写着写着就发现自己写的模块太大了,得拆
    fyibmsd
        4
    fyibmsd  
       2016-06-09 20:31:09 +08:00 via Android
    有一个项目, PHPDesignPattern 就是 php 各种设计模式的实现,有完整的测试用例,可以参考下
    wujunze
        5
    wujunze  
       2016-06-09 21:03:47 +08:00
    PHPUnit +1
    nonesuccess
        6
    nonesuccess  
       2016-06-09 22:21:41 +08:00
    带数据库或者外部资源的单元测试要怎么搞?

    或者说,怎样才能把单元测试和数据库分开?
    murmur
        7
    murmur  
       2016-06-09 22:24:01 +08:00
    @nonesuccess 数据库不建议 mockup 有些东西上了数据库才发现不对就不好玩了
    还是用真实数据吧 junit 的话一般写一些代码在 before after 里捏造一些数据 测试完再把数据删掉
    feiyuanqiu
        8
    feiyuanqiu  
       2016-06-09 22:45:21 +08:00   ❤️ 1
    @nonesuccess 一般来说,如果待测试的代码使用到了第三方的 api ,应该尽可能地避免在代码里直接依赖,尽量使用依赖注入,然后测试的时候注入一个 mock 的依赖对象,就可以很简单地进行测试了,比如有这样的代码:
    class Sample {
    function sampleMethod() {
    $client = new ThirdpartyClient();
    $client->doSomething();
    ...
    }
    }
    这种代码就非常难以测试,因为你的测试结果会依赖于这个真实的 ThirdpartyClient ,而它的行为是不可控制的,这个时候最好重构一下代码,将 ThirdpartyClient 作为方法的参数(更好的方式是作为这个类的构造方法的参数,然后用依赖注入容器去处理依赖的注入):
    class Sample {
    function sampleMethod(ThirdpartyClient $client) {
    $client->doSomething();
    ...
    }
    }
    这样,在写测试的时候,就可以注入一个模拟 ThirdpartyClient 行为的 Mock 对象( PHPUnit 的 getMockBuilder 方法可以很方便地进行这个工作),来进行测试工作。
    你需要记住的就是单元测试的对象是当前这个代码单元的逻辑, ThirdpartyClient 的行为不在这个测试的范畴内,你只需要假设它始终会按照它的接口说明进行返回就可以了

    对于数据库测试,我目前采用的方式是 PHPUnit 的 PHPUnit_Extensions_Database_TestCase 工具来做的
    构造一个 fake DAO (这个 DAO 连接的是我的测试数据库),每个模块的测试会单独提供测试数据集, PHPUnit 在执行每个测试用例前,会调用 setUp 方法,这个方法会将测试数据集初始化到测试数据库,然后将这个测试 Dao 注入到待测试的模块,这个模块之后的所有数据库操作,都会通过这个测试 Dao 来执行,就保证了每次执行测试,数据的一致。
    这里也有个简单的例子:
    class OrderServiceTest extends GenericDatabaseTestCase
    {
    /**
    * 订单模块实例
    *
    * @var OrderService
    */
    protected $service;

    /**
    * 初始化测试数据集
    *
    * @return \PHPUnit_Extensions_Database_DataSet_CompositeDataSet
    * @throws \Exception
    */
    public function getDataSet()
    {
    return $this->loadTestDataset(
    [
    'Order/orders',
    'Order/order_items',
    ]
    );
    }

    /**
    * 初始化基境
    *
    * @return void
    * @throws \Exception
    */
    public function setUp()
    {
    parent::setUp();

    $this->service->setDao($this->getMockDao());
    }
    feiyuanqiu
        9
    feiyuanqiu  
       2016-06-09 22:52:48 +08:00   ❤️ 1
    没写完,按错回复了...那就不多说了
    大致的流程就是:
    1 、准备测试数据集,测试数据集会填充到测试数据库里
    2 、 setUp 方法会在执行每个测试用例前执行,在这里会将测试数据库的连接替换掉模块正常的数据库连接,保证每次执行测试案例的时候测试数据都是一致的,不影响其他环境的
    3 、执行测试方法,因为待测试方法的数据库连接已经被我们的测试数据库连接劫持了,所以这个方法里面操作的所有数据库数据都是测试数据库中的数据
    4 、执行 tearDown 方法

    一般的单元测试看看官方文档就行了

    另外,可以看看 google 测试工程师的这个文档 Guide: Writing Testable Code http://misko.hevery.com/code-reviewers-guide/

    单元测试对代码质量还是很有帮助的,而且写了几天之后就会对依赖注入非常痴迷...
    msg7086
        10
    msg7086  
       2016-06-09 22:55:07 +08:00
    其实做网站的话推荐你看看集成测试(也就是功能测试)。
    codeek
        11
    codeek  
       2016-06-10 10:37:18 +08:00
    单元测试最好的入门方式是 TDD (Test Driven Development),即测试驱动开发。简单来讲,就是先写测试,后写实现代码。

    TDD 不是什么高深的概念,只是一种 Agile 的实践方式。很多人习惯先写实现代码,后“加测试”的编码方式,原因有二,一是实现代码还没有,不知道对哪个方法进行测试;二是懒,觉得测试是额外的工作负担,不到万不得已(比如:项目组强制)不会写测试,即使写也就挑几个好弄的方法,加一个正常流的测试。

    这样的写法很容易导致几个问题:
    1. 实现代码耦合,不便测试;
    2. 实现代码过度设计,类文件剧增,代码量一多,维护性绝对不好;
    3. 测试覆盖率很低,单元测试是拿来忽悠领导的,跟质量无关。

    楼主说自己无从下手,估计是用了“加测试”的方式。如果是这样,那么 refactor (重构) 是你该先学的技能。设计良好的代码,接口很清晰,耦合度低(比如:楼上提到的 DI [依赖注入] ),一般写单元测试非常容易。

    TDD 具体的实践方式,这里我暂按不表,网上的教程多如牛毛。其实它的核心理念就是让你知道如何从需求出发,拆接出任务( tasking ),按照任务一条条来写完测试。然后按照测试->实现->重构(红->绿->黄)的圈完善所有的功能。坚持这样,功能就从需求逐步演化成可维护的代码了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   912 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 20:16 · PVG 04:16 · LAX 12:16 · JFK 15:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.