Kod warisan pemfaktoran semula: Bahagian 5 - Satu cara untuk menjadikan permainan anda boleh diuji

WBOY
Lepaskan: 2023-08-28 16:30:01
asal
927 orang telah melayarinya

重构遗留代码:第五部分 - 游戏可测试的方法

Kod lama. Kod hodoh. Kod kompleks. Kod spageti. mengarut. Pendek kata, kod warisan. Ini adalah siri untuk membantu anda bekerja dan menangani masalah anda.

Dalam tutorial sebelum ini, kami telah menguji fungsi Runner. Dalam pelajaran ini, sudah tiba masanya untuk meneruskan ujian kelas Permainan. Kini, apabila anda bermula dengan sebahagian besar kod seperti yang kami ada di sini, mudah untuk memulakan kaedah ujian mengikut kaedah secara atas ke bawah. Selalunya, ini tidak mungkin. Adalah lebih baik untuk mula mengujinya dengan pendekatan yang pendek dan boleh diuji. Itulah yang akan kita lakukan dalam pelajaran ini: cari dan uji kaedah ini. Game 类的地方继续我们上次停下的地方了。现在,当您从像我们这里这样的一大块代码开始时,很容易开始以自上而下的方式逐个方法进行测试。大多数时候,这是不可能的。最好通过简短的、可测试的方法开始测试它。这就是我们在本课中要做的事情:查找并测试这些方法。

创建游戏

为了测试一个类,我们需要初始化该特定类型的对象。我们可以认为我们的第一个测试是创建这样一个新对象。您会惊讶地发现构造函数可以隐藏多少秘密。

require_once __DIR__ . '/../trivia/php/Game.php';

class GameTest extends PHPUnit_Framework_TestCase {

	function testWeCanCreateAGame() {
		$game = new Game();
	}

}
Salin selepas log masuk

令我们惊讶的是,Game 实际上可以很容易地创建。仅运行 new Game() 时没有问题。没有任何损坏。这是一个非常好的开始,特别是考虑到 Game 的构造函数相当大并且它做了很多事情。

寻找第一个可测试方法

现在很想简化构造函数。但我们只有金主来确保我们不会破坏任何东西。在我们进入构造函数之前,我们需要测试类的大部分其余部分。那么,我们应该从哪里开始呢?

寻找第一个返回值的方法并问自己,“我可以调用并控制这个方法的返回值吗?”。如果答案是肯定的,那么它就是我们测试的良好候选者。

function isPlayable() {
	$minimumNumberOfPlayers = 2;
	return ($this->howManyPlayers() >= $minimumNumberOfPlayers);
}
Salin selepas log masuk

这个方法怎么样?这似乎是一个不错的候选人。只有两行,它返回一个布尔值。但是等等,它调用另一个方法,howManyPlayers()

function howManyPlayers() {
	return count($this->players);
}
Salin selepas log masuk

这基本上只是一个计算类 players 数组中元素的方法。好的,所以如果我们不添加任何玩家,它应该为零。 isPlayable() 应该返回 false。让我们看看我们的假设是否正确。

function testAJustCreatedNewGameIsNotPlayable() {
	$game = new Game();
	$this->assertFalse($game->isPlayable());
}
Salin selepas log masuk

我们重命名了之前的测试方法以反映我们真正想要测试的内容。然后我们就断言游戏无法玩。测试通过。但在许多情况下,误报很常见。因此,出于安全考虑,我们可以断言 true 并确保测试失败。

$this->assertTrue($game->isPlayable());
Salin selepas log masuk

确实如此!

PHPUnit_Framework_ExpectationFailedException :
Failed asserting that false is true.
Salin selepas log masuk

到目前为止,还是很有希望的。我们设法测试了该方法的初始返回值,该值由 Game 类的初始状态表示。请注意强调的词:“状态”。我们需要找到一种方法来控制游戏的状态。我们需要更改它,使其具有最少的玩家数量。

如果我们分析 Gameadd() 方法,我们会看到它向我们的数组添加了元素。

