如何在一个测试是另一测试的设置的地方构造测试?


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; 断言; B; 断言; C; 断言...”,或者我将其分解为单独的测试方法,其中每个测试方法都需要先执行测试的结果,然后才能执行其工作:

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.

我觉得这闻起来。有没有更好的方式编写这些测试?

Answers:


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,但是要清楚地报告失败的地方。
哈维尔2013年

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 2013年

2

并没有太大的改善,但是您至少可以将设置代码与断言代码分开。编写一个单独的方法来逐步讲述整个故事,并使用一个参数来控制应执行的步骤。然后,每个测试可以说出类似simulate 4或的simulate 10含义,然后断言所测试的内容。


1

好吧,我可能无法通过“空中编码”在此处获得Python语法,但是我想您已经明白了:您可以实现一个像这样的通用函数:

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)
  # ...

当然,是否值得使用这种方法的可读性值得商bat,但这会稍微减少样板代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.