级联测试是否违反“每个测试一个断言”原则?

软件测试 测试设计 系统测试
2022-01-12 20:56:56

由于我一直在编写测试,所以我一直坚持每个测试一个断言的原则。现在假设我有一个简单的应用程序,我想使用以下三个测试用例执行系统测试:

  1. 测试用户是否可以注册新帐户
  2. 测试用户是否可以登录
  3. 测试登录用户是否可以添加帖子

我知道在单元测试中,所有三个测试都可以通过模拟其他组件来独立执行。但是,在系统测试中,2. 取决于 1.,而 3. 取决于 2。

我一直在思考如何在测试中对这种依赖关系进行建模,同时仍然保持每个测试原则的一个断言。直到最近,我只是简单地编写三个独立的测试用例,其中每个必要的步骤都在测试代码中重复。然而,这通常会导致测试代码重复,从而导致难以维护测试代码。所以我开始像这个伪代码示例一样级联我的测试:

def test_register_user():
   new_user = app.register new user()
   assert(new_user in app.list_of_users())
   return new_user

def test_login():
   user = test_register_user()
   logged_in_user = app.log_in_user(user)
   assert(logged_in_user in app.list_of_logged_in_users())
   return logged_in_user

def test_write_post():
   app.write_post(text="text", user=test_login())
   assert("text" in app.list_posts())

所以如果test_write_post()被执行,它会调用test_login()and test_register_user()我喜欢这种方法,因为它确实减少了测试代码的重复。但是,我不确定它是否违反了“每个测试一个断言”的原则。一方面,只有一个断言直接属于被测试的方法。另一方面,如果您遵循测试用例的执行路径,则会检查多个断言。

我的问题是:所呈现的级联测试风格是否违反了“每个测试一个断言”的原则?如果是这样,是否有更好的做法可以处理系统测试中的依赖关系,同时保持测试代码的可维护性?

2个回答

我更喜欢单独测试,测试不应该相互依赖,以便能够自己运行它们。这使得开发人员更容易测试应用程序的一小部分。此外,如果每个测试都有自己的运行环境,您可以并行运行测试,而不会使测试相互冲突。

每个测试的设置都会有一些额外的开销,这会使套件的单个测试运行速度变慢,但是由于您可以并行运行测试,因此从长远来看它会更容易扩展。

为测试中的每个重复行为创建一个函数。这很方便,因为如果注册更改,您不必更新多个测试。保持你的代码DRY也看看Page Object Pattern

页面对象是一种在测试自动化中变得流行的设计模式,用于增强测试维护和减少代码重复。页面对象是一个面向对象的类,用作 AUT 页面的接口。然后,测试在需要与 UI 的该页面交互时使用此页面对象类的方法。好处是如果页面的 UI 发生变化,测试本身不需要更改,只需要更改页面对象中的代码。随后,支持该新 UI 的所有更改都位于一个位置。

我会设置你的例子是这样的:

def setup_new_user():
    new_user = app.register new user()
    return new_user

def login(user):
    logged_in_user = app.log_in_user(user)
    return logged_in_user

def test_register_user():
    new_user = setup_new_user()
    assert(new_user in app.list_of_users())

def test_login():
    logged_in_user = login(setup_new_user())
    assert(logged_in_user in app.list_of_logged_in_users())

def test_write_post():
    logged_in_user = login(setup_new_user())
    app.write_post(text="text", user=test_login())
    assert("text" in app.list_posts())

所呈现的级联测试风格是否违反了“每个测试一个断言”的原则?

是的,它确实。虽然样式非常简洁,但您的解决方案的一个缺点是使用不同的用户(例如已经注册的用户)运行 test_login 和 test_write_post 变得更加困难。我个人发现一个更好的解决方案是拥有级联测试套件,它可以定义运行测试的顺序。

def test_register_user(username):
   new_user = app.register new user(username)
   assert(new_user in app.list_of_users())

def test_login(username):
   logged_in_user = app.log_in_user(username)
   assert(logged_in_user in app.list_of_logged_in_users())

def test_write_post():
   app.write_post(text="text")
   assert("text" in app.list_posts())

def suite_new_user_writes_text
   return new suite()
           .add(test(test_register_user))
           .add(test(test_login,username))
           .add(test(test_write_post, username))

def suite_new_user_writes_text_and_logs_out
   return new suite()
           .add(suite(suite_new_user_writes_text))
           .add(test(logout,username))

def suite_new_user_writes_text_and_gets_banned_for_writing_profanities
   return new suite()
           .add(suite(suite_new_user_writes_text))
           .add(test(test_ban_user,username))

def run_tests
    suites = meta_search_for_functions_starting_with("suite_")
    foreach suite in suites test_and_log(suite)