array_push($this->players, $playerName);
Salin selepas log masuk

我们的假设是通过在 RunnerFunctions.php 中使用 add() 方法来强制执行的。

function run() {

	$aGame = new Game();
	$aGame->add("Chet");
	$aGame->add("Pat");
	$aGame->add("Sue");

	// ... //
}
Salin selepas log masuk

根据这些观察,我们可以得出结论,通过使用 add() 两次,我们应该能够使 Game 进入有两个玩家的状态。

function testAfterAddingTwoPlayersToANewGameItIsPlayable() {
	$game = new Game();
	$game->add('First Player');
	$game->add('Second Player');
	$this->assertTrue($game->isPlayable());
}
Salin selepas log masuk

通过添加第二个测试方法,我们可以确保 isPlayable() 在满足条件的情况下返回 true。

但您可能认为这不完全是单元测试。我们使用 add() 方法!我们练习的不仅仅是最少的代码。我们可以只将元素添加到 $players 数组中,而根本不依赖 add() 方法。

嗯,答案是肯定的,也不是。从技术角度来看,我们可以做到这一点。它将具有直接控制阵列的优点。但是,它会存在代码和测试之间代码重复的缺点。因此,选择您认为可以忍受的糟糕选项之一并使用它。我个人更喜欢重用 add()

Buat permainan

Untuk menguji kelas, kita perlu memulakan objek jenis tertentu itu. Kita boleh menganggap ujian pertama kita sebagai mencipta objek baharu sedemikian. Anda akan terkejut berapa banyak rahsia yang boleh disembunyikan oleh pembina.

function testAGameWithNotEnoughPlayersIsNotPlayable() {
	$game = new Game();
	$this->assertFalse($game->isPlayable());
	$game->add('A player');
	$this->assertFalse($game->isPlayable());
}
Salin selepas log masuk

Mengejutkan kami, Permainan sebenarnya mudah dibuat. Tiada masalah apabila hanya menjalankan new Game(). Tiada kerosakan. Ini adalah permulaan yang cukup baik, terutamanya memandangkan pembina Permainan agak besar dan melakukan banyak perkara.

Cari kaedah pertama yang boleh diuji

Sekarang saya benar-benar mahu memudahkan pembina. Tetapi kami hanya mempunyai penyokong kewangan untuk memastikan kami tidak melanggar apa-apa. Sebelum kita masuk ke dalam pembina, kita perlu menguji kebanyakan kelas yang lain. Jadi, di manakah kita harus bermula?

Cari kaedah pertama yang mengembalikan nilai dan tanya diri anda, "Bolehkah saya memanggil dan mengawal nilai pulangan kaedah ini?". Jika jawapannya ya, maka ia adalah calon yang baik untuk ujian kami.

$game->add('First Player');
$game->add('Second Player');
Salin selepas log masuk

Bagaimana dengan kaedah ini? Ini nampaknya calon yang bagus. Terdapat hanya dua baris dan ia mengembalikan nilai boolean. Tetapi tunggu, ia memanggil kaedah lain, howManyPlayers().

function testAfterAddingEnoughPlayersToANewGameItIsPlayable() {
	$game = new Game();
	$this->addEnoughPlayers($game);
	$this->assertTrue($game->isPlayable());
}

private function addEnoughPlayers($game) {
	$game->add('First Player');
	$game->add('Second Player');
}
Salin selepas log masuk
Salin selepas log masuk

Ini pada asasnya hanyalah kaedah yang mengira elemen dalam tatasusunan pemain kelas. Baiklah, jadi jika kita tidak menambah mana-mana pemain, ia sepatutnya sifar. isPlayable() sepatutnya mengembalikan palsu. Mari kita lihat sama ada andaian kita betul. 🎜
class Game {
	static $minimumNumberOfPlayers = 2;

	// ... //

	function  __construct() {
		// ... //
	}

	function isPlayable() {
		return ($this->howManyPlayers() >= self::$minimumNumberOfPlayers);
	}

