如何组织具有相互依赖关系的测试类?

huangapple go评论61阅读模式
英文:

How to organize test classes with interdependencies?

问题

我有多个测试类和测试用例,这些类中的测试用例依赖于其他类的测试用例。我正在为OpenSearch编写Integration Test

我有一个名为CreatePostTest的类,用于测试在OpenSearch中创建文档。然后,我有一个名为UpdatePostTest的类,用于测试更新OpenSearch中的现有文档。因此,为了更新文档,我首先运行CreatePostTest,并获取新文档的ID,然后在UpdatePostTest中使用它。

我尝试使用@depends注释,但出现错误:“此测试依赖于不存在的“App\Tests\Integration\Api\Post\UpdatePostTest::CreatePostTest””。

我有多个具有相互依赖关系的类,我不想将所有测试用例放在同一个测试类中。

如何最好地组织这些测试用例?

英文:

I have multiple test classes and test cases in these classes depends on test case from other classes. I am writing Integration Test for OpenSearch.

I have a CreatePostTest class which tests the creating a document in OpenSearch. Then I have UpdatePostTest class which tests the updating the existing document in OpenSearch. So for updating the document I first run CreatePostTest and grab the ID of new document and use it in UpdatePostTest

class CreatePostTest
{
    public function testCreateDocument(): string
    {
        $postMock = $this->getBaseMockData();
        $response = $this->getCreatePostRequest($postMock);
        self::assertResponseSuccessful($response);
        $post = $response['data']['createPost']['post'];

        return $post['_id'];
    }

    //Other test cases
}

class UpdatePostTest
{
    public function testUpdateDocument(string $postId)
    {
        $postMock = $this->getBaseMockData();
        $postMock['postId'] = $postId;
        $response = $this->getUpdatePostRequest($postMock);
        self::assertResponseSuccessful($response);
    }

    //Other test cases
}

I have tried using @depends annotation but that give me error This test depends on "App\Tests\Integration\Api\Post\UpdatePostTest::CreatePostTest" which does not exist.

I have multiple such classes with interdependencies and I don't want to put all test cases in the same Test class.

What is the best way to organize such test cases?

答案1

得分: 1

你无法为类设置依赖关系。但是,使用测试套件,你可以定义顺序。

这是一个API测试的示例。SecurityControllerTest生成一个在UserControllerTest中需要的JWT令牌。

<testsuites>
    <testsuite name="首要测试">
        <file>tests/Controller/SecurityControllerTest.php</file>
        <file>tests/Controller/UserControllerTest.php</file>
    </testsuite>

    <testsuite name="后续测试">
        <directory>tests</directory>
    </testsuite>
</testsuites>  

根据评论中的问题更新我的答案:

我使用Symfony进行API开发。API是无会话的。测试是ApiTestCases。我使用Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage来在测试期间存储JWT令牌。这意味着我不必自己实现基于文件的缓存/存储。使用MockFileSessionStorage,我有一个键值存储,不需要执行任何其他操作。

对我来说,使用缓存组件(FilesystemAdapter或ArrayAdapter)工作量太大。在我看来,ArrayAdapter也必须集成到Singleton中才能正常工作。

namespace App\Tests\Mock;
use Symfony\Component\HttpFoundation\Session\Session;

class MockFileSessionStorage
{
    public static function getSession(): Session
    {
        $session = new Session(
            new \Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage(
                realpath(__DIR__ . '/../var')
            )
        );
        $session->setId('key');
        return $session;
    }
}

在成功认证后,JWT令牌位于响应主体中的测试方法中。

// SecurityControllerTest
public function testAuthSuccessAndIsJwtTokenInResponseBody()
{
    $session = MockFileSessionStorage::getSession();
    $session->clear();

    // 做一些操作

    $responseData = json_decode($response->getContent(), true);
    $session->set('jwt_token', $responseData['token']);
    $session->save();
}

这是其他测试控制器也需要的一个抽象类方法。

// AbstractController
public function getJwtAuthHeader(array $options): array
{
    if (false === isset($options['headers'])) {
        $options['headers'] = [];
    }
    $session = MockFileSessionStorage::getSession();
    $options['headers']['Authorization'] = sprintf('Bearer %s', $session->get('jwt_token'));
    return $options;
}

以及在UserControllerTest中的调用。

// UserControllerTest
public function testUpdateAction(): void
{
    $options = [];
    $options = $this->getJwtAuthHeader($options);
    $response = static::createClient()->request('PUT', '/api/users', $options);
    // 做一些操作
}
英文:

You cannot set Dependencies for classes. However, using a testsuite, you can define the order.

Here is an example of an API test. The SecurityControllerTest generates a JWT token that is needed in the UserControllerTest.

<testsuites>
    <testsuite name="First Tests">
        <file>tests/Controller/SecurityControllerTest.php</file>
        <file>tests/Controller/UserControllerTest.php</file>
    </testsuite>

    <testsuite name="Following Tests">
        <directory>tests</directory>
    </testsuite>
</testsuites>  

Update my answer because of a question in the comment:

I use Symfony for the API. The API is sessionless. The tests are ApiTestCases. I alienate Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage to store the JWT token during the tests. This meant I did not have to implement a filebased cache/storage myself. With MockFileSessionStorage I have a key-value storage and I do not have to do anything more.

Using a cache component (FilesystemAdapter or ArrayAdapter) was too much work for me at the time. In my opinion, the ArrayAdapter would also have to be integrated into a Singelton for it to work.

namespace App\Tests\Mock;
use Symfony\Component\HttpFoundation\Session\Session;

class MockFileSessionStorage
{
    public static function getSession(): Session
    {
        $session = new Session(
            new \Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage(
                realpath(__DIR__ . '/../var')
            )
        );
        $session->setId('key');
        return $session;
    }
}

In the test method in which the JWT token is in the response body after successful authentication.

// SecurityControllerTest
public function testAuthSuccessAndIsJwtTokenInResponseBody()
{
    $session = MockFileSessionStorage::getSession();
    $session->clear();

    // stuff

    $responseData = json_decode($response->getContent(), true);
    $session->set('jwt_token', $responseData['token']);
    $session->save();
}

This method is an abstract class that other test controllers also need.

// AbstractController
public function getJwtAuthHeader(array $options): array
{
    if (false === isset($options['headers'])) {
        $options['headers'] = [];
    }
    $session = MockFileSessionStorage::getSession();
    $options['headers']['Authorization'] = sprintf('Bearer %s', $session->get('jwt_token'));
    return $options;
}

And the call in UserControllerTest.

// UserControllerTest
public function testUpdateAction(): void
{
    $options = [];
    $options = $this->getJwtAuthHeader($options);
    $response = static::createClient()->request('PUT', '/api/users', $options);
    // stuff
}

huangapple
  • 本文由 发表于 2023年8月5日 15:33:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76840587.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定