연습

완료됨

이 연습에서는 Pytest를 사용하여 함수를 테스트합니다. 그런 다음, 테스트 실패를 일으키는 함수와 관련하여 발생할 수 있는 몇 가지 잠재적인 문제를 찾아서 해결합니다. 오류를 살펴보고 Pytest의 상세한 오류 보고를 사용하는 것은 프로덕션 코드에서 문제가 있는 테스트 또는 버그를 식별하고 수정하는 데 필수적입니다.

이 연습에서는 시스템 명령을 입력으로 수락하고 필요에 따라 sudo 도구를 접두사로 추가하는 admin_command() 함수를 사용합니다. 테스트를 작성하면 해당 함수에 버그가 검색됩니다.

1단계 - 이 연습을 위해 테스트 파일 추가

  1. 테스트 파일에 Python의 파일 이름 규칙을 사용하여 새 테스트 파일을 만듭니다. 테스트 파일의 이름을 test_exercise.py로 지정하고 다음 코드를 추가합니다.

    def admin_command(command, sudo=True):
        """
        Prefix a command with `sudo` unless it is explicitly not needed. Expects
        `command` to be a list.
        """
        if sudo:
            ["sudo"] + command
        return command
    

    admin_command() 함수는 command 인수를 사용하여 목록을 입력으로 사용하고 필요에 따라 목록에 접두사로 sudo를 추가할 수 있습니다. sudo 키워드 인수가 False로 설정된 경우 입력으로 지정된 것과 동일한 명령을 반환합니다.

  2. 동일한 파일에서admin_command() 함수에 대한 테스트를 추가합니다. 테스트는 샘플 명령을 반환하는 도우미 메서드를 사용합니다.

    class TestAdminCommand:
    
    def command(self):
        return ["ps", "aux"]
    
    def test_no_sudo(self):
        result = admin_command(self.command(), sudo=False)
        assert result == self.command()
    
    def test_sudo(self):
        result = admin_command(self.command(), sudo=True)
        expected = ["sudo"] + self.command()
        assert result == expected
    

참고

실제 코드와 동일한 파일 내에 테스트가 있는 것은 일반적이지 않습니다. 간단히 하기 위해 이 연습의 예제에는 동일한 파일에 실제 코드가 있습니다. 실제 Python 프로젝트에서 테스트는 일반적으로 테스트하는 코드와 파일 및 디렉터리로 구분됩니다.

2단계 - 테스트 실행 및 실패 식별

이제 테스트 파일에 테스트할 함수와 해당 동작을 확인하는 몇 가지 테스트가 있으므로, 테스트를 실행하고 실패를 처리해야 합니다.

  • Python을 통해 파일을 실행합니다.

    $ pytest test_exercise.py
    

    한 번의 테스트 통과와 한 번의 실패로 실행이 완료되어야 하며, 실패 출력은 다음 출력과 유사해야 합니다.

    =================================== FAILURES ===================================
    __________________________ TestAdminCommand.test_sudo __________________________
    
    self = <test_exercise.TestAdminCommand object at 0x10634c2e0>
    
        def test_sudo(self):
            result = admin_command(self.command(), sudo=True)
            expected = ["sudo"] + self.command()
    >       assert result == expected
    E       AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux']
    E         At index 0 diff: 'ps' != 'sudo'
    E         Right contains one more item: 'aux'
    E         Use -v to get the full diff
    
    test_exercise.py:24: AssertionError
    =========================== short test summary info ============================
    FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert...
    ========================= 1 failed, 1 passed in 0.04s ==========================
    

    test_sudo() 테스트에서 출력이 실패합니다. Pytest는 비교되는 두 목록에 대한 세부 정보를 제공합니다. 이 경우 result 변수에는 테스트에 예상되는 sudo 명령이 없습니다.

3단계 - 버그 수정 및 테스트 통과

변경하기 전에 먼저 오류가 발생하는 이유를 이해해야 합니다. 예상 결과가 충족되지 않는다는 것은 알 수 있지만(sudo가 결과에 없음), 그 이유를 알아내야 합니다.

sudo=True 조건이 충족되면 admin_command() 함수에서 다음 코드 줄을 확인합니다.

    if sudo:
        ["sudo"] + command

