This is a documentation for Board Game Arena: play board games online !

Testing by developer: Difference between revisions

From Board Game Arena
Jump to navigation Jump to search
Line 229: Line 229:


         self::assertSame('Bga\\Games\\Reversi\\States\\EndScore', $state->onEnteringState());
         self::assertSame('Bga\\Games\\Reversi\\States\\EndScore', $state->onEnteringState());
    }
    public function testReturnsEndScoreWhenActivePlayerHasNoDisc(): void
    {
        $game = $this->createGame();
        $this->attachBoardManager($game, $this->createBoardManagerStub(10, [2 => 5]));
        $state = new NextPlayer($game);
        self::assertSame('Bga\\Games\\Reversi\\States\\EndScore', $state->onEnteringState());
    }
    public function testReturnsEndScoreWhenNeitherPlayerCanMove(): void
    {
        $game = $this->createGame(playerAfterId: 2);
        $this->attachBoardManager($game, $this->createBoardManagerStub(5, [1 => 4, 2 => 4], []));
        $state = new NextPlayer($game);
        self::assertSame('Bga\\Games\\Reversi\\States\\EndScore', $state->onEnteringState());
    }
    public function testPassesTurnWhenActivePlayerCannotMoveButOpponentCan(): void
    {
        $game = $this->createGameMock(playerAfterId: 2);
        $game->expects(self::never())->method('giveExtraTime');
        $this->attachBoardManager($game, $this->createBoardManagerStub(5, [1 => 4, 2 => 4], [2 => [4 => [3 => true]]]));
        $state = new NextPlayer($game);
        self::assertSame(NextPlayer::class, $state->onEnteringState());
     }
     }



Revision as of 10:43, 12 March 2026

When you develop a game you obviously have to test it, this page collects the info about testing in one place

Manual Testing on BGA

For manual testing the most important things to know are:

  • how to start/stop game in one click
  • how to switch between players in one click
  • how to save/restore the game state
  • how to construct the game state automatically

All of these described here https://en.doc.boardgamearena.com/Tools_and_tips_of_BGA_Studio

There is also HUGE checklist of things you need to test manually which you may not even think about, defined here https://en.doc.boardgamearena.com/Pre-release_checklist

Manual Testing locally

HTML/CSS At this era the file sync is almost instant so you do not save much by doing this locally, however if internet is a challenge - here are some tips https://en.doc.boardgamearena.com/Tools_and_tips_of_BGA_Studio#Speed_up_CSS_development_and_layout

PHP You can run and debug php locally using tip https://en.doc.boardgamearena.com/BGA_Studio_Cookbook#Creating_a_test_class_to_run_PHP_locally

Automated Testing

JavaScript

I have not tried it. If you have any success please add instructions here. Theoretically its is possible to hook something like selenium to studio games

PHP

Install PHPunit

Install PHPunit via composer somewhere in home directory, i.e.

  cd /home/<yourusername>/php-composer
  composer require --dev phpunit/phpunit ^13

Then add this to your .bashrc (alias function to be able to test phpunit easily)

 phpunit() {
 /home/<yourusername>/php-composer/vendor/bin/phpunit \
   -c "$PWD/phpunit.xml.dist" \
   --bootstrap "$PWD/tests/bootstrap.php" \
   "$@"
 }

With that, on a new terminal you will be able to run the tests with one of the following commands at the root of your project :

 phpunit
 phpunit tests/BoardManagerTest.php
 phpunit --filter testGivesExtraTime


Setup minimal files

In the tests folder of your project (create it if needed), add :

tests/bootstrap.php

<?php

declare(strict_types=1);

require_once __DIR__ . '/stubs/BgaFrameworkStubs.php';

$autoload = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoload)) {
    require_once $autoload;
}

spl_autoload_register(static function (string $class): void {
    $prefix = 'Bga\\Games\\Reversi\\';

    if (!str_starts_with($class, $prefix)) {
        return;
    }

    $relativeClass = substr($class, strlen($prefix));
    $path = __DIR__ . '/../modules/php/' . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($path)) {
        require_once $path;
    }
});

(replace Reversi by your project name)

tests/stubs/BgaFrameworkStubs.php

<?php

declare(strict_types=1);

require_once __DIR__ . '/stubs/BgaFrameworkStubs.php';

$autoload = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoload)) {
    require_once $autoload;
}

spl_autoload_register(static function (string $class): void {
    $prefix = 'Bga\\Games\\Reversi\\';

    if (!str_starts_with($class, $prefix)) {
        return;
    }

    $relativeClass = substr($class, strlen($prefix));
    $path = __DIR__ . '/../modules/php/' . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($path)) {
        require_once $path;
    }
});

Create first tests

Example of tests for Reversi:

tests/BoardManagerTest.php

<?php

declare(strict_types=1);

namespace Tests;

use Bga\Games\Reversi\BoardManager;
use Bga\Games\Reversi\Game;
use PHPUnit\Framework\TestCase;

