保持的力量:接口開發(fā)最佳實(shí)踐

2018-11-21 21:18 更新

神啊,求你賜給我平靜的心,去接受我無(wú)法改變的事;賜給我勇氣,去做我能改變的事;賜給我智慧,去分辨兩者的不同。 --平靜之禱

1.30.1 論保持的力量

追到一個(gè)心儀的女生不難,難于如何保持和培養(yǎng)一份真摯的感情;獲得一時(shí)的財(cái)富也不難,難于如何長(zhǎng)久保持收益;創(chuàng)業(yè)的公司很容易博得一時(shí)媒體的關(guān)注以及某次天使的投資,但難于如何排除各種障礙、充分利用各方資源發(fā)展成中企業(yè)及至上市公司。

同樣,提供一時(shí)的接口很容易,但當(dāng)我們需要不斷為接口提供升級(jí),以及當(dāng)我們維護(hù)提供一整套接口時(shí),面臨的困難和問題會(huì)越來(lái)越大。
所以,這是一場(chǎng)持久的戰(zhàn)役。需要我們用穩(wěn)重的心態(tài)、專業(yè)的能力在背后持久支撐、推動(dòng)。

值得慶幸的是,這些都是問題而不是限制,都是可以被解決的。
以下是結(jié)合 @郭了個(gè)浩浩 同學(xué)提供的apigee.web_api.pdf文檔,以及我們多年來(lái)的項(xiàng)目實(shí)際開發(fā)經(jīng)驗(yàn)為新手提供的一些建議,對(duì)老同學(xué)相信也會(huì)有所幫助。
每個(gè)建議通常會(huì)包括三部分: 現(xiàn)在主流的做法、PhalApi的做法以及項(xiàng)目的選取。

1.30.2 最佳實(shí)踐建議

為了大家查閱和翻看,這里先羅列本章的全部建議:

  • (1)接口風(fēng)格和協(xié)議的選擇 - HTTP
  • (2)接口域名 - 使用api單獨(dú)域名
  • (3)異常處理 - 200/400/500三大接口結(jié)果狀態(tài)碼
  • (4)對(duì)外的命名規(guī)則 - 使用小寫加下劃線
  • (5)對(duì)內(nèi)的命名規(guī)則 - 使用駝峰法和遵循PEAR命名
  • (6)安全與驗(yàn)證 - 使用接口簽名和token登錄態(tài)雙重機(jī)制
  • (7)返回結(jié)果格式 - JSON
  • (8)URL規(guī)則與路由映射 - 統(tǒng)一service接口服務(wù),可一個(gè)文件一個(gè)接口
  • (9)SDK包 - 給客戶端自由的調(diào)用空間和自由
  • (10)接口文檔 - 使用markdown快速編寫
  • (11)測(cè)試驅(qū)動(dòng)開發(fā) - 堅(jiān)持單元測(cè)試

1.30.3 建議細(xì)說(shuō)

(1)接口風(fēng)格和協(xié)議的選擇 - HTTP

目前,后臺(tái)接口開發(fā)可以用RESTFull風(fēng)格,也可以用Web Service;可以用SOAP協(xié)議、RPC協(xié)議,也可以用HTTP協(xié)議;可以用短鏈接,也可以使用長(zhǎng)鏈接。如果我們希望繼續(xù)進(jìn)行劃分,還可以分為同步或異步、單個(gè)或批量、是否有SDK包、內(nèi)部接口還是開放接口平臺(tái)等。

主流的做法

現(xiàn)在看來(lái),大部分大型的企業(yè)以及大多數(shù)的小公司使用的都是HTTP協(xié)議下的接口開發(fā),部分使用RESTFull,但Web Service較少。如:

PhalApi的做法

我們選取了HTTP的協(xié)議,在于其無(wú)論是客戶端接入、開發(fā)調(diào)試,還是部署構(gòu)建上都很容易實(shí)現(xiàn),而且也符合主流,因?yàn)榇蠹叶急容^熟悉。
這一點(diǎn)是非常重要的:因?yàn)楹?jiǎn)單,后臺(tái)接口開發(fā)的同學(xué)才會(huì)更容易上手;因?yàn)槿菀?,客戶端接入才?huì)更加無(wú)壓力而不用擔(dān)心處處受挫。

項(xiàng)目的選取

