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

Nếu bác xài RDBM thì cho cái test vào transaction ở setUp nhưng đừng commit transaction đó. Còn xài NoSQL thì seed db rồi delete cả db cho khoẻ. Cái Seed cũng quan trọng, bác đầu tư phần này đáng lắm.

Edit: kinh nghiệm là không nên re-use db cho nhiều test cases. Tôi đoán bác đang xài No-SQL nên gặp vấn đề này, 1 db cùng lắm nên xài cho 1 test suite, đừng nên xài chung quá nhiều. Có 2 lý do:

  • DB là lưu state, trong khi test nên là stateless, nếu lưu db mà không xoá sẽ có nhiều side-effect và maybe là flaky tests.
  • Tách db để run test multi-thread -> deploy nhanh hơn.

Tôi nghĩ #39 nói rất đúng. Thật sự tôi không rõ định nghĩa Unit Test của mỗi người là như thế nào để chúng ta có thể thảo luận được một cách tử tế về chủ đề này

(1) Nếu Unit Test nhắm tới các khối code nhỏ nhất như #1 nói đến hoặc như nhiều người "preach" thì đúng... nó sẽ rất "dễ" ... dễ đến mức Unit Testing không còn ý nghĩa gì nữa ?
Để có được mức độ Isolation như vậy thì Unit Test này phải test các khối code rất nhỏ, các phép toán như createSaltedPassword() hay CustomGraphGetNodeByPropertyValue()

(2) Tôi tin phần lớn các test chúng ta viết rơi vào trường hơp "Integration Test / Integration Unit Test" khi đã có đủ các thể loại ban bệ (dependency) trong source code của bạn. Giả sử bạn có 1 hàm như thế này - từ Jellyfin

private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)

Nhìn thì có vẻ rất đơn giản - chỉ có 2 parameter. Tuy nhiên mỗi object được gửi vào có một tấn property và bản thân hàm này cũng cho ra 1 tấn các kết quả khác nhau. Vậy chúng ta làm gì với nó?

  • Mock tất cả mọi thứ và test tất cả mọi thứ? ... well nhắc đến mock thì nó lại là một ổ sâu khác
  • Không test nó?
  • "Hàm trên chưa được viết tốt, khi chúng ta đã phân chia công việc một cách tử tế / đủ nhỏ để unit test thì sẽ không bao giờ xảy ra trường hợp như thế này, bản thân các hàm khác nhỏ hơn được hàm này gọi đã được test thì chúng ta sẽ không bao giờ gặp trường hợp như thế này" - Tôi nghĩ lập luận này là cứt bò. Nếu đập tất cả mọi thứ nhỏ đến mức có thể làm một cái "unit test" hoàn toàn độc lập, nhỏ đến mức trivial như ở điểm (1) thì cái chúng ta có sẽ là x10 số lượng hàm ( Để giảm độ phức tạp từng hàm?) , x10 số lượng class (Để giảm số property trên mỗi class?) , x10 độ sâu call stack ( Hậu quả của việc xẻ nhỏ hàm) và x100 LOC mà không mang lại giá trị gì thật sự cả

Nhận định cá nhân: Nếu một developer thực sự nghĩ trước khi implement một thứ gì trong cùng một lượng thời gian với một developer dùng thời gian đó để nghĩ và viết unit test thì tôi tin tưởng nhiều hơn vào chất lượng của người nghĩ và không viết unit test. Thứ mà unit test mang lại là nó ép developer phải nghĩ. Chất lượng code tốt hơn vì developer đó đã nghĩ kỹ trước khi implement, không phải vì bản thân việc viết Unit Test
Điểm tốt khác là sớm phát hiện breaking changes. Cái này thực sự là một điểm + lớn, không phải bàn cãi
 
Tôi nghĩ #39 nói rất đúng. Thật sự tôi không rõ định nghĩa Unit Test của mỗi người là như thế nào để chúng ta có thể thảo luận được một cách tử tế về chủ đề này

(1) Nếu Unit Test nhắm tới các khối code nhỏ nhất như #1 nói đến hoặc như nhiều người "preach" thì đúng... nó sẽ rất "dễ" ... dễ đến mức Unit Testing không còn ý nghĩa gì nữa ?
Để có được mức độ Isolation như vậy thì Unit Test này phải test các khối code rất nhỏ, các phép toán như createSaltedPassword() hay CustomGraphGetNodeByPropertyValue()

