thảo luận Chia sẻ về Unit Testing và TDD

potholer54-fanboy

Senior Member
Ghi chú: Bài này không phải là tutorial, chỉ kể về con đường tôi đến với TDD và muốn bàn về lý do + thái độ với tests nói chung.

A) Mở màn code như bao dev khác

Từ địa ngục deploy và OT
(có thể đã outdate với tình hình hiện tại)

Cách đây 7 năm khi mình còn làm việc ở VN (dù là công ty nước ngoài nhưng a e toàn người Việt và làm sản phẩm) thì mình có tình trạng như thế này, cứ mỗi lần release là toàn bộ team từ backend, frontend, mobile đều phải deploy lên server Stage và chờ QA test. QA phải test toàn bộ luồng từ đăng nhập tới tính năng, nếu có lỗi nhẹ thì dev phải fix, lỗi nặng thì delay hẳn luôn release. Hậu quả là:

  • Anh em OT mệt mỏi.
  • Đôi lúc không thể fix và missed deadline -> bị trừ KPI.
  • Nhiều khi QA sót bug -> chất lượng SP không được ok lắm.

Cho tới thiên đường không phải OT ngày nào

Cũng hên nhờ mình nhảy việc và công ty mới apply cái process này nên tình trạng kia hoàn toàn không xảy ra. Yêu cầu công ty mới là luôn luôn có tests kèm theo khi mà mình implement bất cứ thứ gì (vài exceptions có thể nói ở thớt khác). Trung bình 1 dòng code mình phải viết 5 dòng test.

B) Ơ, nhưng mà mình là dev tại sao mình phải viết test

Lúc đầu mình cũng bị như thế này nhưng mà CTO cũng đả thông tư tưởng cho mình như sau:

1. Mày code cái gì thì phải có tests thì mới biết đúng hay sai
2. Công ty sẽ tiết kiệm được nhiều tiền, vì mỗi lần mày test manual mà chục lần deploy như vậy thì còn mắc hơn cả automation tests
3. Khi làm việc dự án to bự thì không ai nhớ hết user cases, lỡ mày sửa code mà test failed thì đi đọc user case mà fix -> miễn sao user case cũ passed và feature mới của mày covered là okay.
4. Muốn apply continuous delivery phải có auto-tests
5. Mày không viết test thì phắng, ta thuê đứa khác :D

C) Ok, vậy viết test như thế nào mới hiệu quả

Thực ra test tốt là test cover tất cả các cases, edges. Nhưng trải qua quá trình Code Review thì vẫn còn sai sót do lỗi con người. Nên mình cứ cố gắng cover được tối đa càng nhiều cases càng tốt.

Nếu có 1 bug xảy ra cũng đừng thấy mình tệ hay sao cả, bình tĩnh fix bug đó. Có 2 cách fix như sau

1. Nếu nó dễ và reproducible thì: viết test để reproduce và fix bug đó. Test này sẽ giúp đảm bảo là nó sẽ không bao giờ xảy ra lại (95% bug có thể áp dụng cách này)
2. Nếu khó hoặc gấp thì xài hết mưu hèn kế bẩn để fix bug, deploy, sau đó viết test cover đoạn đó (5% thôi fen)

D) Đã trải nghiệm giờ sao luyện thành tuyệt chiêu? Cái này khuyên các bạn chưa trải qua B,C thì đừng thử

Sau khi làm quen style mới thì công ty thuê 1 ông chuyên gia TDD về dạy (cái này mình nói sơ sơ, mấy bác đọc thêm nha). Đại loại TDD là viết test trước, viết code sau. Hay còn có tên gọi khác là hãy lười nhất có thể.

Ví dụ bài toán đơn giản là giải phương trình bậc 1: ax + b = c ta có hàm như sau:

Code:
fun ptb1(a, b, c): number {
...
}

Bước 1: (hãy nhớ lười nhất có thể) Viết test cho case a = 0

Code:
fun test_ptb1_case_a_zero() {
    ptb1(0, 1, 2) expect exception
}

Sau đó quay lại hàm chính, viết đơn giản:

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.
}

Bước 2: (hãy nhớ lười nhất có thể) Viết test cho case a > 0, b = 0, c = 0

Code:
fun test_ptb1_case_a_not_zero_b_zero() {
    ptb1(1, 0, 0) expect x = 0
}

Sau đó quay lại hàm chính, viết đơn giản:

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.
    if (b==0 && c==0) return 0
}

Bước 3: (hãy nhớ lười nhất có thể) Viết test cho case a > 0, b > 0, c = 0

