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

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.

Tớ thích tư tưởng của ông này trong cuốn philosophy of software design :D
vKlwwUP.jpg


Sent 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:

chuẩn cơm mẹ nấu, rào trước đón sau gì chả khác how to, thời đại gì how to nó nhiều vlll
 
Tôi thấy các bác cũng nói nhiều về pro and cons của TDD rồi, thôi tôi không nói về nó nữa mà nói về real-world problems of TDD.
1. Trừ khi báclà out-source ngoài ra rất khó để test-first. Vì sao? Vì thời gian release cho product là có hạn, resource về con người là có hạn, vậy phải làm sao? Cách giải quyết mà tôi đang làm và áp dụng là chỉ viết tests sau khi passed test phase với QA. Vì khi implement 1 feature thì requirement sẽ thay đổi liên tục, ngay cả khi đang test với QA. Nếu bác viết test-first thì thời gian bác viết test có khi hơn cả thời gian implement

2. Phân biệt rạch rồi giữa unit và integration test, test phải stateless, nếu bác sử dụng DB thì có thể mock với H2-db in-memory, ngoài ra về mocking thì bác có thể tìm hiểu thêm vì nó khá rộng.

3. Phải đặt ra target coverage cho test, 60%-70% tùy dự án, nguyên tắc là mọi thứ phải đo lường được, cái này có thể tránh được cho bác viết ra những dòng code unreachable. Đến khi có code changes làm nó chạy thì bác chỉ có khóc thét. Phần này nó sẽ thể hiện độ stable của system, vì tôi biết product thì có khi phần Review code rất sơ xài vì bị deadline dí.

4. Tests chính là requirements, đúng vậy, đó là requirements mà bác áp vào từng feature, nếu có bất kỳ big change nào xảy ra khi cần scale system, breaking changes về feature, nếu không có tests, tôi tin không 1 QA, DEV, LEAD nào có thể nhớ tất cả mọi features, đến lúc đấy bác chỉ có khấn cho nó đừng crash project mà thôi.
 
Tôi thấy các bác cũng nói nhiều về pro and cons của TDD rồi, thôi tôi không nói về nó nữa mà nói về real-world problems of TDD.
1. Trừ khi báclà out-source ngoài ra rất khó để test-first. Vì sao? Vì thời gian release cho product là có hạn, resource về con người là có hạn, vậy phải làm sao? Cách giải quyết mà tôi đang làm và áp dụng là chỉ viết tests sau khi passed test phase với QA. Vì khi implement 1 feature thì requirement sẽ thay đổi liên tục, ngay cả khi đang test với QA. Nếu bác viết test-first thì thời gian bác viết test có khi hơn cả thời gian implement

2. Phân biệt rạch rồi giữa unit và integration test, test phải stateless, nếu bác sử dụng DB thì có thể mock với H2-db in-memory, ngoài ra về mocking thì bác có thể tìm hiểu thêm vì nó khá rộng.

3. Phải đặt ra target coverage cho test, 60%-70% tùy dự án, nguyên tắc là mọi thứ phải đo lường được, cái này có thể tránh được cho bác viết ra những dòng code unreachable. Đến khi có code changes làm nó chạy thì bác chỉ có khóc thét. Phần này nó sẽ thể hiện độ stable của system, vì tôi biết product thì có khi phần Review code rất sơ xài vì bị deadline dí.

4. Tests chính là requirements, đúng vậy, đó là requirements mà bác áp vào từng feature, nếu có bất kỳ big change nào xảy ra khi cần scale system, breaking changes về feature, nếu không có tests, tôi tin không 1 QA, DEV, LEAD nào có thể nhớ tất cả mọi features, đến lúc đấy bác chỉ có khấn cho nó đừng crash project mà thôi.
Viết test sau QA hay đấy thím, phù hợp với product.
Product bị tình trạng chạy theo MVP nên requirement thay đổi rất nhanh, nhiều khi chưa kịp đến QA test thì requirement đã có thay đổi rồi.
 
