我们如何在PHP的金融科技启动中测试代码
#startup #php #测试

我经常听到开发人员对他们为什么不编写测试的不同论点是合理的。

其中最受欢迎的是:

  1. 我确定我的代码是没有错误的
  2. 写作测试很困难
  3. 企业不想花钱在写作测试上
  4. 我不明白如何写它们
  5. 这可以是您的选择

只是忘记它!测试是代码的强制性部分,也是您作为软件工程师的责任。

在本文中,我想描述我们如何在金融科技启动中测试我们的代码,这使我们能够确保任何回归对我们安全的信心。

让我们从一些基本概念和原理开始

为什么需要单位测试?

  1. 测试有助于使您的生产更稳定
    • 回归测试
    • 合同修复
  2. 测试提高了开发的可用性
    • 更快的反馈
    • 更简单的重构
  3. 测试组织不良代码
    • 让您考虑测试用例
    • 让您考虑代码质量

质量单位测试是什么样的?

  • 易于理解
  • 错误失败
  • 抵抗重构
  • 快速
  • 环境独立

正常测试是什么样的?

  • 很难理解
  • bugs不会失败
  • 任何重构都迫使您修复测试
  • 环境依赖

那么您如何编写高质量的单位测试?

使用合同。单位合同是单位对您的期望,也是您对该单位的期望。因此,如果您想通过单位测试进行测试,则必须合同。

您需要测试的合同条款是:

  • 有用的工作(例如数据库呼叫或其他第三方服务)
  • 预期结果
  • 在边界条件上结果
  • 例外情况

尝试测试您的班级合同,而不是其实施。

您单位测试的功能必须干净,否则单位测试将测试您的功能的实现。
干净的函数又称确定性函数 - 始终给出输入(x)的相同输出(y)。非确定功能会产生可变输出。确定性意味着随机性的相反,每次都给出相同的结果,而不会以任何方式与环境互动,例如:

Input: [1, 2, 3, 4] > array_reverse > Output: [4, 3, 2, 1]

这是一个很好的单位测试的示例,该单位测试测试了一个干净的功能:

final class ItemGrouperTest extends TestCase
{
    public function testGroupReturnsGroupedItemsGroupedByNameAndCurrency(): void
    {
        $itemGrouper = new ItemGrouper();

        $items = [
            $item1 = new Item(1, 'Nike AF 1', 'GBP', 13000),
            $item2 = new Item(1, 'Nike AF 2', 'GBP', 13000),
            $item3 = new Item(1, 'Nike AF 3', 'EUR', 14000),
            $item4 = new Item(2, 'Nike AF 1', 'GBP', 13000),
        ];

        $groupedItems = $itemGrouper->group(
            ItemGrouper::GROUP_BY_NAME & ItemGrouper::GROUP_BY_CURRENCY,
            ...$items
        );

        $this->assertCount(3, $groupedItems);

        $this->assertCount(2, $groupedItems[0]->getItems());
        $this->assertContains($item1, $groupedItems[0]->getItems());
        $this->assertContains($item2, $groupedItems[0]->getItems());

        $this->assertCount(1, $groupedItems[1]->getItems());
        $this->assertContains($item3, $groupedItems[1]->getItems());

        $this->assertCount(1, $groupedItems[2]->getItems());
        $this->assertContains($item4, $groupedItems[2]->getItems());
    }
}

我们知道方法的行为,提供输入数据,并检查预期的结果,完美!这是我一直希望在确定性函数中看到的测试。

但是,不干净的功能呢?

这些功能通常在服务类中或命名为“班级经理”中调用。他们执行更复杂的操作:致电一项或多项外部服务,这些外部服务的汇总结果,通过链电话使用结果等。

这是这样类的描述:

一个调用外部服务用户列表以获取用户列表的控制器类,然后通过用户formatter类传递此用户列表以准备响应。

如果我们遵循这些类的基本规则,我们可以轻松编写此类测试。

基本规则是:

  1. 必须嘲笑所有外部依赖。
  2. 我们仅通过合同测试外部依赖性:我们检查呼叫,预期结果和例外情况。

让我们写上述给定类的测试:

final class UserControllerTest extends TestCase
{
    public function testListActionReturnsSuccessfulJsonResponseAndCallsAllRelatedDependencies(): void
    {
        $userController = new UserController(
            $userRepository = $this->createMock(UserRepository::class),
            $usersFormatter = $this->createMock(UsersFormatter::class),
        );
        $userController->setContainer($this->createMock(ContainerInterface::class));

        $request = $this->createMock(Request::class);

        $userRepository->expects($this->once())
            ->method('getList')
            ->willReturn($users = [
                $this->createMock(User::class),
                $this->createMock(User::class),
            ]);

        $usersFormatter->expects($this->once())
            ->method('format')
            ->with(...$users)
            ->willReturn($formatterResponse = [
                'key' => 'any response here',
                'why' => 'cuz we dont case about result of 3-rd party services here, just any expected results'
            ]);

        $response = $userController->listAction($request);

        $expectedJson = json_encode($formatterResponse, JsonResponse::DEFAULT_ENCODING_OPTIONS);

        $this->assertSame($expectedJson, $response->getContent());
    }
}

结论

尝试编写更确定性的方法,这些方法很容易被测试覆盖。
不确定性的方法不应未经测试,对预期行为的简单检查可以从意外的错误和错误中保存您的代码。
请记住,您的代码越好被测试所涵盖的范围,而不是保护未来的错误和重构。