Code:
fun test_ptb1_case_a_not_zero_b_not_zero_c_zero() {
    ptb1(2, 1, 0) expect x = -1/2
}

Sau đó quay lại hàm chính, viết đơn giản:

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.
    if (b==0 && c==0) return 0
    return (c - b) / a
}

Bước 4: (hãy nhớ lười nhất có thể) Viết test cho case a > 0, b > 0, c > 0

Code:
fun test_ptb1_case_a_not_zero_b_not_zero_c_not_zero() {
    ptb1(2, 1, 3) expect x = 1
}

Sau đó quay lại hàm chính, viết đơn giản và refactor (thực tế chỉ cần xoá ở step 2 là ok)

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.

    return (c - b) / a
}

Cứ thế lặp đi lặp lại cho tới khi bạn có đủ tất cả các cases.

Kết: khẩu quyết của TDD là


  • test trước, implement sau.
  • lười nhất có thể nhưng k được lười nghĩ về case.
  • refactor giữa các bước thoải mái, miễn là các cases đã viết passed hết.
  • mỗi bước chỉ nên cover 1 case và làm càng nhanh càng tốt (lười nhất có thể, hehe)
  • làm vừa đủ thứ họ yêu cầu, không nghĩ xa xôi.

--------------------------

Mong các bác có thể chia sẻ thêm về mindset và thực tế ở công ty các bác đang làm việc hiện như thế nào. Nếu thấy thiếu sót vui lòng chỉ giúp mình.
 
Last edited:
Bài này cũng tương tự mấy cái bài "how to write unit tests" khi google, mang tính chất đọc cho vui :shame: Newbie mà đọc thì càng ko biết phải làm gì.
Muốn chia sẻ Unit Testings hay TDD tốt nhất phải có 1 cái real-world application, mà cái này thì phải join cty có viết tests mới học dc :doubt:
 
Bài này cũng tương tự mấy cái bài "how to write unit tests" khi google, mang tính chất đọc cho vui :shame: Newbie mà đọc thì càng ko biết phải làm gì.
Muốn chia sẻ Unit Testings hay TDD tốt nhất phải có 1 cái real-world application, mà cái này thì phải join cty có viết tests mới học dc :doubt:

Đúng là phải có real-world app mới học nhanh được.

Bài này mình viết chủ yếu muốn bàn về lý do và thái độ với tests, còn tutorial thì google nhiều mà.

via theNEXTvoz for iPhone
 
A) Mở màn code như bao dev khác

Từ địa ngục deploy và OT
(có thể đã outdate với tình hình hiện tại)

Cách đây 7 năm khi mình còn làm việc ở VN (dù là công ty nước ngoài nhưng a e toàn người Việt và làm sản phẩm) thì mình có tình trạng như thế này, cứ mỗi lần release là toàn bộ team từ backend, frontend, mobile đều phải deploy lên server Stage và chờ QA test. QA phải test toàn bộ luồng từ đăng nhập tới tính năng, nếu có lỗi nhẹ thì dev phải fix, lỗi nặng thì delay hẳn luôn release. Hậu quả là:

  • Anh em OT mệt mỏi.
  • Đôi lúc không thể fix và missed deadline -> bị trừ KPI.
  • Nhiều khi QA sót bug -> chất lượng SP không được ok lắm.

Cho tới thiên đường không phải OT ngày nào

Cũng hên nhờ mình nhảy việc và công ty mới apply cái process này nên tình trạng kia hoàn toàn không xảy ra. Yêu cầu công ty mới là luôn luôn có tests kèm theo khi mà mình implement bất cứ thứ gì (vài exceptions có thể nói ở thớt khác). Trung bình 1 dòng code mình phải viết 5 dòng test.

B) Ơ, nhưng mà mình là dev tại sao mình phải viết test

Lúc đầu mình cũng bị như thế này nhưng mà CTO cũng đả thông tư tưởng cho mình như sau:

1. Mày code cái gì thì phải có tests thì mới biết đúng hay sai
2. Công ty sẽ tiết kiệm được nhiều tiền, vì mỗi lần mày test manual mà chục lần deploy như vậy thì còn mắc hơn cả automation tests
3. Khi làm việc dự án to bự thì không ai nhớ hết user cases, lỡ mày sửa code mà test failed thì đi đọc user case mà fix -> miễn sao user case cũ passed và feature mới của mày covered là okay.
4. Muốn apply continuous delivery phải có auto-tests
5. Mày không viết test thì phắng, ta thuê đứa khác :D

C) Ok, vậy viết test như thế nào mới hiệu quả

Thực ra test tốt là test cover tất cả các cases, edges. Nhưng trải qua quá trình Code Review thì vẫn còn sai sót do lỗi con người. Nên mình cứ cố gắng cover được tối đa càng nhiều cases càng tốt.