	// ... //
}
Salin selepas log masuk
Salin selepas log masuk
🎜Kami menamakan semula kaedah ujian sebelumnya untuk mencerminkan perkara yang sebenarnya ingin kami uji. Kami kemudian menegaskan bahawa permainan itu tidak boleh dimainkan. Ujian lulus. Tetapi dalam banyak kes, positif palsu adalah perkara biasa. Oleh itu atas sebab keselamatan kita boleh menegaskan kebenaran dan memastikan ujian itu gagal. 🎜
private function addEnoughPlayers($game) {
	for($i = 0; $i < Game::$minimumNumberOfPlayers; $i++) {
		$game->add('A Player');
	}
}
Salin selepas log masuk
Salin selepas log masuk
🎜Memang! 🎜
function testAGameWithNotEnoughPlayersIsNotPlayable() {
	$game = new Game();
	$this->assertFalse($game->isPlayable());
	$this->addJustNothEnoughPlayers($game);
	$this->assertFalse($game->isPlayable());
}

private function addJustNothEnoughPlayers($game) {
	for($i = 0; $i < Game::$minimumNumberOfPlayers - 1; $i++) {
		$game->add('A player');
	}
}
Salin selepas log masuk
Salin selepas log masuk
🎜Setakat ini, sangat menjanjikan. Kami berjaya menguji nilai pulangan awal kaedah, yang diwakili oleh 🎜keadaan🎜 awal kelas Permainan. Perhatikan perkataan yang ditekankan: "negeri." Kami perlu mencari jalan untuk mengawal keadaan permainan. Kami perlu mengubahnya supaya ia mempunyai bilangan pemain minimum. 🎜 🎜Jika kami menganalisis kaedah Add() Permainan, kami akan melihat bahawa ia menambahkan elemen pada tatasusunan kami. 🎜
private function addEnoughPlayers($game) {
	$this->addManyPlayers($game, Game::$minimumNumberOfPlayers);
}

private function addJustNothEnoughPlayers($game) {
	$this->addManyPlayers($game, Game::$minimumNumberOfPlayers - 1);
}

private function addManyPlayers($game, $numberOfPlayers) {
	for ($i = 0; $i < $numberOfPlayers; $i++) {
		$game->add('A Player');
	}
}
Salin selepas log masuk
Salin selepas log masuk
🎜Andaian kami dikuatkuasakan dengan menggunakan kaedah add() dalam RunnerFunctions.php. 🎜
class GameTest extends PHPUnit_Framework_TestCase {

	private $game;

	function setUp() {
		$this->game = new Game;
	}

	function testAGameWithNotEnoughPlayersIsNotPlayable() {
		$this->assertFalse($this->game->isPlayable());
		$this->addJustNothEnoughPlayers();
		$this->assertFalse($this->game->isPlayable());
	}

	function testAfterAddingEnoughPlayersToANewGameItIsPlayable() {
		$this->addEnoughPlayers($this->game);
		$this->assertTrue($this->game->isPlayable());
	}

	private function addEnoughPlayers() {
		$this->addManyPlayers(Game::$minimumNumberOfPlayers);
	}

	private function addJustNothEnoughPlayers() {
		$this->addManyPlayers(Game::$minimumNumberOfPlayers - 1);
	}