根據(jù)項(xiàng)目不同的項(xiàng)目背景和需求,可以選擇你合適的風(fēng)格或者協(xié)議。但是即使出于安全、性能或者其他技術(shù)或非技術(shù)的原因而不采用HTTP協(xié)議的情況下,你也可以在PhalApi原有的接口開發(fā)實(shí)現(xiàn)時(shí),輕松擴(kuò)展你需要的協(xié)議。如使用SOAP,PHPRpc或者swoole下的TCP協(xié)議。其中,部分協(xié)議已有擴(kuò)展類庫(kù)提供支持。

(2)接口域名 - 使用api單獨(dú)域名

首先,有一點(diǎn)是可以肯定的。
接口系統(tǒng)應(yīng)該有自己?jiǎn)为?dú)的域名,而不應(yīng)該附屬于網(wǎng)站或者管理后臺(tái)。

主流的做法

顯然,主流做法也是這樣做的。如:

項(xiàng)目的選取

如果可以,盡量讓接口系統(tǒng)使用獨(dú)立的域名,并且使用api作為一級(jí)域名。如:

//你的網(wǎng)站為:
http://www.demo.com

//則對(duì)應(yīng)的接口為:
http://api.demo.com

(3)異常處理 - 200/400/500三大接口結(jié)果狀態(tài)碼

對(duì)于接口的異常處理,在使用HTTP協(xié)議下,可以通過HTTP本身的響應(yīng)狀態(tài)碼來(lái)進(jìn)行區(qū)分。在非HTTP協(xié)議并有SDK包的情況下,異常的處理手段則會(huì)更為多樣。

主流的做法

優(yōu)酷接口采用了HTTP響應(yīng)狀態(tài)碼加結(jié)果返回的形式,如:

Request URL:https://openapi.youku.com/v2/videos/show_basic.json
Request Method:GET
Status Code:400 Bad Request

{"error":{"code":1004,"type":"SystemException","description":"Client id null"}}

新浪微博也一樣:

Request URL:https://api.weibo.com/2/statuses/mentions/ids.json
Request Method:GET
Status Code:403 Forbidden

{"error":"auth by Null spi!","error_code":21301,"request":"/2/statuses/mentions/ids.json"}

微信接口則采用了統(tǒng)一200的形式,如:

Request URL:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=
Request Method:GET
Status Code:200 OK

{"errcode":41002,"errmsg":"appid missing"}

PhalApi的做法

為了與HTTP保持一致性,同時(shí)降低不必要的復(fù)雜性,我們采用了200/400/500三大接口結(jié)果狀態(tài)碼。

注意,這里所說(shuō)的三大狀態(tài)碼,是指接口返回結(jié)果中的狀態(tài)碼,而不是HTTP的響應(yīng)狀態(tài)。
也就是說(shuō)接口全部的結(jié)果返回都應(yīng)該是200,除非接口服務(wù)有內(nèi)部未捕獲的異常,即:

Status Code:200 OK

返回結(jié)果狀態(tài)碼剛是以下幾種:

//正常返回
{
    "ret": 200,
    "data": {
        //...
    },
    "msg": ""
}

//客戶端非法請(qǐng)求
{
    "ret": 400,
    "data": [],
    "msg": "非法請(qǐng)求:接口服務(wù)Default.Test不存在"
}

//服務(wù)端內(nèi)部錯(cuò)誤
{
    "ret": 500,
    "data": [],
    "msg": "服務(wù)器運(yùn)行錯(cuò)誤: can not connect to database db_demo"
}

####項(xiàng)目的選取
你可以根據(jù)你的需要,擴(kuò)展400和500這兩系列的錯(cuò)誤,如401表示登錄失敗等。  
此外,在data里面,你也可以添加一個(gè)code來(lái)表示業(yè)務(wù)級(jí)的操作碼,以及客戶端根據(jù)不同的業(yè)務(wù)場(chǎng)景做出不同和反應(yīng)、交互或引導(dǎo)提示。

(4)對(duì)外的命名規(guī)則 - 使用小寫加下劃線

對(duì)外的命名,是指外部看得到的命名,如接口參數(shù)的名字,接口返回的結(jié)果節(jié)點(diǎn)名字,以及數(shù)據(jù)庫(kù)的表名、字段名。

主流的做法

新浪微博采用了小寫加下劃線的做法,如:

//URL
https://c.api.weibo.com/2/friendships/followers/trend_count.json