Nếu có 1 bug xảy ra cũng đừng thấy mình tệ hay sao cả, bình tĩnh fix bug đó. Có 2 cách fix như sau

1. Nếu nó dễ và reproducible thì: viết test để reproduce và fix bug đó. Test này sẽ giúp đảm bảo là nó sẽ không bao giờ xảy ra lại (95% bug có thể áp dụng cách này)
2. Nếu khó hoặc gấp thì xài hết mưu hèn kế bẩn để fix bug, deploy, sau đó viết test cover đoạn đó (5% thôi fen)

D) Đã trải nghiệm giờ sao luyện thành tuyệt chiêu? Cái này khuyên các bạn chưa trải qua B,C thì đừng thử

Sau khi làm quen style mới thì công ty thuê 1 ông chuyên gia TDD về dạy (cái này mình nói sơ sơ, mấy bác đọc thêm nha). Đại loại TDD là viết test trước, viết code sau. Hay còn có tên gọi khác là hãy lười nhất có thể.

Ví dụ bài toán đơn giản là giải phương trình bậc 1: ax + b = c ta có hàm như sau:

Code:
fun ptb1(a, b, c): number {
...
}

Bước 1: (hãy nhớ lười nhất có thể) Viết test cho case a = 0

Code:
fun test_ptb1_case_a_zero() {
    ptb1(0, 1, 2) expect exception
}

Sau đó quay lại hàm chính, viết đơn giản:

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.
}

Bước 2: (hãy nhớ lười nhất có thể) Viết test cho case a > 0, b = 0, c = 0

Code:
fun test_ptb1_case_a_not_zero_b_zero() {
    ptb1(1, 0, 0) expect x = 0
}

Sau đó quay lại hàm chính, viết đơn giản:

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.
    if (b==0 && c==0) return 0
}

Bước 3: (hãy nhớ lười nhất có thể) Viết test cho case a > 0, b > 0, c = 0

Code:
fun test_ptb1_case_a_not_zero_b_not_zero_c_zero() {
    ptb1(2, 1, 0) expect x = -1/2
}

Sau đó quay lại hàm chính, viết đơn giản:

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.
    if (b==0 && c==0) return 0
    return (c - b) / a
}

Bước 4: (hãy nhớ lười nhất có thể) Viết test cho case a > 0, b > 0, c > 0

Code:
fun test_ptb1_case_a_not_zero_b_not_zero_c_not_zero() {
    ptb1(2, 1, 3) expect x = 1 
}

Sau đó quay lại hàm chính, viết đơn giản và refactor (thực tế chỉ cần xoá ở step 2 là ok)

Code:
fun ptb1(a, b, c): number {
    if (a==0) throw exception.

    return (c - b) / a
}

Cứ thế lặp đi lặp lại cho tới khi bạn có đủ tất cả các cases.

Kết: khẩu quyết của TDD là


  • test trước, implement sau.
  • lười nhất có thể nhưng k được lười nghĩ về case.
  • refactor giữa các bước thoải mái, miễn là các cases đã viết passed hết.
  • mỗi bước chỉ nên cover 1 case và làm càng nhanh càng tốt (lười nhất có thể, hehe)
  • làm vừa đủ thứ họ yêu cầu, không nghĩ xa xôi.

--------------------------

Mong các bác có thể chia sẻ thêm về mindset và thực tế ở công ty các bác đang làm việc hiện như thế nào. Nếu thấy thiếu sót vui lòng chỉ giúp mình.

chắc mình làm toàn cty cùi, k viết test. Đang định viết cái sample, có unit test đầy đủ đi pv có ổn k bác :)

Sent from Samsung SM-G960F using vozFApp
 
Bài này cũng tương tự mấy cái bài "how to write unit tests" khi google, mang tính chất đọc cho vui :shame: Newbie mà đọc thì càng ko biết phải làm gì.
Muốn chia sẻ Unit Testings hay TDD tốt nhất phải có 1 cái real-world application, mà cái này thì phải join cty có viết tests mới học dc :doubt:

Đang định cmt y chang vầy thì thấy này nên ưng luôn
Mấy cái test này nó như lúc mới đi tìm việc vậy (muốn có việc phải kn, muốn có kn phải làm việc): phải làm thực tế mới hiểu nhưng để được làm thực tế thì phải có kinh nghiệm về nó (hoặc được ai đó chỉ dẫn), ko thì loay hoay rất mất tg :shame:

Gửi từ Samsung SM-M515F bằng vozFApp
 
