使用ChatGPT进行有效的单元测试

694 阅读3分钟

在这篇文章中,我们将探讨我在进行自动化软件测试时利用ChatGPT功能的一个工作流程。

如果你对用人工智能提高开发者的生产力感兴趣,你可能会对我的另一篇关于使用ChatGPT和PUML进行软件架构可视化的文章感兴趣。

迭代提示的工作流程

我所使用的一般工作流程包括以下步骤:

  1. 用有限的信息进行初始提示。

  2. 审查和完善输出。

  3. 用固定的输出进行提示,并说明如何进一步改进。

  4. ChatGPT根据提供的修复方法生成改进的输出。

  5. 重复这个过程,直到输出符合你的标准。

这个工作流程的强大之处在于,能够对生成的输出的特定部分提供反馈,并让人工智能模型对整个输出进行改进。

这种方法可以用于不同的应用,例如写作或学习新概念,并反复提出问题以加深理解。

通过迭代提示进行单元测试

让我们深入了解一下单元测试的例子。在我的例子中,我将要求ChatGPT为PaymentOrderProcessor类写单元测试。在这个类中有很多的依赖关系,有一些事情会导致成功或失败。此外,在执行过程中可能会抛出不同类型的异常。

我使用PHP和phpunit进行测试,但是这种方法可以应用于任何单元测试框架或语言。

PaymentOrderProcessor 类的定义如下。我们将为其processPayment 方法编写单元测试。

class PaymentOrderProcessor {

    private PaymentOrderRepositoryInterface $repository;
    private NotificationSenderInterface $notificationService;
    private APIClientInterface $apiClient;
    private LoggerInterface $logger;
    private FraudDetectorInterface $fraudDetector;

    public function __construct(
        PaymentOrderRepositoryInterface $repository,
        NotificationSenderInterface $notificationService,
        APIClientInterface $apiClient,
        LoggerInterface $logger,
        FraudDetectorInterface $fraudDetector
    ) {
        $this->repository = $repository;
        $this->notificationService = $notificationService;
        $this->apiClient = $apiClient;
        $this->logger = $logger;
        $this->fraudDetector = $fraudDetector;
    }

    public function processPayment(PaymentOrder $paymentOrder): PaymentOrderProcessingResult
    {
        if ($this->fraudDetector->isSuspicious($paymentOrder)) {
            $this->logger->warning("PaymentOrder {$paymentOrder->getId()} marked as suspicious");
            $this->notificationService->sendNotification($paymentOrder, false);
            return new PaymentOrderProcessingResult(false, "Payment marked as suspicious");
        }

        $paymentOrderApiResponse = $this->apiClient->sendPaymentOrderRequest($paymentOrder);

        if ($paymentOrderApiResponse->isSuccessful() === false) {
            throw new PaymentOrderRequestFailure($paymentOrderApiResponse->getMessage());
        }

        try {
            $this->repository->savePaymentOrderResult($paymentOrder, $paymentOrderApiResponse);
        } catch (PersistenceException $e) {
            $this->logger->critical($e->getMessage());
            return new PaymentOrderProcessingResult(false, "Failed to save payment order result");
        }

        $this->notificationService->sendNotification($paymentOrder, true);

        return new PaymentOrderProcessingResult(true);
    }
}

提示 1:

我要求进行单元测试,并提供了一个PaymentOrderProcessor类的定义。

为下面这个类写单元测试。类测试的模拟属性名称应该以 "mock "后缀{类定义}结尾。

image.png

输出 1:

image.png

下面是ChatGPT生成的测试方法的整个主体:

   public function testProcessPaymentSuspicious(): void
    {
        $paymentOrder = new PaymentOrder(1, 100, 'USD');

        $this->fraudDetectorMock
            ->expects($this->once())
            ->method('isSuspicious')
            ->with($paymentOrder)
            ->willReturn(true);

        $this->loggerMock
            ->expects($this->once())
            ->method('warning')
            ->with("PaymentOrder 1 marked as suspicious");

        $this->notificationServiceMock
            ->expects($this->once())
            ->method('sendNotification')
            ->with($paymentOrder, false);

        $processor = new PaymentOrderProcessor(
            $this->repositoryMock,
            $this->notificationServiceMock,
            $this->apiClientMock,
            $this->loggerMock,
            $this->fraudDetectorMock
        );

        $result = $processor->processPayment($paymentOrder);

        $this->assertInstanceOf(PaymentOrderProcessingResult::class, $result);
        $this->assertFalse($result->isSuccess());
        $this->assertEquals("Payment marked as suspicious", $result->getMessage());
    }
}