목록 작업은 값을 반환하는 데 사용되지 않습니다. 반환되지 않으므로 함수는 항상 sudo 없이 명령을 반환합니다.

  1. sudo 명령을 요청할 때 수정된 결과가 사용되도록 admin_command() 함수를 업데이트하여 목록 작업을 반환합니다. 업데이트된 함수는 다음과 같습니다.

    def admin_command(command, sudo=True):
        """
        Prefix a command with `sudo` unless it is explicitly not needed. Expects
        `command` to be a list.
        """
        if sudo:
            return ["sudo"] + command
        return command
    
  2. Pytest를 사용하여 테스트를 다시 실행합니다. Pytest와 함께 -v 플래그를 사용하여 출력의 자세한 정도를 높입니다.

    $ pytest -v test_exercise.py
    
  3. 이제 출력을 확인합니다. 이제 두 개의 통과 테스트가 표시됩니다.

    ============================= test session starts ==============================
    Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
    cachedir: .pytest_cache
    rootdir: /private
    collected 2 items
    
    test_exercise.py::TestAdminCommand::test_no_sudo PASSED                  [ 50%]
    test_exercise.py::TestAdminCommand::test_sudo PASSED                     [100%]
    
    ============================== 2 passed in 0.00s ===============================
    

참고

함수는 다른 대/소문자 구분의 더 많은 값으로 작업할 수 있기 때문에 이러한 변형을 다루기 위해 더 많은 테스트를 추가해야 합니다. 이렇게 하면 나중에 함수 변경으로 인해 다른(예기치 않은) 동작이 발생하는 것을 방지할 수 있습니다.

4단계 - 테스트가 있는 새 코드 추가

이전 단계에서 테스트를 추가한 후에는 함수를 더 많이 변경하고 테스트로 확인하는 데 어려움이 없을 것입니다. 변경 내용을 기존 테스트에서 다루지 않더라도 이전 가정을 위반하지 않는다고 확신할 수 있습니다.

이 경우 admin_command() 함수는 command 인수가 항상 목록임을 맹목적으로 신뢰합니다. 유용한 오류 메시지가 있는 예외가 발생하도록 하여 이를 개선해 보겠습니다.

  1. 먼저 동작을 캡처하는 테스트를 만듭니다. 아직 해당 함수가 업데이트되지 않았지만, 테스트 우선 방식(테스트 주도 개발 또는 TDD라고도 함)을 시도해 보세요.

    • 맨 위에서 pytest를 가져오도록 test_exercise.py 파일을 업데이트합니다. 이 테스트는 pytest 프레임워크의 내부 도우미를 사용합니다.
    import pytest
    
    • 이제 클래스에 새 테스트를 추가하여 예외를 확인합니다. 이 테스트는 전달된 값이 목록이 아닌 경우 함수에서 TypeError를 예상해야 합니다.
        def test_non_list_commands(self):
            with pytest.raises(TypeError):
                admin_command("some command", sudo=True)
    
  2. Pytest를 사용하여 테스트를 다시 실행하면 모두 통과해야 합니다.

    ============================= test session starts ==============================
    Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
    rootdir: /private/
    collected 3 items
    
    test_exercise.py ...                                                     [100%]
    
    ============================== 3 passed in 0.00s ===============================
    

    테스트는 TypeError를 검사하기에 충분하지만 유용한 오류 메시지와 함께 코드를 추가하는 것이 좋습니다.

  3. 유용한 오류 메시지를 사용하여 TypeError가 명시적으로 발생하도록 함수를 업데이트합니다.

    def admin_command(command, sudo=True):
        """
        Prefix a command with `sudo` unless it is explicitly not needed. Expects
        `command` to be a list.
        """
        if not isinstance(command, list):
            raise TypeError(f"was expecting command to be a list, but got a {type(command)}")
        if sudo:
            return ["sudo"] + command
        return command
    
  4. 마지막으로 test_non_list_commands() 메서드를 업데이트하여 오류 메시지를 확인합니다.

    def test_non_list_commands(self):
        with pytest.raises(TypeError) as error:
            admin_command("some command", sudo=True)
        assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"
    

    업데이트된 테스트는 모든 예외 정보를 보유하는 변수로 error를 사용합니다. error.value.args를 사용하면 예외 인수를 살펴볼 수 있습니다. 이 경우 첫 번째 인수에는 테스트에서 확인할 수 있는 오류 문자열이 있습니다.

작업 확인

이 시점에서 test_exercise.py라는 Python 테스트 파일이 있어야 하며, 여기에는 다음이 포함되어야 합니다.

  • 인수 및 키워드 인수를 허용하는 admin_command() 함수입니다.
  • admin_command() 함수에 유용한 오류 메시지가 있는 TypeError 예외입니다.
  • command() 도우미 메서드와 admin_command() 함수를 확인하는 세 가지 테스트 메서드가 있는 TestAdminCommand() 테스트 클래스입니다.

터미널에서 테스트를 실행하면 모든 테스트가 오류 없이 통과해야 합니다.