	private function addManyPlayers($numberOfPlayers) {
		for ($i = 0; $i < $numberOfPlayers; $i++) {
			$this->game->add('A Player');
		}
	}

}
Salin selepas log masuk
Salin selepas log masuk
🎜Berdasarkan pemerhatian ini, kita boleh membuat kesimpulan bahawa dengan menggunakan add() dua kali, kita seharusnya dapat membuat Permainan memasuki keadaan dengan dua pemain. 🎜
function testItCanAddANewPlayer() {
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
}
Salin selepas log masuk
Salin selepas log masuk
🎜Dengan menambahkan kaedah ujian kedua, kami boleh memastikan bahawa isPlayable() kembali benar jika syarat dipenuhi. 🎜 🎜Tetapi anda mungkin fikir ini bukan ujian unit. Kami menggunakan kaedah add()! Kami mengamalkan lebih daripada kod minimum. Kita hanya boleh menambah elemen pada tatasusunan $players dan tidak bergantung pada kaedah add() sama sekali. 🎜 🎜Nah, jawapannya ya dan tidak. Dari perspektif teknikal, kita boleh melakukan ini. Ia akan mempunyai kelebihan kawalan langsung tatasusunan. Walau bagaimanapun, ia mempunyai kelemahan pertindihan kod antara kod dan ujian. Jadi pilih salah satu pilihan jelek yang anda fikir anda boleh hidup dan gunakannya. Saya secara peribadi lebih suka menggunakan semula kaedah seperti add(). 🎜 🎜Ujian pemfaktoran semula🎜 🎜Kami berada dalam status hijau dan kami sedang memfaktorkan semula. Bolehkah kita membuat ujian kita lebih baik? Ya kita boleh. Kami boleh menukar ujian pertama kami untuk mengesahkan semua syarat tanpa pemain yang mencukupi. 🎜
function testItCanAddANewPlayer() {
	$this->assertEquals(0, count($this->game->players));
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
}
Salin selepas log masuk
Salin selepas log masuk
🎜Anda mungkin pernah mendengar tentang konsep "satu penegasan setiap ujian". Saya kebanyakannya bersetuju dengan ini, tetapi jika anda mempunyai ujian yang mengesahkan satu konsep dan memerlukan berbilang pernyataan untuk melakukan pengesahan, saya fikir ia boleh diterima untuk menggunakan berbilang pernyataan. Pandangan ini juga sangat digalakkan dalam ajaran Robert C. Martin. 🎜 🎜Tetapi bagaimana pula dengan kaedah ujian kedua kami? Adakah ini cukup? Saya kata tidak. 🎜
function testItCanAddANewPlayer() {
	$this->assertEquals(0, count($this->game->players));
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
	$this->assertEquals(0, $this->game->places[1]);
	$this->assertEquals(0, $this->game->purses[1]);
	$this->assertFalse($this->game->inPenaltyBox[1]);
}
Salin selepas log masuk
Salin selepas log masuk
🎜Dua panggilan ini mengganggu saya sedikit. Ia adalah pelaksanaan terperinci yang tidak dijelaskan secara eksplisit dalam pendekatan kami. Mengapa tidak mengekstraknya ke dalam kaedah peribadi? 🎜
function testAfterAddingEnoughPlayersToANewGameItIsPlayable() {
	$game = new Game();
	$this->addEnoughPlayers($game);
	$this->assertTrue($game->isPlayable());
}

private function addEnoughPlayers($game) {
	$game->add('First Player');
	$game->add('Second Player');
}
Salin selepas log masuk
Salin selepas log masuk

这要好得多,它也让我们想到了另一个我们错过的概念。在这两次测试中,我们都以这样或那样的方式表达了“足够多的玩家”的概念。但多少才够呢?是两个吗?是的,目前是这样。但是,如果 Game 的逻辑需要至少三个玩家,我们是否希望测试失败?我们不希望这种情况发生。我们可以为其引入一个公共静态类字段。

class Game {
	static $minimumNumberOfPlayers = 2;

	// ... //

	function  __construct() {
		// ... //
	}

	function isPlayable() {
		return ($this->howManyPlayers() >= self::$minimumNumberOfPlayers);
	}

	// ... //
}
Salin selepas log masuk
Salin selepas log masuk

这将使我们能够在测试中使用它。

private function addEnoughPlayers($game) {
	for($i = 0; $i < Game::$minimumNumberOfPlayers; $i++) {
		$game->add('A Player');
	}
}
Salin selepas log masuk
Salin selepas log masuk

我们的小助手方法只会添加玩家,直到添加足够的玩家为止。我们甚至可以为我们的第一次测试创建另一个这样的方法,因此我们添加了几乎足够的玩家。

function testAGameWithNotEnoughPlayersIsNotPlayable() {
	$game = new Game();
	$this->assertFalse($game->isPlayable());
	$this->addJustNothEnoughPlayers($game);
	$this->assertFalse($game->isPlayable());
}