(2) Tôi tin phần lớn các test chúng ta viết rơi vào trường hơp "Integration Test / Integration Unit Test" khi đã có đủ các thể loại ban bệ (dependency) trong source code của bạn. Giả sử bạn có 1 hàm như thế này - từ Jellyfin

private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)

Nhìn thì có vẻ rất đơn giản - chỉ có 2 parameter. Tuy nhiên mỗi object được gửi vào có một tấn property và bản thân hàm này cũng cho ra 1 tấn các kết quả khác nhau. Vậy chúng ta làm gì với nó?

  • Mock tất cả mọi thứ và test tất cả mọi thứ? ... well nhắc đến mock thì nó lại là một ổ sâu khác
  • Không test nó?
  • "Hàm trên chưa được viết tốt, khi chúng ta đã phân chia công việc một cách tử tế / đủ nhỏ để unit test thì sẽ không bao giờ xảy ra trường hợp như thế này, bản thân các hàm khác nhỏ hơn được hàm này gọi đã được test thì chúng ta sẽ không bao giờ gặp trường hợp như thế này" - Tôi nghĩ lập luận này là cứt bò. Nếu đập tất cả mọi thứ nhỏ đến mức có thể làm một cái "unit test" hoàn toàn độc lập, nhỏ đến mức trivial như ở điểm (1) thì cái chúng ta có sẽ là x10 số lượng hàm ( Để giảm độ phức tạp từng hàm?) , x10 số lượng class (Để giảm số property trên mỗi class?) , x10 độ sâu call stack ( Hậu quả của việc xẻ nhỏ hàm) và x100 LOC mà không mang lại giá trị gì thật sự cả

Nhận định cá nhân: Nếu một developer thực sự nghĩ trước khi implement một thứ gì trong cùng một lượng thời gian với một developer dùng thời gian đó để nghĩ và viết unit test thì tôi tin tưởng nhiều hơn vào chất lượng của người nghĩ và không viết unit test. Thứ mà unit test mang lại là nó ép developer phải nghĩ. Chất lượng code tốt hơn vì developer đó đã nghĩ kỹ trước khi implement, không phải vì bản thân việc viết Unit Test
Điểm tốt khác là sớm phát hiện breaking changes. Cái này thực sự là một điểm + lớn, không phải bàn cãi
Bỏ qua BDD.

Theo tôi Integration test không nhất thiết phải dính đến DB mới gọi là Integration. Unit test là test một unit nhỏ của code, có thể là 1 hàm, 1 class (hay gọi là 1 module/unit). Integration test là test cover mối liên hệ giữa các module với nhau, thông thường hay test xuyên unit và tới DB, nhưng quan điểm cá nhân là test giữa các Units với nhau.

Tôi xem qua ví dụ của bác, đầu tiên tôi nhận xét là class đó violate nhiều rules, ví dụ:

  • Single Responsibility
  • Đặt tên Builder là mis-leading, theo tôi nên là Factory.

Như có nói ở 1 comment khác là ngoài Unit Testing còn có nhiều quy trình đảm bảo chất lượng code như Code Review, và mục đích bài này chỉ muốn bàn về lý do + thái độ với tests (không phải tutorial).

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