final class BoardManagerTest extends TestCase
{
    public function testGetPossibleMovesOnInitialBoardForBlackPlayer(): void
    {
        $board = $this->createEmptyBoard(8);
        $board[4][4] = 2;
        $board[5][5] = 2;
        $board[4][5] = 1;
        $board[5][4] = 1;

        $game = $this->createGame(8);
        $boardManager = $this->createBoardManager($game, $board);

        $moves = $boardManager->getPossibleMoves(1);

        self::assertSame(
            [
                3 => [4 => true],
                4 => [3 => true],
                5 => [6 => true],
                6 => [5 => true],
            ],
            $moves,
        );
    }

    public function testGetPossibleMovesReturnsEmptyArrayWhenNoMoveExists(): void
    {
        $board = $this->createEmptyBoard(4);
        for ($x = 1; $x <= 4; $x++) {
            for ($y = 1; $y <= 4; $y++) {
                $board[$x][$y] = 1;
            }
        }

        $game = $this->createGame(4);
        $boardManager = $this->createBoardManager($game, $board);

        self::assertSame([], $boardManager->getPossibleMoves(2));
    }

    private function createGame(int $boardSize): Game
    {
        $game = $this->createStub(Game::class);
        $game->method('getBoardSize')->willReturn($boardSize);

        return $game;
    }

    private function createBoardManager(Game $game, array $board): BoardManager
    {
        return new class ($game, $board) extends BoardManager {
            public function __construct(Game $game, private array $board)
            {
                parent::__construct($game);
            }

            public function getBoard(): array
            {
                return $this->board;
            }
        };
    }

    private function createEmptyBoard(int $size): array
    {
        $board = [];
        for ($x = 1; $x <= $size; $x++) {
            $board[$x] = [];
            for ($y = 1; $y <= $size; $y++) {
                $board[$x][$y] = null;
            }
        }

        return $board;
    }
}


tests/States/NextPlayerTest.php

<?php

declare(strict_types=1);

namespace Tests\States;

use Bga\Games\Reversi\BoardManager;
use Bga\Games\Reversi\Game;
use Bga\Games\Reversi\States\NextPlayer;
use PHPUnit\Framework\TestCase;

final class NextPlayerTest extends TestCase
{
    public function testReturnsEndScoreWhenNoFreeSquareLeft(): void
    {
        $game = $this->createGame();
        $this->attachBoardManager($game, $this->createBoardManagerStub(0, []));

        $state = new NextPlayer($game);

        self::assertSame('Bga\\Games\\Reversi\\States\\EndScore', $state->onEnteringState());
    }

    public function testGivesExtraTimeAndReturnsPlayDiscWhenPlayerCanMove(): void
    {
        $game = $this->createGameMock();
        $game->expects(self::once())
            ->method('giveExtraTime')
            ->with(1, null);
        $this->attachBoardManager($game, $this->createBoardManagerStub(5, [1 => 4, 2 => 4], [1 => [4 => [3 => true]]]));

        $state = new NextPlayer($game);

        self::assertSame('Bga\\Games\\Reversi\\States\\PlayDisc', $state->onEnteringState());
    }

    private function createGame(int $activePlayerId = 1, int $playerAfterId = 2): Game
    {
        $game = $this->createStub(Game::class);
        $game->method('activeNextPlayer')->willReturn($activePlayerId);
        $game->method('getPlayerAfter')->willReturn($playerAfterId);

        return $game;
    }

    private function createGameMock(int $activePlayerId = 1, int $playerAfterId = 2): Game
    {
        $game = $this->getMockBuilder(Game::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['activeNextPlayer', 'getPlayerAfter', 'giveExtraTime'])
            ->getMock();
        $game->method('activeNextPlayer')->willReturn($activePlayerId);
        $game->method('getPlayerAfter')->willReturn($playerAfterId);

        return $game;
    }

    private function createBoardManagerStub(int $freeSquares, array $discsByPlayer, array $possibleMovesByPlayer = []): BoardManager
    {
        $boardManager = $this->createStub(BoardManager::class);
        $boardManager->method('countFreeSquares')->willReturn($freeSquares);
        $boardManager->method('getDiscCountsByPlayer')->willReturn($discsByPlayer);
        $boardManager->method('getPossibleMoves')->willReturnCallback(
            static fn (int $playerId): array => $possibleMovesByPlayer[$playerId] ?? []
        );

        return $boardManager;
    }

    private function attachBoardManager(Game $game, BoardManager $boardManager): void
    {
        $game->boardManager = $boardManager;
    }
}

Running the tests

Run the tests using

 phpunit

If you want to run a single test file

 phpunit tests/BoardManagerTest.php

If you want to run a single test

 phpunit --filter testGivesExtraTime

If you came here from Reversi Tutorial, go back to it https://en.doc.boardgamearena.com/Tutorial_reversi#Optional:_add_unit_tests

Play testing on studio

To play test on studio you can use your test accounts dev0... dev9. You can give some of these account to other people just make sure you change the password. You should not encourage other people who are not developers to create studio account, this is against bga policy.