private function addJustNothEnoughPlayers($game) {
	for($i = 0; $i < Game::$minimumNumberOfPlayers - 1; $i++) {
		$game->add('A player');
	}
}
Salin selepas log masuk
Salin selepas log masuk

但这引入了一些重复。我们的两个辅助方法非常相似。我们不能从中提取第三个吗?

private function addEnoughPlayers($game) {
	$this->addManyPlayers($game, Game::$minimumNumberOfPlayers);
}

private function addJustNothEnoughPlayers($game) {
	$this->addManyPlayers($game, Game::$minimumNumberOfPlayers - 1);
}

private function addManyPlayers($game, $numberOfPlayers) {
	for ($i = 0; $i < $numberOfPlayers; $i++) {
		$game->add('A Player');
	}
}
Salin selepas log masuk
Salin selepas log masuk

这更好,但它引入了一个不同的问题。我们减少了这些方法中的重复,但是我们的 $game 对象现在向下传递了三个级别。管理变得越来越困难。是时候在测试的 setUp() 方法中初始化它并重用它了。

class GameTest extends PHPUnit_Framework_TestCase {

	private $game;

	function setUp() {
		$this->game = new Game;
	}

	function testAGameWithNotEnoughPlayersIsNotPlayable() {
		$this->assertFalse($this->game->isPlayable());
		$this->addJustNothEnoughPlayers();
		$this->assertFalse($this->game->isPlayable());
	}

	function testAfterAddingEnoughPlayersToANewGameItIsPlayable() {
		$this->addEnoughPlayers($this->game);
		$this->assertTrue($this->game->isPlayable());
	}

	private function addEnoughPlayers() {
		$this->addManyPlayers(Game::$minimumNumberOfPlayers);
	}

	private function addJustNothEnoughPlayers() {
		$this->addManyPlayers(Game::$minimumNumberOfPlayers - 1);
	}

	private function addManyPlayers($numberOfPlayers) {
		for ($i = 0; $i < $numberOfPlayers; $i++) {
			$this->game->add('A Player');
		}
	}

}
Salin selepas log masuk
Salin selepas log masuk

好多了。所有不相关的代码都在私有方法中,$gamesetUp()中初始化,并且从测试方法中去除了很多污染。然而,我们确实必须在这里做出妥协。在我们的第一个测试中,我们从一个断言开始。这假设 setUp() 将始终创建一个空游戏。现在这样就可以了。但最终,您必须意识到不存在完美的代码。只有您愿意接受的妥协代码。

第二种可测试方法

如果我们从上到下扫描 Game 类,列表中的下一个方法是 add()。是的,与我们在上一段测试中使用的方法相同。但我们可以测试一下吗?

function testItCanAddANewPlayer() {
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
}
Salin selepas log masuk
Salin selepas log masuk

现在这是测试对象的不同方式。我们调用我们的方法,然后验证对象的状态。由于 add() 总是返回 true,因此我们无法测试其输出。但是我们可以从一个空的 Game 对象开始,然后在添加一个用户后检查是否有单个用户。但这足够验证吗?

function testItCanAddANewPlayer() {
	$this->assertEquals(0, count($this->game->players));
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
}
Salin selepas log masuk
Salin selepas log masuk

在调用 add() 之前先验证一下是否没有玩家不是更好吗?好吧,这里可能有点太多了,但正如您在上面的代码中看到的,我们可以做到。当你不确定初始状态时,你应该对其进行断言。这还可以保护您免受将来可能会更改对象初始状态的代码更改的影响。

但是我们是否测试了 add() 方法所做的所有事情?我拒绝。除了添加用户之外,它还为其设置了很多设置。我们还应该检查这些。

function testItCanAddANewPlayer() {
	$this->assertEquals(0, count($this->game->players));
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
	$this->assertEquals(0, $this->game->places[1]);
	$this->assertEquals(0, $this->game->purses[1]);
	$this->assertFalse($this->game->inPenaltyBox[1]);
}
Salin selepas log masuk
Salin selepas log masuk