Để ví dụ dễ hiểu hơn, nếu là tôi viết cái StreamBuilder đó tôi sẽ apply pattern Builder (https://refactoring.guru/design-patterns/builder) đúng cách như sau:

StreamBuilder: có một loạt các property ở ctor/setter mục đích để build a stream, có 1 nhiệm vụ là tạo builder với các property đó (tất nhiên kèm theo validation). Builder chỗ này nó là raw logic, không quan tâm datasource nên có thể viết unit test bằng cách provide đủ property và expect result tương ứng với property đó. Ví dụ:

  • property A = 0 -> invalid
  • property A > 0, property B = 1 => expect có X stream với Max Bitrate là Y

StreamBuilderDirector: mục đích là fetch dữ liệu từ data source và pass to StreamBuilder có thể viết unit test như sau:

  • Expect fetch dữ liệu db từ repository A (đoạn này thậm chí chỉ cần mock)
  • Expect đọc config từ file B (đoạn này thậm chí chỉ cần mock)

Client: Chính là cái Factory như code bác provided, Client rất đơn giản là tạo director, xài director tạo builder và build thành Stream. Lúc này cần phải viết Integration Test cho Client, mục đích là đảm bảo mình gọi đúng director và khi gọi build trả về kết quả expected. Có thể mock, có thể không nhưng the point là nó test through 2 units.

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

Cheers!

Edit: StreamBuilder có thể nhận props ở ctor hoặc qua setter, tuỳ style nhưng nguyên tắc vẫn là pure data.
 
Last edited:
Nếu bác xài RDBM thì cho cái test vào transaction ở setUp nhưng đừng commit transaction đó. Còn xài NoSQL thì seed db rồi delete cả db cho khoẻ. Cái Seed cũng quan trọng, bác đầu tư phần này đáng lắm.

Edit: kinh nghiệm là không nên re-use db cho nhiều test cases. Tôi đoán bác đang xài No-SQL nên gặp vấn đề này, 1 db cùng lắm nên xài cho 1 test suite, đừng nên xài chung quá nhiều. Có 2 lý do:

  • DB là lưu state, trong khi test nên là stateless, nếu lưu db mà không xoá sẽ có nhiều side-effect và maybe là flaky tests.
  • Tách db để run test multi-thread -> deploy nhanh hơn.
Sao RDBM mình k seed db như NoSQL v ạ.
 
Đã trải nghiệm g
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.
mindset này hay quá thím ơi :p
 
testing front-end mình thấy có hai loại ( theo những gì mình thấy, hồi trước làm fullstack hơn một năm xong nhảy qua đội data-infra )

logic testing: các thuật toán hay hàm dùng để xử lý dữ liệu từ dạng này sang dạng khác, hoặc làm các công vụ không liên quan đến UI, có thể dùng jest/mocha

UI test: render các component riêng rẽ, rồi test xem nó trông thế nào dựa vào các props/state khác nhau
Có thể dùng story book: https://storybook.js.org/docs/react/workflows/testing-with-storybook

mình sai hay thiếu gì các bác chỉ dạy nhé

UI test nhanh nhất vẫn là xài mấy cái solution về visual testing nhé ví dụ Applitools. Commit xong lên review UI changes trên cross browsers, devices nếu thấy ko có issue thì UI ok thôi, time đâu check layout rồi responsive = auto đơn thuần sao chịu nổi
 
Nếu việc trả về một giá trị của hàm trong unit test cần thiết thì không nên để kiểu trả về là void đúng không mọi người?
 
tôi đang có kinh nghiệm migration 1 loạt app từ on-premise lên aws cloud. đổi db, đổi message queue. khoảng 500+ web app, còn gần 100 cái nữa là xong

ko có code test thì việc này ko thể làm được. rất may bên khách hàng là công ty mỹ, gần như đủ hết các loại test. dẫn đến việc của tôi làm rất dễ

có unit test -> test function, sql query, docker test, intergration test riêng
 
tôi đang có kinh nghiệm migration 1 loạt app từ on-premise lên aws cloud. đổi db, đổi message queue. khoảng 500+ web app, còn gần 100 cái nữa là xong

ko có code test thì việc này ko thể làm được. rất may bên khách hàng là công ty mỹ, gần như đủ hết các loại test. dẫn đến việc của tôi làm rất dễ

có unit test -> test function, sql query, docker test, intergration test riêng
Giá trị của tests thể hiện rõ nhất đoạn migrate như thế này đây :beauty:

via theNEXTvoz for iPhone
 
Tôi nghĩ #39 nói rất đúng. Thật sự tôi không rõ định nghĩa Unit Test của mỗi người là như thế nào để chúng ta có thể thảo luận được một cách tử tế về chủ đề này

(1) Nếu Unit Test nhắm tới các khối code nhỏ nhất như #1 nói đến hoặc như nhiều người "preach" thì đúng... nó sẽ rất "dễ" ... dễ đến mức Unit Testing không còn ý nghĩa gì nữa ?
Để có được mức độ Isolation như vậy thì Unit Test này phải test các khối code rất nhỏ, các phép toán như createSaltedPassword() hay CustomGraphGetNodeByPropertyValue()

(2) Tôi tin phần lớn các test chúng ta viết rơi vào trường hơp "Integration Test / Integration Unit Test" khi đã có đủ các thể loại ban bệ (dependency) trong source code của bạn. Giả sử bạn có 1 hàm như thế này - từ Jellyfin

private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)

