Skip to content

Commit c8168a7

Browse files
authored
Merge pull request #45 from BoatraceOpenAPI/feature/issue-27-tests
test: add unit tests
2 parents 3b4c9a0 + d32e8e9 commit c8168a7

File tree

6 files changed

+361
-6
lines changed

6 files changed

+361
-6
lines changed

scraper.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use BOA\Programs\ProgramScraper;
88
use BOA\Programs\ProgramStorage;
9+
use BOA\Programs\ScraperAdapter;
910
use BVP\Scraper\Scraper;
1011
use Carbon\CarbonImmutable as Carbon;
1112

@@ -17,9 +18,9 @@
1718

1819
// v2 の場合のみ ProgramScraper を利用して出走表データを取得
1920
if ($version === 'v2') {
20-
$scraper = new ProgramScraper(
21-
Scraper::getInstance()
22-
);
21+
$scraperInstance = Scraper::getInstance();
22+
$scraperAdapter = new ScraperAdapter($scraperInstance);
23+
$scraper = new ProgramScraper($scraperAdapter);
2324

2425
// 指定日付の出走表データをスクレイピング
2526
$programs = $scraper->scrape($date);

src/ProgramScraper.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace BOA\Programs;
66

7-
use BVP\Scraper\Scraper;
87
use Carbon\CarbonImmutable as Carbon;
98
use Carbon\CarbonInterface;
109

@@ -14,9 +13,9 @@
1413
final class ProgramScraper
1514
{
1615
/**
17-
* @param \BVP\Scraper\Scraper $scraper
16+
* @param \BOA\Programs\ScraperInterface $scraper
1817
*/
19-
public function __construct(private readonly Scraper $scraper)
18+
public function __construct(private readonly ScraperInterface $scraper)
2019
{
2120
//
2221
}

src/ScraperAdapter.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BOA\Programs;
6+
7+
use BVP\Scraper\Scraper;
8+
use Carbon\CarbonInterface;
9+
10+
/**
11+
* @author shimomo
12+
*/
13+
final class ScraperAdapter implements ScraperInterface
14+
{
15+
/**
16+
* @param \BVP\Scraper\Scraper $scraper
17+
*/
18+
public function __construct(private readonly Scraper $scraper)
19+
{
20+
//
21+
}
22+
23+
/**
24+
* @psalm-type ScrapedBoat = array{
25+
* racer_boat_number: int,
26+
* racer_name: string,
27+
* racer_number: int,
28+
* racer_class_number: int,
29+
* racer_branch_number: int,
30+
* racer_birthplace_number: int,
31+
* racer_age: int,
32+
* racer_weight: float,
33+
* racer_flying_count: int,
34+
* racer_late_count: int,
35+
* racer_average_start_timing: float,
36+
* racer_national_top_1_percent: float,
37+
* racer_national_top_2_percent: float,
38+
* racer_national_top_3_percent: float,
39+
* racer_local_top_1_percent: float,
40+
* racer_local_top_2_percent: float,
41+
* racer_local_top_3_percent: float,
42+
* racer_assigned_motor_number: int,
43+
* racer_assigned_motor_top_2_percent: float,
44+
* racer_assigned_motor_top_3_percent: float,
45+
* racer_assigned_boat_number: int,
46+
* racer_assigned_boat_top_2_percent: float,
47+
* racer_assigned_boat_top_3_percent: float
48+
* }
49+
* @psalm-type ScrapedRace = array{
50+
* race_date: string,
51+
* race_stadium_number: int,
52+
* race_number: int,
53+
* race_closed_at: string,
54+
* race_grade_number: int,
55+
* race_title: string,
56+
* race_subtitle: string,
57+
* race_distance: int,
58+
* boats: array<int, ScrapedBoat>
59+
* }
60+
* @psalm-type ScrapedRaces = array<int, ScrapedRace>
61+
* @psalm-type ScrapedStadiumRaces = array<int, ScrapedRaces>
62+
*
63+
* @param \Carbon\CarbonInterface $date
64+
* @return ScrapedStadiumRaces
65+
*/
66+
#[\Override]
67+
public function scrapePrograms(CarbonInterface $date): array
68+
{
69+
/** @psalm-var ScrapedStadiumRaces */
70+
return $this->scraper->scrapePrograms($date);
71+
}
72+
}

src/ScraperInterface.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BOA\Programs;
6+
7+
use Carbon\CarbonInterface;
8+
9+
/**
10+
* @author shimomo
11+
*/
12+
interface ScraperInterface
13+
{
14+
/**
15+
* @psalm-type ScrapedBoat = array{
16+
* racer_boat_number: int,
17+
* racer_name: string,
18+
* racer_number: int,
19+
* racer_class_number: int,
20+
* racer_branch_number: int,
21+
* racer_birthplace_number: int,
22+
* racer_age: int,
23+
* racer_weight: float,
24+
* racer_flying_count: int,
25+
* racer_late_count: int,
26+
* racer_average_start_timing: float,
27+
* racer_national_top_1_percent: float,
28+
* racer_national_top_2_percent: float,
29+
* racer_national_top_3_percent: float,
30+
* racer_local_top_1_percent: float,
31+
* racer_local_top_2_percent: float,
32+
* racer_local_top_3_percent: float,
33+
* racer_assigned_motor_number: int,
34+
* racer_assigned_motor_top_2_percent: float,
35+
* racer_assigned_motor_top_3_percent: float,
36+
* racer_assigned_boat_number: int,
37+
* racer_assigned_boat_top_2_percent: float,
38+
* racer_assigned_boat_top_3_percent: float
39+
* }
40+
* @psalm-type ScrapedRace = array{
41+
* race_date: string,
42+
* race_stadium_number: int,
43+
* race_number: int,
44+
* race_closed_at: string,
45+
* race_grade_number: int,
46+
* race_title: string,
47+
* race_subtitle: string,
48+
* race_distance: int,
49+
* boats: array<int, ScrapedBoat>
50+
* }
51+
* @psalm-type ScrapedRaces = array<int, ScrapedRace>
52+
* @psalm-type ScrapedStadiumRaces = array<int, ScrapedRaces>
53+
*
54+
* @param \Carbon\CarbonInterface $date
55+
* @return ScrapedStadiumRaces
56+
*/
57+
public function scrapePrograms(CarbonInterface $date): array;
58+
}

tests/ProgramScraperTest.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BOA\Previews\Tests;
6+
7+
use BOA\Programs\ProgramScraper;
8+
use BOA\Programs\ScraperInterface;
9+
use Carbon\CarbonImmutable as Carbon;
10+
use PHPUnit\Framework\TestCase;
11+
12+
/**
13+
* @author shimomo
14+
*/
15+
final class ProgramScraperTest extends Testcase
16+
{
17+
/**
18+
* @param \Carbon\CarbonInterface|string $date
19+
* @return void
20+
*/
21+
public function testScrape(): void
22+
{
23+
$mockScraper = $this->createMock(ScraperInterface::class);
24+
$mockScraper->method('scrapePrograms')
25+
->with(Carbon::create(2025, 7, 15))
26+
->willReturn([
27+
$this->testScrapeData(1),
28+
]);
29+
$scraper = new ProgramScraper($mockScraper);
30+
$programs = $scraper->scrape(Carbon::create(2025, 7, 15));
31+
$this->assertSame($this->testScrapeData(0), $programs);
32+
}
33+
34+
/**
35+
* @psalm-type NormalizedBoat array{
36+
* racer_boat_number: int,
37+
* racer_name: string,
38+
* racer_number: int,
39+
* racer_class_number: int,
40+
* racer_branch_number: int,
41+
* racer_birthplace_number: int,
42+
* racer_age: int,
43+
* racer_weight: int|float,
44+
* racer_flying_count: int,
45+
* racer_late_count: int,
46+
* racer_average_start_timing: float,
47+
* racer_national_top_1_percent: float,
48+
* racer_national_top_2_percent: float,
49+
* racer_national_top_3_percent: float,
50+
* racer_local_top_1_percent: float,
51+
* racer_local_top_2_percent: float,
52+
* racer_local_top_3_percent: float,
53+
* racer_assigned_motor_number: int,
54+
* racer_assigned_motor_top_2_percent: float,
55+
* racer_assigned_motor_top_3_percent: float,
56+
* racer_assigned_boat_number: int,
57+
* racer_assigned_boat_top_2_percent: float,
58+
* racer_assigned_boat_top_3_percent: float
59+
* }
60+
*
61+
* @psalm-type NormalizedRace array{
62+
* race_date: string,
63+
* race_stadium_number: int,
64+
* race_number: int,
65+
* race_closed_at: string,
66+
* race_grade_number: int,
67+
* race_title: string,
68+
* race_subtitle: string,
69+
* race_distance: int,
70+
* boats: array<int, NormalizedBoat>
71+
* }
72+
*
73+
* @psalm-type NormalizedRaces array<int, NormalizedRace>
74+
*
75+
* @param int $keyIndex
76+
* @return NormalizedRaces
77+
*/
78+
private function testScrapeData(int $keyIndex): array
79+
{
80+
return [
81+
$keyIndex => [
82+
'race_date' => '2025-07-15',
83+
'race_stadium_number' => 1,
84+
'race_number' => 1,
85+
'race_closed_at' => '2025-07-15 15:17:00',
86+
'race_grade_number' => 5,
87+
'race_title' => 'にっぽん未来プロジェクト競走in桐生',
88+
'race_subtitle' => '予選',
89+
'race_distance' => 1800,
90+
'boats' => [
91+
$keyIndex => [
92+
'racer_boat_number' => 1,
93+
'racer_name' => '松本 浩貴',
94+
'racer_number' => 3860,
95+
'racer_class_number' => 3,
96+
'racer_branch_number' => 11,
97+
'racer_birthplace_number' => 11,
98+
'racer_age' => 52,
99+
'racer_weight' => 52,
100+
'racer_flying_count' => 0,
101+
'racer_late_count' => 0,
102+
'racer_average_start_timing' => 0.19,
103+
'racer_national_top_1_percent' => 4.09,
104+
'racer_national_top_2_percent' => 18.48,
105+
'racer_national_top_3_percent' => 39.13,
106+
'racer_local_top_1_percent' => 4.85,
107+
'racer_local_top_2_percent' => 33.33,
108+
'racer_local_top_3_percent' => 40.74,
109+
'racer_assigned_motor_number' => 24,
110+
'racer_assigned_motor_top_2_percent' => 28,
111+
'racer_assigned_motor_top_3_percent' => 42.67,
112+
'racer_assigned_boat_number' => 23,
113+
'racer_assigned_boat_top_2_percent' => 30.47,
114+
'racer_assigned_boat_top_3_percent' => 51.56,
115+
],
116+
],
117+
],
118+
];
119+
}
120+
}

tests/ProgramStorageTest.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BOA\Programs\Tests;
6+
7+
use BOA\Programs\ProgramStorage;
8+
use PHPUnit\Framework\TestCase;
9+
10+
/**
11+
* @author shimomo
12+
*/
13+
final class ProgramStorageTest extends TestCase
14+
{
15+
/**
16+
* @var non-empty-string
17+
*/
18+
private string $tempDir;
19+
20+
/**
21+
* @return void
22+
*/
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
$this->tempDir = sys_get_temp_dir() . '/program_storage_test_' . uniqid();
27+
mkdir($this->tempDir, 0777, true);
28+
}
29+
30+
/**
31+
* @return void
32+
*/
33+
protected function tearDown(): void
34+
{
35+
if (is_dir($this->tempDir)) {
36+
$files = new \RecursiveIteratorIterator(
37+
new \RecursiveDirectoryIterator($this->tempDir, \FilesystemIterator::SKIP_DOTS),
38+
\RecursiveIteratorIterator::CHILD_FIRST
39+
);
40+
41+
foreach ($files as $file) {
42+
$file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
43+
}
44+
45+
rmdir($this->tempDir);
46+
}
47+
}
48+
49+
/**
50+
* @return void
51+
*/
52+
public function testSave(): void
53+
{
54+
$storage = new ProgramStorage();
55+
$path = $this->tempDir . '/programs.json';
56+
57+
$programs = [
58+
[
59+
'race_date' => '2025-07-15',
60+
'race_stadium_number' => 1,
61+
'race_number' => 1,
62+
'race_closed_at' => '2025-07-15 15:17:00',
63+
'race_grade_number' => 5,
64+
'race_title' => 'にっぽん未来プロジェクト競走in桐生',
65+
'race_subtitle' => '予選',
66+
'race_distance' => 1800,
67+
'boats' => [
68+
[
69+
'racer_boat_number' => 1,
70+
'racer_name' => '松本 浩貴',
71+
'racer_number' => 3860,
72+
'racer_class_number' => 3,
73+
'racer_branch_number' => 11,
74+
'racer_birthplace_number' => 11,
75+
'racer_age' => 52,
76+
'racer_weight' => 52,
77+
'racer_flying_count' => 0,
78+
'racer_late_count' => 0,
79+
'racer_average_start_timing' => 0.19,
80+
'racer_national_top_1_percent' => 4.09,
81+
'racer_national_top_2_percent' => 18.48,
82+
'racer_national_top_3_percent' => 39.13,
83+
'racer_local_top_1_percent' => 4.85,
84+
'racer_local_top_2_percent' => 33.33,
85+
'racer_local_top_3_percent' => 40.74,
86+
'racer_assigned_motor_number' => 24,
87+
'racer_assigned_motor_top_2_percent' => 28,
88+
'racer_assigned_motor_top_3_percent' => 42.67,
89+
'racer_assigned_boat_number' => 23,
90+
'racer_assigned_boat_top_2_percent' => 30.47,
91+
'racer_assigned_boat_top_3_percent' => 51.56,
92+
],
93+
],
94+
],
95+
];
96+
97+
$storage->save($programs, $path);
98+
99+
$this->assertFileExists($path);
100+
101+
$content = json_decode(file_get_contents($path), true);
102+
$this->assertArrayHasKey('programs', $content);
103+
$this->assertSame($programs, $content['programs']);
104+
}
105+
}

0 commit comments

Comments
 (0)