//請(qǐng)求參數(shù)
source
access_token

//返回結(jié)果
{
    "uid": 10438,
    "result": [            
        {
            "days": "2012-04-04",
            "follower_count_online":"15",  //粉絲數(shù) 
            "active_follower":"14", //活躍粉絲數(shù)
            "loyal_follower":"0"   //互動(dòng)粉絲數(shù)            
        },
        ....
    ]
}

Amazon采用了首字母大寫且無(wú)下劃線的做法,如:

//Responses
HTTP/1.1 200 OK
Date: Wed, 25 Nov 2009 12:00:00 GMT
Connection: close
Server: AmazonS3

<?xml version="1.0" encoding="UTF-8"?>
<BucketLoggingStatus xmlns="http://doc.s3.amazonaws.com/2006-03-01">
  <LoggingEnabled>
    <TargetBucket>mybucketlogs</TargetBucket>
    <TargetPrefix>mybucket-access_log-/</TargetPrefix>
    <TargetGrants>
      <Grant>
        <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:type="AmazonCustomerByEmail">
          <EmailAddress>user@company.com</EmailAddress>
        </Grantee>
        <Permission>READ</Permission>
      </Grant>
    </TargetGrants>
  </LoggingEnabled>
</BucketLoggingStatus>

PhalApi的做法

我們提倡使用全部小寫加下劃線的命名,因?yàn)檫@樣更符合客戶端的使用,如:接口參數(shù):

//正確的
&user_id=888

//錯(cuò)誤的
&userId=888

返回字段:

//正確的
"device_type": "cube",

//錯(cuò)誤的
"deviceType": "cube",

數(shù)據(jù)庫(kù)字段:

//正確的
`user_id` bigint(20) DEFAULT '0' COMMENT '創(chuàng)建者的用戶ID',

//錯(cuò)誤的
`userId` bigint(20) DEFAULT '0' COMMENT '創(chuàng)建者的用戶ID',

項(xiàng)目的選取

不管是使用全部小寫,還是全部大寫,項(xiàng)目都應(yīng)該保持一致的命名風(fēng)格,而不是混合凌亂的風(fēng)格。

(5)對(duì)內(nèi)的命名規(guī)則 - 使用駝峰法和遵循PEAR命名

與對(duì)外命名對(duì)應(yīng)的則是對(duì)內(nèi)的命名規(guī)則,這里又回歸到了老生常談的PHP代碼風(fēng)格。
這里不作過多的說(shuō)明,只是稍作提及。

PhalApi的做法

我們建議使用PEAR包的命名風(fēng)格,和駝峰法,如下為一個(gè)接口示例:

$ vim ./Api/Default.php 
<?php
/**
 * 默認(rèn)接口服務(wù)類
 *
 * @author: dogstar <chanzonghuang@gmail.com> 2014-10-04
 */

class Api_Default extends PhalApi_Api {

    public function getRules() {
        return array(
            'index' => array(
                'username'  => array('name' => 'username', 'default' => 'PHPer', ),
            ),
        );
    }

    public function index() {
        return array(
            'title' => 'Default Api',
            'content' => T('Hello {name}, Welcome to use PhalApi!', array('name' => $this->username)),
            'version' => PHALAPI_VERSION,
            'time' => $_SERVER['REQUEST_TIME'],
        );
    }
}

項(xiàng)目的選取

你可以選擇你喜歡的風(fēng)格,但團(tuán)隊(duì)?wèi)?yīng)該保持一致。

即便你不喜歡PhalApi約定的PEAR命名,你也可以自行實(shí)現(xiàn)內(nèi)部的類加載機(jī)制。
當(dāng)有多個(gè)項(xiàng)目或者多個(gè)模塊并存時(shí),可以添加模塊名前綴來(lái)作區(qū)分,如:

$ tree
.
├── Demo
│   └── Api
│       └── DUser.php
├── MyApp
│   └── Api
│       └── MUser.php
└── Task
    └── Api
        └── TUser.php