Nhìn thì có vẻ rất đơn giản - chỉ có 2 parameter. Tuy nhiên mỗi object được gửi vào có một tấn property và bản thân hàm này cũng cho ra 1 tấn các kết quả khác nhau. Vậy chúng ta làm gì với nó?

  • Mock tất cả mọi thứ và test tất cả mọi thứ? ... well nhắc đến mock thì nó lại là một ổ sâu khác
  • Không test nó?
  • "Hàm trên chưa được viết tốt, khi chúng ta đã phân chia công việc một cách tử tế / đủ nhỏ để unit test thì sẽ không bao giờ xảy ra trường hợp như thế này, bản thân các hàm khác nhỏ hơn được hàm này gọi đã được test thì chúng ta sẽ không bao giờ gặp trường hợp như thế này" - Tôi nghĩ lập luận này là cứt bò. Nếu đập tất cả mọi thứ nhỏ đến mức có thể làm một cái "unit test" hoàn toàn độc lập, nhỏ đến mức trivial như ở điểm (1) thì cái chúng ta có sẽ là x10 số lượng hàm ( Để giảm độ phức tạp từng hàm?) , x10 số lượng class (Để giảm số property trên mỗi class?) , x10 độ sâu call stack ( Hậu quả của việc xẻ nhỏ hàm) và x100 LOC mà không mang lại giá trị gì thật sự cả

Nhận định cá nhân: Nếu một developer thực sự nghĩ trước khi implement một thứ gì trong cùng một lượng thời gian với một developer dùng thời gian đó để nghĩ và viết unit test thì tôi tin tưởng nhiều hơn vào chất lượng của người nghĩ và không viết unit test. Thứ mà unit test mang lại là nó ép developer phải nghĩ. Chất lượng code tốt hơn vì developer đó đã nghĩ kỹ trước khi implement, không phải vì bản thân việc viết Unit Test
Điểm tốt khác là sớm phát hiện breaking changes. Cái này thực sự là một điểm + lớn, không phải bàn cãi


zzzz anh đi tin vào con người thay vì code là tôi thấy ko dc rồi :)
Lời nói của anh mâu thuẫn quá.
Anh tin vào người "nghĩ và ko viết UT", nhưng lại cho rằng UT khiến dev nghĩ tốt hơn.
Có thấy mâu thuẫn trong câu nói này ko :)
Nếu UT khiến dev nghĩ tốt hơn, vậy tại sao ko là vừa "UT vừa nghĩ", mà lại là "nghĩ mà ko viết UT" :)

Tôi tin code hơn là tin con người nhé.

Chưa kể con ng nghĩ tới mấy cũng có sai sót. Ko viết UT thì lấy gì mà đo lường dc là 1 ng dev đã nghĩ kỹ hay chưa.
UT rất hay, có chăng là ko thể giành quá nhiều thời gian cho nó, thì ít nhất cũng phải có các case cơ bản, corner case, abnormal case.

Các quy trình đưa ra để giảm thiểu lỗi của con người, UT cũng thế. Tôi còn ko tin tôi viết code thì anh lấy gì mà tin dc người khác?

Đấy là còn chưa kể các tiện lợi khác của UT khi maintain, update code v.v...
 
zzzz anh đi tin vào con người thay vì code là tôi thấy ko dc rồi :)
Lời nói của anh mâu thuẫn quá.
Anh tin vào người "nghĩ và ko viết UT", nhưng lại cho rằng UT khiến dev nghĩ tốt hơn.
Có thấy mâu thuẫn trong câu nói này ko :)
Nếu UT khiến dev nghĩ tốt hơn, vậy tại sao ko là vừa "UT vừa nghĩ", mà lại là "nghĩ mà ko viết UT" :)