chắc mình làm toàn cty cùi, k viết test. Đang định viết cái sample, có unit test đầy đủ đi pv có ổn k bác :)

Sent from Samsung SM-G960F using vozFApp
Như 2 comments của 2 bạn kia nói cũng đúng, nếu không có môi trường + người chỉ thì khó làm cái này lắm. Mình gợi ý cách học như sau ha

  • Về công việc trên cty: tập viết thêm test vào, dù nó không required, thằng nào cười kệ nó, chỉ cần hiểu vì sao mình phải viết test là ok.
  • Tạo 1 pet project trên github, và thử build 1 CI có code quality, code coverage report (ví dụ: coveralls.io, travis-ci, codeclimate). Rồi cứ tập code cho tới khi coverage được 100%, quality được A.
 
Em thấy cái việc khó nhất là ngồi nghĩ ra cases để test. Mún nghĩ ra dc như vại thì chắc chắn phải có kinh nghiệm trải qua các case đó thì mới nghĩ ra dc.

Sent from Vsmart Active 3 using vozFApp
 
TDD làm bài bản, mình thấy tính riêng LOC phải gấp 4-5 lần phần code dc test.
Đúng nha, mình có nói trong bài viết 1 line code 5 lines test

Về chi phí thì tuỳ dự án, mình chỉ đứng dưới góc độ kỹ thuật. Làm TDD chi phí cao là bình thường, bù lại chất lượng dự án sẽ tốt hơn rất nhiều, phù hợp cho team sản phẩm hơn là outsource.
 
Vấn đề lớn nhất của tdd là chi phí. Bạn mới nhìn ở góc độ kỹ thuật chứ chưa ở vị trí quản lý dự án. Tdd nói qua thì mọi thứ đều tuyệt vời, trừ chi phí

via theNEXTvoz for iPhone

TDD chạy theo feature, khi pass đủ case là xong, trong khi mục tiêu của software là hướng tới tính abstraction. Làm TDD mà ko dành thêm thời gian để chỉnh sửa design thì không ổn. Mình nghĩ TDD phù hợp với dự án nào mà apply CICD, đỡ risk.

Sent using vozFApp
 
TDD chạy theo feature, khi pass đủ case là xong, trong khi mục tiêu của software là hướng tới tính abstraction. Làm TDD mà ko dành thêm thời gian để chỉnh sửa design thì không ổn. Mình nghĩ TDD phù hợp với dự án nào mà apply CICD, đỡ risk.

Sent using vozFApp
Mục tiêu của software cuối cùng là khách hàng (hay cuối cuối cùng là money), khách không quan tâm abstraction và sếp sale không quan tâm tới code đẹp xấu.

Về design thì có bước Code Review lo rồi, ví dụ xài pattern sai thì khi sửa lại cũng không phải sửa quá nhiều tests nếu có 1 integration test cover feature.

via theNEXTvoz for iPhone
 
Mục tiêu của software cuối cùng là khách hàng (hay cuối cuối cùng là money), khách không quan tâm abstraction và sếp sale không quan tâm tới code đẹp xấu.

Về design thì có bước Code Review lo rồi, ví dụ xài pattern sai thì khi sửa lại cũng không phải sửa quá nhiều tests nếu có 1 integration test cover feature.

via theNEXTvoz for iPhone

Tớ đang là dev nên tớ đứng ở góc nhìn của dev thôi. Product càng hướng tới abstraction thì càng tốt. TDD thì vẫn đang thành công ở nhiều dự án. Tương lai thì chưa biết sao.:D

Sent using vozFApp
 
Và thường cty nào áp dụng TDD thì team QA vứt xó, có còn mịe gì để test đâu.
Tuy nhiên trình dev cùi thì áp dụng TDD ko ổn. Không có mindset nghĩ ra nhiều case thì mất thời gian, làm ko hiệu quả nên chung quy vẫn áp dụng cách truyền thống là dev viết code QA tìm bug.
 
Tùy sản phẩm mà TDD nó mang lại giá trị nhiều ít. Ví dụ làm tầng Core thì TDD là phải có. Vì chủ yếu là về logic.

Nhưng ở tầng Application nó thì đôi khi nó không cần. Ví dụ như TDD cho Repository là ko cần.

Rồi tới tầng UI thì TDD là ác mộng nếu làm cho Web. Bên FE mà refactor CSS class là sửa lại Unit Test chết choa luôn.

Nói vậy thôi, chứ một sản phẩm có Unit Test là rất tốt. Nhưng đa phần toàn làm để khoe Test Coverage thôi chứ thật sự nhìn vào code chả có ý nghĩa gì.
 
Back
Top