Viết test sau QA hay đấy thím, phù hợp với product.
Product bị tình trạng chạy theo MVP nên requirement thay đổi rất nhanh, nhiều khi chưa kịp đến QA test thì requirement đã có thay đổi rồi.
Đúng rồi thím, nói chung tôi đi nhiều công ty nhưng vụ test first chắc chỉ ở trường mới làm được thôi, out-source cũng ít thì product chịu sao nổi, requirement nó đổi chóng hết cả mặt, có khi vừa code xong feature lại phải xóa hết làm cái khác, cay đcd :devilish:
 
cho mình hỏi một vấn đề khi viết Unit Test, giả sử hàm của mình dài nên sẽ refactor thành 3-5 sub methods. Nhưng các methods này đặt trong cùng class thì access modifier của nó sẽ là private. Như vậy làm cách nào để Unit test cho các sub methods này
1. Hàm nào cần Unit test thì cứ để nó public/hoặc default
-> không ổn lắm nhỉ
2. Đưa ra các class riêng.
-> Phù hợp với Single Responsibility Principle, nhưng trong nhiều trường hợp cũng không thiết thực lắm
 
cho mình hỏi một vấn đề khi viết Unit Test, giả sử hàm của mình dài nên sẽ refactor thành 3-5 sub methods. Nhưng các methods này đặt trong cùng class thì access modifier của nó sẽ là private. Như vậy làm cách nào để Unit test cho các sub methods này
1. Hàm nào cần Unit test thì cứ để nó public/hoặc default
-> không ổn lắm nhỉ
2. Đưa ra các class riêng.
-> Phù hợp với Single Responsibility Principle, nhưng trong nhiều trường hợp cũng không thiết thực lắm
dùng reflection đó fen
 
cho mình hỏi một vấn đề khi viết Unit Test, giả sử hàm của mình dài nên sẽ refactor thành 3-5 sub methods. Nhưng các methods này đặt trong cùng class thì access modifier của nó sẽ là private. Như vậy làm cách nào để Unit test cho các sub methods này
1. Hàm nào cần Unit test thì cứ để nó public/hoặc default
-> không ổn lắm nhỉ
2. Đưa ra các class riêng.
-> Phù hợp với Single Responsibility Principle, nhưng trong nhiều trường hợp cũng không thiết thực lắm
Theo tôi nghĩ đã là private thì k test. Ngay từ ban đầu trong design của ông là không có mấy private method đó, lý do xuất hiện của nó thì hàm chính dài cần tách ra thành các method riêng. Vậy chỉ cần viết test cái public api + check đủ case là được :D
 
Theo tôi nghĩ đã là private thì k test. Ngay từ ban đầu trong design của ông là không có mấy private method đó, lý do xuất hiện của nó thì hàm chính dài cần tách ra thành các method riêng. Vậy chỉ cần viết test cái public api + check đủ case là được :D
Đúng, nhưng hàm dài, logic phức tạp mà viết test case cho đầy đủ các sub Methods thì rất đau đầu. Nên chia nhỏ ra sẽ dễ hơn, hàm chính chỉ cần test logic chính của nó thôi, ko cần quan tâm logic của sub methods nữa

via theNEXTvoz for iPhone
 
Đúng, nhưng hàm dài, logic phức tạp mà viết test case cho đầy đủ các sub Methods thì rất đau đầu. Nên chia nhỏ ra sẽ dễ hơn, hàm chính chỉ cần test logic chính của nó thôi, ko cần quan tâm logic của sub methods nữa

via theNEXTvoz for iPhone

Unitest miễn là chạy qua hết code, đúng mong đợi là được mà. :D. Nên tớ nghỉ private ko vấn đề đâu. Có cái test tớ đau đầu là call service thứ 3, không biết test sao. Ví dụ call twillo service để làm 2fa. :beat_brick:

Sent using vozFApp
 