Tôi tin code hơn là tin con người nhé.

Chưa kể con ng nghĩ tới mấy cũng có sai sót. Ko viết UT thì lấy gì mà đo lường dc là 1 ng dev đã nghĩ kỹ hay chưa.
UT rất hay, có chăng là ko thể giành quá nhiều thời gian cho nó, thì ít nhất cũng phải có các case cơ bản, corner case, abnormal case.

Các quy trình đưa ra để giảm thiểu lỗi của con người, UT cũng thế. Tôi còn ko tin tôi viết code thì anh lấy gì mà tin dc người khác?

Đấy là còn chưa kể các tiện lợi khác của UT khi maintain, update code v.v...
Vì người nghĩ và viết UT thì sẽ tốn thời gian cho việc viết và bớt thời gian cho việc nghĩ đó anh :) Tôi chưa thấy có gì mâu thuẫn ở đây cả
Còn tôi cũng nói rõ lợi ích của UT khi có breaking change rồi... việc đấy không phải bàn cãi
 
Last edited:
cái vấn đề của UT là mất thời gian thôi, nhưng nó sẽ đảm cho chất lượng.
Còn lợi ích thì ko đơn giản là chỉ khi breaking change, lấy gì để biết code 1 ng có chất lượng hay ko? Phải test đúng ko?
Giờ a đưa code để ng khác review, ko lẽ họ phải chạy tay từng test case để kiểm tra?
Vì họ đâu thể biết dc chất lượng của anh thế nào?
Nói đơn giản thế thôi.
 
Vì người nghĩ và viết UT thì sẽ tốn thời gian cho việc viết và bớt thời gian cho việc nghĩ đó anh :) Tôi chưa thấy có gì mâu thuẫn ở đây cả
Còn tôi cũng nói rõ lợi ích của UT khi có breaking change rồi... việc đấy không phải bàn cãi
A phải biết là lỗi ở môi trường production nó thế nào, so với thời gian dev của a, cái nào tốn thời gian hơn

Đối với tôi code ko có test là code rác, tôi từ chối làm ngay từ đầu luôn.

A gặp tôi thì ko có thời gian nghĩ đâu. Task break nhỏ ra, ko bao giờ tôi để a làm cái task nào quá 1 tuần. Ko dc thì làm cái khác.

có ngoại lệ, nhưng task đó phải trên 3 tháng, và thời gian lại là vô biên
 
Tôi nghĩ #39 nói rất đúng. Thật sự tôi không rõ định nghĩa Unit Test của mỗi người là như thế nào để chúng ta có thể thảo luận được một cách tử tế về chủ đề này

(1) Nếu Unit Test nhắm tới các khối code nhỏ nhất như #1 nói đến hoặc như nhiều người "preach" thì đúng... nó sẽ rất "dễ" ... dễ đến mức Unit Testing không còn ý nghĩa gì nữa ?
Để có được mức độ Isolation như vậy thì Unit Test này phải test các khối code rất nhỏ, các phép toán như createSaltedPassword() hay CustomGraphGetNodeByPropertyValue()

(2) Tôi tin phần lớn các test chúng ta viết rơi vào trường hơp "Integration Test / Integration Unit Test" khi đã có đủ các thể loại ban bệ (dependency) trong source code của bạn. Giả sử bạn có 1 hàm như thế này - từ Jellyfin

private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)

Nhìn thì có vẻ rất đơn giản - chỉ có 2 parameter. Tuy nhiên mỗi object được gửi vào có một tấn property và bản thân hàm này cũng cho ra 1 tấn các kết quả khác nhau. Vậy chúng ta làm gì với nó?

  • Mock tất cả mọi thứ và test tất cả mọi thứ? ... well nhắc đến mock thì nó lại là một ổ sâu khác
  • Không test nó?
  • "Hàm trên chưa được viết tốt, khi chúng ta đã phân chia công việc một cách tử tế / đủ nhỏ để unit test thì sẽ không bao giờ xảy ra trường hợp như thế này, bản thân các hàm khác nhỏ hơn được hàm này gọi đã được test thì chúng ta sẽ không bao giờ gặp trường hợp như thế này" - Tôi nghĩ lập luận này là cứt bò. Nếu đập tất cả mọi thứ nhỏ đến mức có thể làm một cái "unit test" hoàn toàn độc lập, nhỏ đến mức trivial như ở điểm (1) thì cái chúng ta có sẽ là x10 số lượng hàm ( Để giảm độ phức tạp từng hàm?) , x10 số lượng class (Để giảm số property trên mỗi class?) , x10 độ sâu call stack ( Hậu quả của việc xẻ nhỏ hàm) và x100 LOC mà không mang lại giá trị gì thật sự cả

