한 테스트가 다른 테스트의 설정 인 테스트를 구성하는 방법은 무엇입니까?


18

통합이 단지 공용 API를 사용하여 시스템을 테스트. 다음과 같은 테스트가 있습니다.

def testAllTheThings():
  email = create_random_email()
  password = create_random_password()

  ok = account_signup(email, password)
  assert ok
  url = wait_for_confirmation_email()
  assert url
  ok = account_verify(url)
  assert ok

  token = get_auth_token(email, password)
  a = do_A(token)
  assert a
  b = do_B(token, a)
  assert b
  c = do_C(token, b)

  # ...and so on...

기본적으로 단일 트랜잭션의 전체 "흐름"을 테스트하려고합니다. 플로우의 각 단계는 이전 단계의 성공에 따라 다릅니다. 외부 API로 제한하기 때문에 데이터베이스에 값을 파고 갈 수는 없습니다.

그래서, 나는`A; 주장; 비; 주장; 씨; assert ... "또는 별도의 테스트 방법으로 나눕니다. 여기서 각 테스트 방법은 이전 테스트의 결과가 필요합니다.

def testAccountSignup():
  # etc.
  return email, password

def testAuthToken():
  email, password = testAccountSignup()
  token = get_auth_token(email, password)
  assert token
  return token

def testA():
  token = testAuthToken()
  a = do_A(token)
  # etc.

나는 이것이 냄새가 생각합니다. 이 테스트를 작성하는 더 좋은 방법이 있습니까?

답변:


10

이 테스트가 빈번하게 실행 되는 경우, 테스트 결과를 편리한 방식으로 제시하는 방법에 중점을 두어야 합니다. 할 것으로 예상되는 사용자에게 합니다.

이러한 관점에서, testAllTheThings거대한 적기가 발생합니다. 매 시간마다 또는 더 빈번하게 (이는 버그가있는 코드베이스와 달리, 그렇지 않으면 다시 실행할 필요가 없음)이 테스트를 실행하고 모든 시간이 동일하게 나타나는 것을 상상해보십시오.FAIL 어떤 단계가 실패했는지 명확하게 표시하지 않고 .

재실행 결과 (코드에서 버그를 수정하는 데 꾸준한 진전이 있다고 가정) 때문에 별도의 방법이 훨씬 더 매력적으로 보입니다.

    FAIL FAIL FAIL FAIL
    PASS FAIL FAIL FAIL -- 1st stage fixed
    PASS FAIL FAIL FAIL
    PASS PASS FAIL FAIL -- 2nd stage fixed
    ....
    PASS PASS PASS PASS -- we're done

참고로, 지난 프로젝트 중 하나에서 종속 테스트 가 너무 많이 다시 실행 되어 사용자가 이후 단계에서 실패로 인해 반복적으로 예상되는 실패가 "트리거"되는 것을보고 싶지 않다고 불평하기 시작했습니다. 그들은이 쓰레기 테스트 결과를 분석하기 위해 열심히 그들에게 그것을하게 말했다 "우리가 반복 귀찮게하지 않습니다 우리는 나머지는 테스트 설계에 의해 실패 이미 알고" .

결과적으로 테스트 개발자는 결국 추가 SKIP상태로 프레임 워크를 확장하고 테스트 관리자 코드에 기능을 추가하여 종속 테스트 실행을 중단 SKIP하고 보고서에서 ped 테스트 결과 를 삭제하는 옵션을 제공 해야했습니다.

    FAIL -- the rest is skipped
    PASS FAIL -- 1st stage fixed, abort after 2nd test
    PASS FAIL
    PASS PASS FAIL -- 2nd stage fixed, abort after 3rd test
    ....
    PASS PASS PASS PASS -- we're done

1
내가 읽을 때 실제로 testAllTheThings를 작성하는 것이 더 좋았지 만 실패한 곳을 명확하게보고하는 것처럼 들립니다.
Javier

2
@Javier 는 어디에서 실패 했는지 명확하게보고 하지만 이론 상으로는 테스트가 자주 실행될 때마다 이러한 작업을 수행하는 사람들은 멍청한 PASS-FAIL 토큰을
gnat

7

테스트 코드와 설정 코드를 분리했습니다. 혹시:

# Setup
def accountSignup():
    email = create_random_email()
    password = create_random_password()

    ok = account_signup(email, password)
    url = wait_for_confirmation_email()
    verified = account_verify(url)
    return email, password, ok, url, verified

def authToken():
    email, password = accountSignup()[:2]
    token = get_auth_token(email, password)
    return token

def getA():
    token = authToken()
    a = do_A()
    return a

def getB():
    a = getA()
    b = do_B()
    return b

# Testing
def testAccountSignup():
    ok, url, verified = accountSignup()[2:]
    assert ok
    assert url
    assert verified

def testAuthToken():
    token = authToken()
    assert token

def testA():
    a = getA()
    assert a

def testB():
    b = getB()
    assert b

생성 된 모든 임의의 정보는 실패한 경우 어설 션에 포함 되어야 합니다. 그렇지 않으면 테스트를 재현 할 수 없습니다. 사용 된 임의의 시드를 기록 할 수도 있습니다. 또한 임의의 경우가 실패 할 경우 회귀를 방지하기 위해 특정 입력을 하드 코딩 된 테스트로 추가하십시오.


1
당신을 위해 +1! 테스트는 코드이며 DRY는 프로덕션에서와 마찬가지로 테스트에 적용됩니다.
DougM

2

아니 훨씬 더 나은,하지만 당신은 코드를 주장에서 적어도 별도의 설치 코드가 있습니다. 전체 스토리를 단계별로 알려주는 별도의 방법을 작성하고 필요한 단계 수를 제어하는 ​​매개 변수를 사용하십시오. 그런 다음 각 테스트는 simulate 4또는 같은 것을 말한 다음 테스트하는 것을 simulate 10주장 할 수 있습니다 .


1

글쎄, "공기 코딩"으로 파이썬 구문을 얻지 못할 수도 있지만 아이디어를 얻었을 것입니다. 다음과 같은 일반적인 기능을 구현할 수 있습니다.

def asserted_call(create_random_email,*args):
    var result=create_random_email(*args)
    assert result
    return result

다음과 같이 테스트를 작성할 수 있습니다.

  asserted_call(account_signup, email, password)
  url = asserted_call(wait_for_confirmation_email)
  asserted_call(account_verify,url)
  token = asserted_call(get_auth_token,email, password)
  # ...

물론이 접근법의 가독성 손실이 그것을 사용할 가치가 있는지는 논쟁의 여지가 있지만, 상용구 코드를 약간 줄입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.