这样更好。我们验证 add() 方法执行的每个操作。这次,我更愿意直接测试 $players 数组。为什么?我们可以使用 howManyPlayers() 方法,它基本上做同样的事情,对吗?好吧,在这种情况下,我们认为更重要的是通过 add() 方法对对象状态的影响来描述我们的断言。如果我们需要更改 add(),我们预计测试其严格行为的测试将会失败。我和 Syneto 的同事就这个问题进行了无休止的争论。特别是因为这种类型的测试在测试与 add() 方法的实际实现方式之间引入了强耦合。因此,如果您更愿意以相反的方式进行测试,这并不意味着您的想法是错误的。

我们可以安全地忽略对输出的测试,即 echoln() 行。他们只是在屏幕上输出内容。我们还不想触及这些方法。我们的金主完全靠这个输出。

重构测试(之二)

我们有另一种经过测试的方法,通过了全新的测试。是时候重构它们了,只是一点点。让我们从测试开始。最后三个断言是不是有点令人困惑?它们似乎与添加玩家没有严格关系。让我们改变它:

function testItCanAddANewPlayer() {
	$this->assertEquals(0, count($this->game->players));
	$this->game->add('A player');
	$this->assertEquals(1, count($this->game->players));
	$this->assertDefaultPlayerParametersAreSetFor(1);
}
Salin selepas log masuk

这样更好。该方法现在更加抽象、可重用、命名更明确,并且隐藏了所有不重要的细节。

重构 add() 方法

我们可以用我们的生产代码做类似的事情。

function add($playerName) {
	array_push($this->players, $playerName);
	$this->setDefaultPlayerParametersFor($this->howManyPlayers());

	echoln($playerName . " was added");
	echoln("They are player number " . count($this->players));
	return true;
}
Salin selepas log masuk

我们将不重要的细节提取到setDefaultPlayerParametersFor()中。

private function setDefaultPlayerParametersFor($playerId) {
	$this->places[$playerId] = 0;
	$this->purses[$playerId] = 0;
	$this->inPenaltyBox[$playerId] = false;
}
Salin selepas log masuk

其实这个想法是我写完测试之后就产生的。这是另一个很好的例子,说明测试如何迫使我们从不同的角度思考我们的代码。我们必须利用这种看待问题的不同角度,并让我们的测试指导我们的生产代码设计。

第三种可测试方法

让我们找到第三个候选者进行测试。 howManyPlayers() 太简单并且已经间接测试过。 roll() 太复杂,无法直接测试。另外它返回 nullaskQuestions() 乍一看似乎很有趣,但它都是演示,没有返回值。

currentCategory() 是可测试的,但测试起来非常困难。这是一个巨大的选择器,有十个条件。我们需要一个十行长的测试,然后我们需要认真地重构这个方法,当然还有测试。我们应该记下这个方法,并在完成更简单的方法后再回来使用它。对于我们来说,这将出现在我们的下一个教程中。

wasCorrectlyAnswered() 又变得复杂了。我们需要从中提取可测试的小段代码。然而, wrongAnswer() 似乎很有前途。它在屏幕上输出内容,但它也会改变对象的状态。让我们看看是否可以控制它并测试它。

function testWhenAPlayerEntersAWrongAnswerItIsSentToThePenaltyBox() {
	$this->game->add('A player');
	$this->game->currentPlayer = 0;
	$this->game->wrongAnswer();
	$this->assertTrue($this->game->inPenaltyBox[0]);
}
Salin selepas log masuk

Grrr...编写这个测试方法相当困难。 wrongAnswer() 的行为逻辑依赖于 $this->currentPlayer,但在其表示部分也使用了 $this->players。一个丑陋的例子说明了为什么你不应该混合逻辑和表示。我们将在以后的教程中处理这个问题。现在,我们测试了用户进入惩罚框。我们还必须观察到该方法中有一个 if() 语句。这是我们尚未测试的条件,因为我们只有一个玩家,因此我们不满足该条件。不过,我们可以测试 $currentPlayer 的最终值。但是将这行代码添加到测试中将会导致测试失败。