Nhận định cá nhân: Nếu một developer thực sự nghĩ trước khi implement một thứ gì trong cùng một lượng thời gian với một developer dùng thời gian đó để nghĩ và viết unit test thì tôi tin tưởng nhiều hơn vào chất lượng của người nghĩ và không viết unit test. Thứ mà unit test mang lại là nó ép developer phải nghĩ. Chất lượng code tốt hơn vì developer đó đã nghĩ kỹ trước khi implement, không phải vì bản thân việc viết Unit Test
Điểm tốt khác là sớm phát hiện breaking changes. Cái này thực sự là một điểm + lớn, không phải bàn cãi
Bác cố thể bổ sung thêm phần này được ko ?
 
cái vấn đề của UT là mất thời gian thôi, nhưng nó sẽ đảm cho chất lượng.
Còn lợi ích thì ko đơn giản là chỉ khi breaking change, lấy gì để biết code 1 ng có chất lượng hay ko? Phải test đúng ko?
Giờ a đưa code để ng khác review, ko lẽ họ phải chạy tay từng test case để kiểm tra?
Vì họ đâu thể biết dc chất lượng của anh thế nào?
Nói đơn giản thế thôi.

Đúng. Và không phải lúc nào chúng ta cũng có thời gian phải không nào. Nếu dev sẵn sang bỏ ra một vài tiếng để nghĩ, thêm một vài tiếng cho viết UT và thêm một vài phút cho implementation thì chắc chắn mọi thứ sẽ rất tốt đẹp rồi. Tôi rất ủng hộ UT nếu nó phù hợp với tính chất dự án và tổng lợi ích nó mang lại lớn hơn tổng công sức bỏ ra. Chuyện không phải lúc nào cũng thế
Tôi không nghĩ để UT "drive" development luôn là một ý hay

A phải biết là lỗi ở môi trường production nó thế nào, so với thời gian dev của a, cái nào tốn thời gian hơn

Đối với tôi code ko có test là code rác, tôi từ chối làm ngay từ đầu luôn.

A gặp tôi thì ko có thời gian nghĩ đâu. Task break nhỏ ra, ko bao giờ tôi để a làm cái task nào quá 1 tuần. Ko dc thì làm cái khác.

có ngoại lệ, nhưng task đó phải trên 3 tháng, và thời gian lại là vô biên

Nếu anh có suy nghĩ như thế thì khá tiếc khi nhiều dự án thú vị không có cơ hội nhận được sự đóng góp của anh rồi...

Nhiều các repo public có cộng đồng lớn, đang active development như Jellyfin tôi có nhắc đến ở trên hay webtorrent họ không hề làm TDD, lượng test cũng cực kỳ hạn chế. Điều đó không cản trở họ làm ra một dự án tốt

https://github.com/webtorrent/webtorrent/commits/master/test

All-in-all như tôi đã nói trong bài viết đầu tiên, tôi không advocate việc "Đừng TDD, đừng viết UT, nó quá vô bổ và lãng phí"
Cái tôi muốn nói là
"TDD không phải là chìa khóa vạn năng cho một project tốt, ngược lại, nó sẽ kéo project của bạn đi xuống nếu không nhận thức được rõ những vấn đề mà nó mang lại"
 
Đúng. Và không phải lúc nào chúng ta cũng có thời gian phải không nào. Nếu dev sẵn sang bỏ ra một vài tiếng để nghĩ, thêm một vài tiếng cho viết UT và thêm một vài phút cho implementation thì chắc chắn mọi thứ sẽ rất tốt đẹp rồi. Tôi rất ủng hộ UT nếu nó phù hợp với tính chất dự án và tổng lợi ích nó mang lại lớn hơn tổng công sức bỏ ra. Chuyện không phải lúc nào cũng thế
Tôi không nghĩ để UT "drive" development luôn là một ý hay



