如果測試正在觸摸數據庫(或網絡,文件系統或任何其他外部服務),則它不是單元測試。除了任何定義之外,通過數據庫進行測試的速度緩慢且容易出錯。嘲笑在動態語言中幾乎很容易,但很多人仍然通過數據庫測試業務邏輯。我通常不會發布完整的問題解決方案。在這種情況下,我覺得有必要這樣做來證明它確實很容易,特別是在問題中所描述的那種情況下。
class CheckBruteTest extends PHPUnit_Framework_TestCase {
public function test_checkbrute__some_user__calls_db_and_statement_with_correct_params() {
$expected_user_id = 23;
$statement_mock = $this->getMock('StatementIface', array());
$statement_mock->expects($this->once())->method('bind_param')
->with($this->equalTo('i'), $this->equalTo($expected_user_id));
$statement_mock->expects($this->once())->method('execute');
$statement_mock->expects($this->once())->method('store_result');
$db_mock = $this->getMock('DbIface', array());
$time_ignoring_the_last_two_decimals = floor((time() - 2 * 60 * 60)/100);
$db_mock->expects($this->once())->method('prepare')
->with($this->stringStartsWith("SELECT time FROM login_attempts WHERE user_id = ? AND time > '$time_ignoring_the_last_two_decimals"))
->will($this->returnValue($statement_mock));
checkbrute($expected_user_id, $db_mock);
}
public function test_checkbrute__more_then_five__return_true() {
$statement_mock = $this->getMock('StatementIface', array());
$statement_mock->num_rows = 6;
$db_mock = $this->getMock('DbIface', array());
$db_mock->expects($this->once())->method('prepare')
->will($this->returnValue($statement_mock));
$result = checkbrute(1, $db_mock);
$this->assertTrue($result);
}
public function test_checkbrute__less_or_equal_then_five__return_false() {
$statement_mock = $this->getMock('StatementIface', array());
$statement_mock->num_rows = 5;
$db_mock = $this->getMock('DbIface', array());
$db_mock->expects($this->once())->method('prepare')
->will($this->returnValue($statement_mock));
$result = checkbrute(1, $db_mock);
$this->assertFalse($result);
}
}
interface DbIface {
public function prepare($query);
}
abstract class StatementIface {
public abstract function bind_param($i, $user_id);
public abstract function execute();
public abstract function store_result();
public $num_rows;
}
由於我不知道函數的規範,我只能從代碼中派生出來。
在第一個測試案例中,我只檢查數據庫和語句的模擬,如果代碼實際上按預期調用這些服務。最重要的是它會檢查是否將正確的用戶標識傳遞給語句以及是否使用了正確的時間限制。時間檢查相當不方便,但如果您直接在業務代碼中調用time()
等服務,那就是您所得到的。
第二個測試用例強制嘗試登錄的次數爲6,並且如果函數返回true
,則斷言。
第三個測試用例強制嘗試登錄的次數爲5,並且如果函數返回false
,則斷言。
這涵蓋了(幾乎)函數中的所有代碼路徑。只有一個代碼路徑被省略:如果$mysqli->prepare()
返回null或任何其他值爲false
的整數if-block被繞過並隱式返回null。我不知道這是否有意。代碼應該像這樣明確。
對於嘲笑的原因,我創建了小界面和小抽象類。只有在測試環境中才需要它們。也可以實現$mysqli
參數和返回值$mysqli->prepare()
的自定義模擬類,但我更喜歡使用自動模擬。
一些補充說明,什麼都沒有做的解決方案:
- 單元測試是開發人員測試,應該由開發商自己寫的,而不是一些可憐的測試儀。測試人員編寫驗收和迴歸測試。
- 測試用例的「hackiness」顯示爲什麼在事後編寫測試更困難。如果開發人員編寫代碼TDD樣式,代碼和測試將會更清晰。
- checkbrute功能的設計相當不理想:
- 'checkbrute'是一個壞名字。它並沒有真正說出它的故事。
- 它將業務代碼與數據庫訪問混合在一起。時間限制的計算是商業代碼以及
>5
的檢查。它們之間的代碼是DB代碼,屬於它自己的函數/類/任何內容。
- 幻數。請使用常量來表示幻數,如2h值和最大登錄嘗試次數。
我在哪裏運行此代碼?你知道任何好的教程或網站,可以幫助我學習這一點。 – user2354898 2013-05-06 18:45:45
你可以在任何你想要的地方運行這個代碼!檢查這個鏈接:http://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html。我通過在Google上搜索「php單元測試」發現它......也許這會幫助你。 – Alarid 2013-05-07 07:08:10
你知道如何測試沒有課程的功能嗎?我得到了一個文件,它有很多我想單元測試的函數,但是我似乎無法在不使用類的情況下使其工作。任何想法如何解決這個沒有實現類? – user2354898 2013-05-07 08:41:02