$ head */*/*
==> Demo/Api/DUser.php <==
<?php
class Api_DUser extends PhalApi_Api {
}

==> MyApp/Api/MUser.php <==
<?php
class Api_MUser extends PhalApi_Api {
}

==> Task/Api/TUser.php <==
<?php
class Api_TUser extends PhalApi_Api {
}

其它的Domain層和Model層等也類似,這樣可以避免類名沖突,或者IDE開發(fā)環(huán)境下的混淆。

(6)安全與驗(yàn)證 - 使用接口簽名和token登錄態(tài)雙重機(jī)制

既然采用HTTP協(xié)議,那么安全方面就需要接口自身進(jìn)行保證。

所幸,現(xiàn)在可用的加密手段有多種選擇。
對(duì)于接口簽名,我們可以使用非對(duì)稱的驗(yàn)簽方式,如md5;也可以用對(duì)稱的方式,如RSA。
最后,為每一個(gè)接入的客戶端分配app_key和app_secrect即可。

當(dāng)然,更好的安全是接口系統(tǒng)再提供登錄態(tài)的驗(yàn)證,即通常所說(shuō)的token。這兩者的相合,會(huì)為接口增加更好的安全保障。

主流的做法

  • 七牛云存儲(chǔ),采用Access Key/Secret Key,并且在需要時(shí)添加相應(yīng)的憑證
  • 微信公眾號(hào),采用由AppID(應(yīng)用ID)和AppSecret(應(yīng)用密鑰)生成的ACCESS_TOKEN
  • 優(yōu)酷開放平臺(tái),采用應(yīng)用Key client_id

PhalApi的做法

我們不提供具體的接口簽名方案,是因?yàn)榘堰@種決策移交給項(xiàng)目應(yīng)用本身進(jìn)行定制。
而定制也是非常簡(jiǎn)單的,只需要簡(jiǎn)單的兩步即可:

  • 1、實(shí)現(xiàn)過濾器接口 PhalApi_Filter::check();
  • 2、注冊(cè)過濾器服務(wù) DI()->filter;

對(duì)于token,雖然框架沒有提供內(nèi)置的實(shí)現(xiàn),但可以從PhalApi的擴(kuò)展類庫(kù)尋找這種支持,這一點(diǎn)已經(jīng)User擴(kuò)展類庫(kù)支持。

項(xiàng)目的選取

正如PhalApi提供的自由空間,項(xiàng)目可以自行實(shí)現(xiàn)接口簽名,和根據(jù)需要是否采用User擴(kuò)展類庫(kù),或者自行實(shí)現(xiàn)token的處理。

(7)返回結(jié)果格式 - JSON

[1.14.1 統(tǒng)一返回的格式]一節(jié)中,已經(jīng)對(duì)JSON的返回格式作了說(shuō)明,這里不再贅述,也只是稍作提及。

主流的做法

目前采用了JSON的格式返回的有:

  • 新浪微博
  • 優(yōu)酷開放平臺(tái)
  • 騰訊開放平臺(tái)
  • 微信接口

采用了XML格式返回的有:

  • Amazon

PhalApi的做法

我們默認(rèn)采了JSON的格式返回。

項(xiàng)目的選取

項(xiàng)目可以輕松擴(kuò)展成其他格式的返回。

(8)URL規(guī)則與路由映射 - 統(tǒng)一service接口服務(wù),可一個(gè)文件一個(gè)接口

先從項(xiàng)目?jī)?nèi)部的文件劃分說(shuō)起,通常最為常見的情況是,很多開發(fā)人員都喜歡把很多很多很多接口都塞到一個(gè)接口文件里面。

這樣的文件,通常會(huì)有2K到3K左右。
我覺得這是一種極端,而且是一種不好的極端。因?yàn)槲募^大的話,會(huì)帶來(lái)很多問題。

但與之對(duì)立的有另一種做法,即一個(gè)文件,一個(gè)接口。
這一點(diǎn),在我之前就職的一家出名的游戲公司中得到了廣泛的認(rèn)可和遵循。如:

//?service=UserInfo.Go
<?php
class Api_UserInfo extends PhalApi_Api {

     public function go() {
           //TODO
     }
}

//?service=GroupInfo.Go
<?php
class Api_GroupInfo extends PhalApi_Api {

     public function go() {
           //TODO
     }
}

雖然也是一種極端,但卻很好地做到了接口隔離,即不用擔(dān)心修改此接口的實(shí)現(xiàn)而影響到其他接口服務(wù)。

最后,我們?cè)賮?lái)聊URL規(guī)則,就更順暢了。如果我們采用一個(gè)文件對(duì)應(yīng)一個(gè)接口,則我們可以省略Action(全部都為go()方法),簡(jiǎn)寫成:?service=XXX。
再進(jìn)一步,我們可以利用接口服務(wù)器(如Nginx)的規(guī)則Rewrite來(lái)提供更好的URL規(guī)則,同時(shí)盡量隱藏我們的接口內(nèi)部實(shí)現(xiàn)細(xì)節(jié),如:

//原始地
http://api.demo.com/?service=UserInfo.Go

//簡(jiǎn)化地
http://api.demo.com/?service=UserInfo

//再進(jìn)一步
http://api.demo.com/UserInfo

//或者
http://api.demo.com/UserInfo.json

還有一點(diǎn)需要關(guān)注的就是接口的版本,當(dāng)有v1,v2,v3等不同的版本時(shí),我們也需要在接口URL中體現(xiàn)這些版本的不同。

主流的做法

PhalApi的做法

目前而言,PhalApi在URL規(guī)則和路由這塊還比較欠缺,沒有像其他網(wǎng)站一樣提供強(qiáng)大的路由支持。
但我們?cè)诖a實(shí)現(xiàn)的層面,可以提供不同的入口,以開放給不同的終端(內(nèi)部的或者外部的), 以及不同的版本支持。如:

$ tree Public/
Public/
├── v1
│   └── index.php
├── v2
│   └── index.php
└── v3
    └── index.php

3 directories, 3 files

則對(duì)應(yīng)的版本URL則可以為:

//v1版本
http://api.demo.com/v1/?service=Default.Index

//v2版本
http://api.demo.com/v2/?service=Default.Index

//v3版本
http://api.demo.com/v3/?service=Default.Index

項(xiàng)目的選取

項(xiàng)目可以結(jié)合不同的入口,以及接口服務(wù)器的URL規(guī)則Rewrite作一些自定的URL路由。

(9)SDK包 - 給客戶端自由的調(diào)用空間和自由

目前移動(dòng)開發(fā)主要有iOS、Android、Windowns Phone、網(wǎng)站等不同的終端,各種終端又有不同的語(yǔ)言,如果我們需要提供SDK包,不僅僅需要考慮到縱向的版本升級(jí),還需要維護(hù)橫向的多樣性。
而且,如果我們使用的是HTTP協(xié)議,則不必要擔(dān)心這些維護(hù)的成本,同時(shí)給客戶端提供一個(gè)自由的空間進(jìn)行調(diào)用 -- 即客戶端可以自己編寫本身的接口客戶端。

主流的做法

很多國(guó)內(nèi)的開放平臺(tái)接口都是不提供SDK包的,但有些安全度高的則會(huì),如支持寶。

以下是一些提供了SDK的平臺(tái) :

PhalApi的做法

我們暫時(shí)沒有提供SDK包,但對(duì)于PHP,有一個(gè)簡(jiǎn)單的客戶端類,可見: [1.13]-統(tǒng)一的接口請(qǐng)求方式

項(xiàng)目的選取

出于公司產(chǎn)品簇的項(xiàng)目考慮,項(xiàng)目可以內(nèi)部提供SDK給同類的客戶端使用,如分為iOS版的客戶端SDK,以及Android版的客戶端SDK。

小故事:與SDK包的一個(gè)真實(shí)的痛苦經(jīng)歷

有一點(diǎn)是非常重要的,千萬(wàn)不要讓不懂PHP語(yǔ)言的人去開發(fā)提供PHP的SDK包,更不要使用所謂的工具自動(dòng)轉(zhuǎn)換生成SDK包代碼。
在我曾經(jīng)做過的一個(gè)項(xiàng)目中,因?yàn)樾枰尤胍粋€(gè)接口系統(tǒng),而這個(gè)接口是由專業(yè)的JAVA團(tuán)隊(duì)維護(hù)的,但他們對(duì)PHP語(yǔ)言則是非常薄弱,以致他們使用了工具來(lái)生成PHP語(yǔ)言的SDK包。

這就導(dǎo)致了我在接入一個(gè)簡(jiǎn)單的接口時(shí),卻開發(fā)聯(lián)調(diào)耗費(fèi)了兩天、測(cè)試聯(lián)調(diào)時(shí)耗費(fèi)了在接口調(diào)用超時(shí)問題排查上。
而最后找到的原因卻是因?yàn)閍pp_key不對(duì)而導(dǎo)致服務(wù)端異常,而在SDK包卻隱藏了這一異常錯(cuò)誤信息,反而給出了time out超時(shí)的提示,嚴(yán)重誤導(dǎo)了排查的方向!
而當(dāng)我嘗試深入去調(diào)試SDK時(shí),得到卻又是既沒有code又沒有message的異常!最讓人難以忍受的是,他們提供的SDK包竟然和JAVA的企業(yè)系統(tǒng)一樣復(fù)雜的結(jié)構(gòu)(正如他們是使用工具來(lái)生成轉(zhuǎn)換的)!

想象一下,PHP代碼下有\(zhòng)com\sina\webo\sdk\Constants.php這樣類似JAVA的文件結(jié)構(gòu),PHP的同學(xué)會(huì)作何感想?用JAVA的世界的方式來(lái)開發(fā)PHP,顯然是走不通的啊!
而執(zhí)意要走的話,到最后就是各種接入的痛苦,稍微按奈不住的同學(xué)難免就會(huì)因?yàn)榍榫w問題而大開爭(zhēng)論了。而這一切,只是因?yàn)榉荘HP人員使用了自動(dòng)生成工具。
我覺得,這是一種不負(fù)責(zé)任的做法,希望大家不要效仿。

(10)接口文檔 - 使用markdown快速編寫

(場(chǎng)外音:通過沐浴法理清了頭緒,繼續(xù)回來(lái)執(zhí)筆編寫)。
就我個(gè)人經(jīng)歷而言,markdown就是一個(gè)開始你會(huì)拒絕,接著你會(huì)越來(lái)越喜歡,到最后會(huì)愛不釋手的一個(gè)工具。

如果你或者你的團(tuán)隊(duì)還在使用郵件或者work文檔來(lái)傳遞共享接口文檔,那就太不應(yīng)該了;如果你正在使用某個(gè)WIKI系統(tǒng)進(jìn)行文檔的維護(hù)但卻不喜歡它的編輯或者展示方式時(shí),你可以嘗試使用一下markdown。
正如你現(xiàn)在正在查看的文檔也是通過markdown編寫的。

主流的做法

作為開放接口平臺(tái),文檔肯定是以網(wǎng)站的形式提供。但很多時(shí)候,對(duì)于我們內(nèi)部的接口或者小項(xiàng)目來(lái)說(shuō),顯然這樣的成本太大了。
接口,從簡(jiǎn)單開始。
我們理應(yīng)一直堅(jiān)持這一點(diǎn),所以文檔也是一樣,我們應(yīng)該尋求一種在內(nèi)部快速共享最新接口文檔的途徑。如:

  • 1、使用內(nèi)部WIKI
  • 2、使用開源中國(guó)或者其他站點(diǎn)的WIKI(這時(shí)可以通過在線編輯或者GIT更新)

項(xiàng)目的選取

你可以根據(jù)項(xiàng)目的需要,或者公司以往的做法,但至少不要再使用郵件或者word文檔。

(11)測(cè)試驅(qū)動(dòng)開發(fā) - 堅(jiān)持單元測(cè)試

單元測(cè)試,在PhalApi里面不只一次提到了,這里再次進(jìn)行說(shuō)明,是希望能引起大家的關(guān)注,去嘗試體驗(yàn)一下。

我們都知道,在開發(fā)一個(gè)新功能時(shí)、新接口時(shí),修復(fù)一個(gè)BUG或者作一些大的調(diào)整或者重構(gòu)工作,我們是毫無(wú)壓力的,而且這時(shí)的成本很低,僅在于開發(fā)人員本身的時(shí)間和精力的消耗。
當(dāng)提測(cè)后進(jìn)入測(cè)試階段,測(cè)試人員發(fā)現(xiàn)一個(gè)BUG后,有些團(tuán)隊(duì)會(huì)以禪道或者Bugzilla或其他方式來(lái)紀(jì)錄和追蹤BUG。這時(shí)我們開發(fā)會(huì)覺得一個(gè)這么小的問題還需要去紀(jì)錄、去登記很不值得。然后,我們應(yīng)當(dāng)注意到這時(shí)修復(fù)一個(gè)BUG會(huì)涉及到測(cè)試人員資源的開銷。
當(dāng)進(jìn)入了回歸測(cè)試階段,特別是多系統(tǒng)交互、跨團(tuán)隊(duì)合作時(shí),一個(gè)BUG就會(huì)從一個(gè)人傳到另一個(gè)人,從這個(gè)團(tuán)隊(duì)流到那個(gè)團(tuán)隊(duì),這時(shí)成本就會(huì)逐漸增大。

最后,上線后,當(dāng)一個(gè)奇怪的問題出現(xiàn)后,我們需要定位原因就更加困難重重了。
我曾經(jīng)就經(jīng)歷這樣一番:有用戶發(fā)現(xiàn)游戲的道具減少了。我們一開始以為是某些運(yùn)營(yíng)配置、或者數(shù)據(jù)以及用戶的等級(jí)限制所引發(fā)的,但在排除了各種業(yè)務(wù)的問題后,到最后卻發(fā)現(xiàn)是PHP中array使用“+”運(yùn)算而引發(fā)的血案!
在正常情況下,我們都知道array_merge()函數(shù)對(duì)于數(shù)值的下標(biāo)則會(huì)追加并重新生成下標(biāo)序列,即會(huì)合并;而數(shù)組+則會(huì)去掉相同下標(biāo)的元素。

但實(shí)際情況下,線上BUG所產(chǎn)生的影響不在于排查和修復(fù)的時(shí)間成本,而在于在這段時(shí)間內(nèi)所損失的金額、數(shù)據(jù)等成本。

當(dāng)然,從測(cè)試的角度上看,測(cè)試并不能保證我們的系統(tǒng)沒有BUG,只能說(shuō)暫時(shí)未發(fā)現(xiàn)BUG。
單元測(cè)試也一樣,作為開發(fā)人員,我們應(yīng)當(dāng)在最低成本的時(shí)期就及時(shí)發(fā)現(xiàn)我們直覺覺得可能會(huì)出現(xiàn)的問題并進(jìn)行修復(fù)。
對(duì)我們親手所編寫的代碼負(fù)責(zé),并且用客觀的方式來(lái)證明我們的代碼目前未發(fā)現(xiàn)問題,而不是主觀認(rèn)為“我寫的代碼沒有問題”。更不應(yīng)該一次又一次地犯下各種低級(jí)或者重復(fù)的錯(cuò)誤,而讓團(tuán)隊(duì)其他成員對(duì)我們喪失信任。

PhalApi一直很注重單元測(cè)試,也很注重自動(dòng)化,為了減輕大家重復(fù)編寫單元測(cè)試骨架代碼的痛苦,我們提供了一個(gè)可以生成單元測(cè)試代碼的腳本。
假設(shè)我們有這么一個(gè)類:

<?php

class Api_Default extends PhalApi_Api {

        public function index() {
             //TODO
        }
}

那么,我們可以這樣生成測(cè)試代碼:

$ cd .//Demo/Tests
$ phalapi-buildtest ../Api/Default.php Api_Default ./test_env.php 
<?php
/**
 * PhpUnderControl_ApiDefault_Test
 *
 * 針對(duì) ../Api/Default.php Api_Default 類的PHPUnit單元測(cè)試
 *
 * @author: dogstar 20150514
 */

