Bagaimana menyusun tes di mana satu tes adalah pengaturan tes lain?

18

Saya integrasi sedang menguji suatu sistem, dengan hanya menggunakan API publik. Saya memiliki tes yang terlihat seperti ini:

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

Pada dasarnya, saya mencoba menguji seluruh "aliran" dari satu transaksi. Setiap langkah dalam aliran tergantung pada langkah sebelumnya yang berhasil. Karena saya membatasi diri ke API eksternal, saya tidak bisa begitu saja memasukkan nilai ke dalam basis data.

Jadi, entah saya punya satu metode pengujian yang sangat panjang yang tidak `A; menegaskan; B; menegaskan; C; menegaskan ... ", atau saya memecahnya menjadi metode pengujian terpisah, di mana setiap metode pengujian membutuhkan hasil pengujian sebelumnya sebelum dapat melakukan hal tersebut:

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.

Saya pikir ini baunya. Apakah ada cara yang lebih baik untuk menulis tes ini?

Roger Lipscombe
sumber

Jawaban:

10

Jika tes ini dimaksudkan untuk sering dijalankan , kekhawatiran Anda akan lebih difokuskan pada cara menyajikan hasil tes dengan cara yang nyaman bagi mereka yang diharapkan bekerja dengan hasil ini.

Dari perspektif ini, testAllTheThingsmemunculkan bendera merah besar. Bayangkan seseorang menjalankan tes ini setiap jam atau bahkan lebih sering (melawan basis kode buggy tentu saja, jika tidak maka tidak ada gunanya untuk menjalankan kembali), dan melihat setiap waktu semua sama FAIL, tanpa indikasi yang jelas tentang tahap apa yang gagal.

Metode terpisah terlihat jauh lebih menarik, karena hasil re-running (dengan asumsi kemajuan yang stabil dalam memperbaiki bug dalam kode) dapat terlihat seperti:

    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

Side catatan, di salah satu proyek masa lalu saya, ada begitu banyak kembali berjalan dari tergantung tes yang pengguna bahkan mulai mengeluh tentang tidak bersedia untuk melihat kegagalan diharapkan diulang pada tahap berikutnya "dipicu" oleh kegagalan di satu sebelumnya. Mereka mengatakan sampah ini mempersulit mereka untuk menganalisis hasil tes "kami sudah tahu bahwa sisanya akan gagal oleh desain tes, jangan repot-repot kami mengulangi" .

Akibatnya, pengembang pengujian akhirnya terpaksa memperpanjang kerangka kerja mereka dengan SKIPstatus tambahan dan menambahkan fitur dalam kode manajer pengujian untuk membatalkan pelaksanaan tes dependen dan opsi untuk menjatuhkan SKIPhasil tes ped dari laporan, sehingga terlihat seperti:

    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
agas
sumber
1
ketika saya membacanya, sepertinya akan lebih baik untuk benar-benar menulis testAllTheThings, tetapi dengan pelaporan yang jelas tentang di mana ia gagal.
Javier
2
@Laporan yang lebih jelas tentang di mana kegagalan itu kedengarannya bagus dalam teori, tetapi dalam praktik saya, setiap kali tes sering dilakukan, mereka yang bekerja dengan ini lebih suka melihat token
nyamuk
7

Saya akan memisahkan kode pengujian dari kode pengaturan. Mungkin:

# 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

Ingat, semua informasi acak yang dihasilkan harus dimasukkan dalam pernyataan jika gagal, jika tidak pengujian Anda mungkin tidak dapat direproduksi. Saya bahkan mungkin merekam benih acak yang digunakan. Setiap kali kasus acak gagal, tambahkan input spesifik sebagai tes hard-code untuk mencegah regresi.

infogulch
sumber
1
+1 untuk Anda! Tes adalah kode, dan KERING berlaku sebanyak dalam pengujian seperti halnya dalam produksi.
DougM
2

Tidak jauh lebih baik, tetapi Anda setidaknya dapat memisahkan kode pengaturan dari kode menegaskan. Tulis metode terpisah yang menceritakan keseluruhan cerita selangkah demi selangkah, dan ambil parameter yang mengontrol berapa langkah yang harus diambil. Kemudian setiap tes dapat mengatakan sesuatu seperti simulate 4atau simulate 10dan kemudian menegaskan apa pun yang diuji.

Kilian Foth
sumber
1

Yah, saya mungkin tidak mendapatkan sintaks Python di sini dengan "air coding", tapi saya kira Anda mendapatkan ide: Anda dapat mengimplementasikan fungsi umum seperti ini:

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

yang akan memungkinkan Anda untuk menulis tes Anda seperti ini:

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

Tentu saja, masih bisa diperdebatkan jika hilangnya keterbacaan dari pendekatan ini layak untuk digunakan, tetapi itu mengurangi kode boilerplate sedikit.

Doc Brown
sumber