Unitest miễn là chạy qua hết code, đúng mong đợi là được mà. :D. Nên tớ nghỉ private ko vấn đề đâu. Có cái test tớ đau đầu là call service thứ 3, không biết test sao. Ví dụ call twillo service để làm 2fa. :beat_brick:

Sent using vozFApp
mock tất cả các response có thể, 401 403 .... Mình không test thằng khác nó chạy thế nào. Mình chỉ test code của mình chạy thế nào khi nhận được các kết quả tương ứng.
 
cho mình hỏi một vấn đề khi viết Unit Test, giả sử hàm của mình dài nên sẽ refactor thành 3-5 sub methods. Nhưng các methods này đặt trong cùng class thì access modifier của nó sẽ là private. Như vậy làm cách nào để Unit test cho các sub methods này
1. Hàm nào cần Unit test thì cứ để nó public/hoặc default
-> không ổn lắm nhỉ
2. Đưa ra các class riêng.
-> Phù hợp với Single Responsibility Principle, nhưng trong nhiều trường hợp cũng không thiết thực lắm
Chung quy thì đấy vẫn có thể coi là 1 method thím ơi, chẳng qua separate thành nhiều method để đảm bảo extract method thôi. Thím có dùng mock, reflect để test method nhỏ thì đằng nào cũng phải test method public dài loằn ngoằn kia thoai. Nhưng mà nên tesst 1 method public là đủ rồi, đảm bảo cover lines hết là ngon cơm roài :big_smile: :big_smile:
 
Chung quy thì đấy vẫn có thể coi là 1 method thím ơi, chẳng qua separate thành nhiều method để đảm bảo extract method thôi. Thím có dùng mock, reflect để test method nhỏ thì đằng nào cũng phải test method public dài loằn ngoằn kia thoai. Nhưng mà nên tesst 1 method public là đủ rồi, đảm bảo cover lines hết là ngon cơm roài :big_smile: :big_smile:
Ví dụ đây là code của main_method
JavaScript:
main_method(){
    ......
    if (1) {
        logic_1
    }
    .....
    if (2) {
        logic_2
    }
    .....
    if (3) {
         logic_3
    }
    .....
}

Nhìn vào đoạn code này, để viết Unit test cho main_method, nó như 1 đống hổ lốn vô tổ chức. Với cá nhân mình đánh giá rất phức tạp
Nếu refactor lại theo hướng này

JavaScript:
main_method(){
    ......
    if (1) {
        submethod1();
    }
    .....
    if (2) {
        submethod2();
    }
    .....
    if (3) {
        submethod3();
    }
    .....
}

Mình sẽ
1. Viết Unit test cho các sub methods riêng
2. main_method sẽ chỉ cần viết Unit test cover hết mấy cái if của nó.
 
main_method sẽ chỉ cần viết Unit test cover hết mấy cái if của nó.
Phần này hơi đi sâu vào kỹ thuật test rồi, tôi ví dụ submethod1, submethod2, submethod tôi giả định là side effect function thì sẽ làm thay đổi biến đầu vào, nếu phần số 2 thím test đủ cases thì sẽ là cascade của 3 phần if (1), if (2), if (3) x với số test cases cho domain testing (ví dụ thím có 1 biến đầu vào là a int thì có 3 trường hợp: a < 0, a = 0, a > 0) Như vậy thì nó đã overlap luôn phần số 1 rồi. Thím phải làm như vậy vì có thể if (1), if (2), if (3) có thể thay đổi cùng 1 biến global trong main_method.

Trường hợp của thím chỉ khả thi khi submethod1,2,3 là pure function thì thím có thể tách ra, domain testing cho từng submethod và cascade cho main_method (vì main_method chỉ quan tâm output của submethod).
FY7e6U1.png
 