require_once dirname(__FILE__) . '/test_env.php';

if (!class_exists('Api_Default')) {
    require dirname(__FILE__) . '/../Api/Default.php';
}

class PhpUnderControl_ApiDefault_Test extends PHPUnit_Framework_TestCase
{
    public $apiDefault;

    protected function setUp()
    {
        parent::setUp();

        $this->apiDefault = new Api_Default();
    }

    protected function tearDown()
    {
    }

    /**
     * @group testGetRules
     */ 
    public function testGetRules()
    {
        $rs = $this->apiDefault->getRules();
    }

    /**
     * @group testIndex
     */ 
    public function testIndex()
    {
        $rs = $this->apiDefault->index();
    }

}

溫馨提示:

  1. 可以先執(zhí)行:ln -s /path/to/PhalApi/phalapi-buildtest /usr/bin/phalapi-buildtest
  2. test_env.php為測(cè)試環(huán)境初始化文件,可以在里面引用init.php文件,并作一些調(diào)整
  3. 輸出的測(cè)試代碼可以重定向到./Demo/Tests/Api/Api_Default_Test.php,讓測(cè)試代碼與產(chǎn)品代碼對(duì)齊

最后,我們就可以這樣執(zhí)行單元測(cè)試了:

$ phpunit ./Api_Default_Test.php 

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)