Nếu anh có suy nghĩ như thế thì khá tiếc khi nhiều dự án thú vị không có cơ hội nhận được sự đóng góp của anh rồi...

Nhiều các repo public có cộng đồng lớn, đang active development như Jellyfin tôi có nhắc đến ở trên hay webtorrent họ không hề làm TDD, lượng test cũng cực kỳ hạn chế. Điều đó không cản trở họ làm ra một dự án tốt

https://github.com/webtorrent/webtorrent/commits/master/test

All-in-all như tôi đã nói trong bài viết đầu tiên, tôi không advocate việc "Đừng TDD, đừng viết UT, nó quá vô bổ và lãng phí"
Cái tôi muốn nói là
"TDD không phải là chìa khóa vạn năng cho một project tốt, ngược lại, nó sẽ kéo project của bạn đi xuống nếu không nhận thức được rõ những vấn đề mà nó mang lại"

Tôi hỏi thật, anh làm việc kiểu "ko có thời gian viết UT" chiếm bao nhiêu % trong các dự án của anh (trừ trường hợp anh ko phải là 1 startup nào đó và trong giai đoạn cần chạy đua để release).

Nếu anh bảo > 30% thì tôi nghĩ anh nên cân nhắc 1 vài thứ về việc quản lý thời gian của leader/PM dự án, hoặc của anh!
Mà kể cả qua giai đoạn gấp rút đó thì sau này cũng phải viết UT để bù vào thôi :go:

Tôi đồng ý quan điểm trên kia, code ko có UT thì quality thấp, ko gì đảm bảo dc chất lượng release.
Chưa kể hôm nay tiết kiêm mấy tiếng viết UT, ngày sau lại tốn thêm thời gian để fix bug, maintain thôi.

Bổ sung thêm 1 cái: Việc anh dẫn chứng cái repo kia là ko hợp lý, tôi có thể quăng ra 10 cái repo có UT đầy đủ cho anh coi, anh tin ko? Thế muốn theo repo nào :go:

Đúng là UT ko phải vạn năng, và chất lượng của 1 project nó chịu ảnh hưởng của rất nhiều yếu tố.
Chính vì thế nên mới cần UT để đảm bảo chất lượng 1 phần nào đó.
 
Last edited:
Unit Test hay test code nói chung là tốt cho dài hạn chứ ngắn hạn thì không. Cho nên các thím tranh luận thì xem lại có đúng góc nhìn chưa nhé ?
 
Tôi hỏi thật, anh làm việc kiểu "ko có thời gian viết UT" chiếm bao nhiêu % trong các dự án của anh (trong trường hợp anh ko phải là 1 startup nào đó và trong giai đoạn cần chạy đua để release)
Nếu anh bảo > 30% thì tôi nghĩ anh nên cân nhắc 1 vài thứ về việc quản lý thời gian của leader/PM dự án, hoặc của anh!

Cũng có thể :) tính chất công việc mỗi người khác nhau mà. Vì thế tôi mới có đóng góp hơi trái chiều ở đây, từ góc nhìn của tôi
Có thể các bạn ở đây trình độ cao hơn hoặc mặt bằng chung team trình độ tốt hơn nên không gặp phải những khó khăn mà tôi gặp phải... đó cũng là chuyện bình thường thôi :)
 
Thực ra khi làm Dev lâu năm thì khi viết hàm hay lớp "tự nhiên" nó sẽ sạch. Khi nó ko sạch bạn sẽ có cảm giác cái gì đó không đúng.

Mà khi hàm lớp đã viết tốt thì test cũng viết nhanh thôi.

Khổ nhất chậm nhất quản lý khó nhất chính là test data mock up. Chán chả buồn nói.

Tui ko làm TDD mà là Bug DD. Mấy cái Bug Blocker là phải có test. Sau đó là Bug nghiêm trọng, khá nghiêm trọng, ít nghiêm trọng.

Có thời gian tới đâu thì làm tới đó. Ko xì stress.
 
Back
Top