这段代码几乎是正确的!

我应用了以下非常简单的修正,删除了不存在的参数和方法的假设。

image.png

然后测试就通过了:

image.png

第2个提示:

在第二个提示中,鉴于ChatGPT已经解析了PaymentOrderProcessor 的类定义,并为其中一个场景编写了几乎完全正确的测试,我可以继续进行迭代提示。

我将只提供ChatGPT生成的方法的主体,并应用我的简单修正。

我做了一些修正。根据下面固定的例子写更多的测试。包括成功场景
{方法体}。

image.png

输出2:

这里有更多的测试,包括成功场景:{其余的测试方法}。

image.png

整个输出:

public function testProcessPaymentSuccessful(): void
{
    $paymentOrder = new PaymentOrder(1, 100);

    $this->fraudDetectorMock
        ->expects($this->once())
        ->method('isSuspicious')
        ->with($paymentOrder)
        ->willReturn(false);

    $paymentOrderApiResponse = new PaymentOrderApiResponse(true);

    $this->apiClientMock
        ->expects($this->once())
        ->method('sendPaymentOrderRequest')
        ->with($paymentOrder)
        ->willReturn($paymentOrderApiResponse);

    $this->repositoryMock
        ->expects($this->once())
        ->method('savePaymentOrderResult')
        ->with($paymentOrder, $paymentOrderApiResponse);

    $this->notificationServiceMock
        ->expects($this->once())
        ->method('sendNotification')
        ->with($paymentOrder, true);

    $processor = new PaymentOrderProcessor(
        $this->repositoryMock,
        $this->notificationServiceMock,
        $this->apiClientMock,
        $this->loggerMock,
        $this->fraudDetectorMock
    );

    $result = $processor->processPayment($paymentOrder);

    $this->assertInstanceOf(PaymentOrderProcessingResult::class, $result);
    $this->assertTrue($result->isSuccessful());
}

public function testProcessPaymentPaymentOrderRequestFailure(): void
{
    $paymentOrder = new PaymentOrder(1, 100);

    $this->fraudDetectorMock
        ->expects($this->once())
        ->method('isSuspicious')
        ->with($paymentOrder)
        ->willReturn(false);

    $paymentOrderApiResponse = new PaymentOrderApiResponse(false, 'Payment failed');

    $this->apiClientMock
        ->expects($this->once())
        ->method('sendPaymentOrderRequest')
        ->with($paymentOrder)
        ->willReturn($paymentOrderApiResponse);

    $this->expectException(PaymentOrderRequestFailure::class);
    $this->expectExceptionMessage('Payment failed');

    $processor = new PaymentOrderProcessor(
        $this->repositoryMock,
        $this->notificationServiceMock,
        $this->apiClientMock,
        $this->loggerMock,
        $this->fraudDetectorMock
    );

    $processor->processPayment($paymentOrder);
}

public function testProcessPaymentPersistenceException(): void
{
    $paymentOrder = new PaymentOrder(1, 100);

    $this->fraudDetectorMock
        ->expects($this->once())
        ->method('isSuspicious')
        ->with($paymentOrder)
        ->willReturn(false);

    $paymentOrderApiResponse = new PaymentOrderApiResponse(true);

    $this->apiClientMock
        ->expects($this->once())
        ->method('sendPaymentOrderRequest')
        ->with($paymentOrder)
        ->willReturn($paymentOrderApiResponse);

    $this->repositoryMock
        ->expects($this->once())
        ->method('savePaymentOrderResult')
        ->with($paymentOrder, $paymentOrderApiResponse)
        ->willThrowException(new PersistenceException('Failed to save payment order result'));

    $this->loggerMock
        ->expects($this->once())
        ->method('critical');

    $processor = new PaymentOrderProcessor(
        $this->repositoryMock,
        $this->notificationServiceMock,
        $this->apiClientMock,
        $this->loggerMock,
        $this->fraudDetectorMock
    );

    $result = $processor->processPayment($paymentOrder);

    $this->assertInstanceOf(PaymentOrderProcessingResult::class, $result);
    $this->assertFalse($result->isSuccessful());
}

这些测试几乎都是正确的!

ChatGPT不知道PaymentOrderApiResponse 类的第二个参数$message ,因为它在被测试的类中从未被访问。

我应用了以下修正:

image.png

现在所有的测试都通过了!

image.png

试试吧!

经过一段时间的练习,你会感觉到这种方法对你的各种任务(包括测试或编写代码)的作用,但你可以学会用它来相当快地加速编写单元测试。