Phần này hơi đi sâu vào kỹ thuật test rồi, tôi ví dụ submethod1, submethod2, submethod tôi giả định là side effect function thì sẽ làm thay đổi biến đầu vào, nếu phần số 2 thím test đủ cases thì sẽ là cascade của 3 phần if (1), if (2), if (3) x với số test cases cho domain testing (ví dụ thím có 1 biến đầu vào là a int thì có 3 trường hợp: a < 0, a = 0, a > 0) Như vậy thì nó đã overlap luôn phần số 1 rồi. Thím phải làm như vậy vì có thể if (1), if (2), if (3) có thể thay đổi cùng 1 biến global trong main_method.

Trường hợp của thím chỉ khả thi khi submethod1,2,3 là pure function thì thím có thể tách ra, domain testing cho từng submethod và cascade cho main_method (vì main_method chỉ quan tâm output của submethod).
FY7e6U1.png
Chính xác là như vậy.
Ngoài ra, giả sử không tách hàm, phần if(1) làm thay đổi giá trị của biến a. Đến phần if(2), cùng 1 xử lý logic/case sẽ có 2 trường hợp:

1. case này xảy ra do giá trị của biến a bị thay đổi ở if(1)
2. case này xảy ra không liên quan đến giá trị của biến a

Như vậy, Unit test cũng cần phải conver cả 2 trường hợp này chứ nhỉ.
 
Chính xác là như vậy.
Ngoài ra, giả sử không tách hàm, phần if(1) làm thay đổi giá trị của biến a. Đến phần if(2), cùng 1 xử lý logic/case sẽ có 2 trường hợp:

1. case này xảy ra do giá trị của biến a bị thay đổi ở if(1)
2. case này xảy ra không liên quan đến giá trị của biến a

Như vậy, Unit test cũng cần phải conver cả 2 trường hợp này chứ nhỉ.
Đúng rồi thím, còn cách tôi nói ở trên với pure function là blackbox testing, áp dụng thêm doman testing để cover biến a nữa là okay.
 
Ví dụ đây là code của main_method
JavaScript:
main_method(){
    ......
    if (1) {
        logic_1
    }
    .....
    if (2) {
        logic_2
    }
    .....
    if (3) {
         logic_3
    }
    .....
}

Nhìn vào đoạn code này, để viết Unit test cho main_method, nó như 1 đống hổ lốn vô tổ chức. Với cá nhân mình đánh giá rất phức tạp
Nếu refactor lại theo hướng này

JavaScript:
main_method(){
    ......
    if (1) {
        submethod1();
    }
    .....
    if (2) {
        submethod2();
    }
    .....
    if (3) {
        submethod3();
    }
    .....
}

Mình sẽ
1. Viết Unit test cho các sub methods riêng
2. main_method sẽ chỉ cần viết Unit test cover hết mấy cái if của nó.
Bác ví dụ thế này thì lại rơi vào phần pattern, tuỳ vào từng case nhưng pattern có 2 mục đích chính:

1. Ai cũng hiểu lý do vì sao xài pattern đó, vì pattern là proven và chuẩn rồi
2. Pattern design để dễ test :feel_good:

Một rule of thumb là khi bác phải sửa private method thành public chỉ để viết test cover cho nó thì: hoặc bác xài pattern sai hoặc cách bác viết test sai. Tuy nhiên một vài ngôn ngữ mới nó cố overcome điểm này, ví dụ kotlin định nghĩa scope internal.


via theNEXTvoz for iPhone
 
Bác ví dụ thế này thì lại rơi vào phần pattern, tuỳ vào từng case nhưng pattern có 2 mục đích chính:

1. Ai cũng hiểu lý do vì sao xài pattern đó, vì pattern là proven và chuẩn rồi
2. Pattern design để dễ test :feel_good:

Một rule of thumb là khi bác phải sửa private method thành public chỉ để viết test cover cho nó thì: hoặc bác xài pattern sai hoặc cách bác viết test sai. Tuy nhiên một vài ngôn ngữ mới nó cố overcome điểm này, ví dụ kotlin định nghĩa scope internal.


via theNEXTvoz for iPhone
Bác nói chuẩn, nguyên tắc của tôi thì không bh sửa code để viết được unit test trong mọi trường hợp
yAW5d3s.gif
 
Back
Top