$this->assertEquals(1, $this->game->currentPlayer);
Salin selepas log masuk

仔细看看私有方法 shouldResetCurrentPlayer() 就会发现问题。如果当前玩家的索引等于玩家数量,则将其重置为零。啊啊啊!我们实际上输入的是if()!

function testWhenAPlayerEntersAWrongAnswerItIsSentToThePenaltyBox() {
	$this->game->add('A player');
	$this->game->currentPlayer = 0;
	$this->game->wrongAnswer();
	$this->assertTrue($this->game->inPenaltyBox[0]);
	$this->assertEquals(0, $this->game->currentPlayer);
}

function testCurrentPlayerIsNotResetAfterWrongAnswerIfOtherPlayersDidNotYetPlay() {
	$this->addManyPlayers(2);
	$this->game->currentPlayer = 0;
	$this->game->wrongAnswer();
	$this->assertEquals(1, $this->game->currentPlayer);
}
Salin selepas log masuk

好。我们创建了第二个测试,以测试仍然有玩家没有玩的情况。我们不关心第二次测试的 inPenaltyBox 状态。我们只对当前玩家的索引感兴趣。

最终的可测试方法

我们可以测试然后重构的最后一个方法是 didPlayerWin()

function didPlayerWin() {
	$numberOfCoinsToWin = 6;
	return !($this->purses[$this->currentPlayer] == $numberOfCoinsToWin);
}
Salin selepas log masuk

我们立即可以观察到它的代码结构与我们首先测试的方法 isPlayable() 非常相似。我们的解决方案也应该类似。当你的代码如此短,只有两到三行时,执行不止一个小步骤并不是那么大的风险。在最坏的情况下,您将恢复三行代码。因此,让我们一步完成此操作。

function testTestPlayerWinsWithTheCorrectNumberOfCoins() {
	$this->game->currentPlayer = 0;
	$this->game->purses[0] = Game::$numberOfCoinsToWin;
	$this->assertTrue($this->game->didPlayerWin());
}
Salin selepas log masuk

但是等等!那失败了。这怎么可能?不应该过去吗?我们提供了正确数量的硬币。如果我们研究我们的方法,我们会发现一些误导性的事实。

return !($this->purses[$this->currentPlayer] == $numberOfCoinsToWin);
Salin selepas log masuk

返回值实际上是取反的。因此,该方法并不是告诉我们玩家是否获胜,而是告诉我们玩家是否没有赢得比赛。我们可以进去找到使用该方法的地方,并在那里否定它的价值。然后在此处更改其行为,以免错误地否定答案。但它是在 wasCorrectlyAnswered() 中使用的,我们还无法对这个方法进行单元测试。也许暂时,简单的重命名以突出显示正确的功能就足够了。

function didPlayerNotWin() {
	return !($this->purses[$this->currentPlayer] == self::$numberOfCoinsToWin);
}
Salin selepas log masuk

想法与结论

教程到此就结束了。虽然我们不喜欢名称中的否定,但这是我们目前可以做出的妥协。当我们开始重构代码的其他部分时,这个名称肯定会改变。此外,如果您看一下我们的测试,它们现在看起来很奇怪:

function testTestPlayerWinsWithTheCorrectNumberOfCoins() {
	$this->game->currentPlayer = 0;
	$this->game->purses[0] = Game::$numberOfCoinsToWin;
	$this->assertFalse($this->game->didPlayerNotWin());
}
Salin selepas log masuk

通过在否定方法上测试 false,并使用表明真实结果的值来执行,我们给代码的可读性带来了很多混乱。但这目前来说很好,因为我们确实需要在某个时候停下来,对吗?

在下一个教程中,我们将开始研究 Game 类中的一些更困难的方法。感谢您的阅读。

Atas ialah kandungan terperinci Kod warisan pemfaktoran semula: Bahagian 5 - Satu cara untuk menjadikan permainan anda boleh diuji. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!