TDD: Tôi đang làm đúng không?


14

Tôi là một lập trình viên mới (chỉ mới học được khoảng một năm) và trong mục tiêu trở nên tốt hơn về nó, tôi mới học về TDD gần đây. Tôi muốn tập thói quen sử dụng nó vì nó có vẻ rất hữu ích. Tôi muốn kiểm tra và chắc chắn rằng tôi đang sử dụng nó một cách chính xác.

Những gì tôi đang làm:

  1. Hãy nghĩ về một phương pháp mới tôi cần.
  2. Tạo một bài kiểm tra cho phương pháp đó.
  3. Kiểm tra thất bại.
  4. Viết phương pháp.
  5. Thi đỗ.
  6. Phương pháp tái cấu trúc.
  7. Nói lại.

Tôi đang làm điều này cho MỌI phương pháp tôi viết, có một số điều tôi không nên bận tâm? Sau này tôi thường nghĩ ra một cách để kiểm tra các phương thức đã có của mình theo một cách hoặc tình huống khác. Tôi có nên thực hiện các thử nghiệm mới này không, hoặc vì mỗi phương pháp đã có một thử nghiệm riêng nên tôi không nên bận tâm? Tôi có thể QUÁ TRÌNH kiểm tra mã của mình không, tôi đoán là mối quan tâm chính của tôi khi hỏi điều này.

BIÊN TẬP

Ngoài ra, đây là điều tôi chỉ thắc mắc. Khi làm một cái gì đó như tạo GUI, TDD có cần thiết trong tình huống đó không? Cá nhân, tôi không thể nghĩ làm thế nào tôi sẽ viết bài kiểm tra cho điều đó.


5
Bạn đã làm điều đó tốt hơn rất nhiều so với các chuyên gia dày dạn nói rằng họ đang thử nghiệm mọi thứ (nhưng không).
yannis

Những gì bạn mô tả không phải là Tinh thần của TDD.

1
Bạn có thể muốn xem xét ATDD hoặc BDD.
dietbuddha

Có lẽ bắt đầu cao hơn - nghĩ về một mô-đun mới mà bạn cần.

Câu trả lời:


16

Theo tôi, những gì bạn đang mô tả như là một quy trình công việc không phải là tinh thần của TDD.

Bản tóm tắt của cuốn sách Kent Becks trên Amazon nói:

Rất đơn giản, phát triển dựa trên thử nghiệm có nghĩa là để loại bỏ nỗi sợ hãi trong phát triển ứng dụng.Trong khi một số nỗi sợ là lành mạnh (thường được xem là một lương tâm nói với các lập trình viên "hãy cẩn thận!"), Tác giả tin rằng các sản phẩm phụ của nỗi sợ hãi bao gồm các lập trình viên khó tính, cục cằn và không truyền thông, không thể tiếp thu những lời chỉ trích mang tính xây dựng. Khi các nhóm lập trình mua vào TDD, họ thấy ngay kết quả tích cực. Họ loại bỏ nỗi sợ liên quan đến công việc của họ, và được trang bị tốt hơn để giải quyết những thách thức khó khăn mà họ phải đối mặt. TDD loại bỏ các đặc điểm dự kiến, nó dạy các lập trình viên giao tiếp và nó khuyến khích các thành viên trong nhóm tìm kiếm những lời chỉ trích. Nói tóm lại, tiền đề đằng sau TDD là mã cần được kiểm tra và tái cấu trúc liên tục.

TDD thực hành

Kiểm thử tự động chính thức, đặc biệt là Kiểm thử đơn vị mọi phương thức của mọi lớp đều tệ như một mô hình chống và không kiểm tra bất cứ điều gì. Có một sự cân bằng để có được. Bạn đang viết bài kiểm tra đơn vị cho mọi setXXX/getXXXphương pháp, chúng cũng là phương pháp!

Các xét nghiệm cũng có thể giúp tiết kiệm thời gian và tiền bạc, nhưng đừng quên rằng chúng tốn thời gian và tiền bạc để phát triển và chúng là mã, vì vậy chúng tốn thời gian và tiền bạc để duy trì. Nếu họ teo vì thiếu bảo trì thì họ trở thành một trách nhiệm nhiều hơn là một lợi ích.

Giống như mọi thứ như thế này, có một sự cân bằng không thể được xác định bởi bất kỳ ai trừ chính bạn. Bất kỳ giáo điều nào, có lẽ là sai nhiều hơn mà đúng.

Một số liệu tốt là mã quan trọng đối với logic nghiệp vụ và có thể sửa đổi thường xuyên dựa trên các yêu cầu thay đổi. Những thứ đó cần các bài kiểm tra chính thức được tự động hóa, đó sẽ là một khoản đầu tư lớn.

Bạn sẽ rất khó khăn để tìm thấy nhiều cửa hàng chuyên nghiệp hoạt động theo cách này. Nó chỉ không có ý nghĩa kinh doanh để chi tiền thử nghiệm những thứ sẽ cho tất cả các mục đích thực tế không bao giờ thay đổi sau khi thử nghiệm khói đơn giản được hình thành trước. Viết các bài kiểm tra đơn vị tự động chính thức cho .getXXX/.setXXXcác phương pháp là một ví dụ điển hình cho việc này, hoàn toàn lãng phí thời gian.

Bây giờ là hai thập kỷ kể từ khi chỉ ra rằng thử nghiệm chương trình có thể chứng minh một cách thuyết phục sự hiện diện của lỗi, nhưng không bao giờ có thể chứng minh sự vắng mặt của chúng. Sau khi trích dẫn lời nhận xét được công bố rộng rãi này, kỹ sư phần mềm trở lại trật tự trong ngày và tiếp tục hoàn thiện các chiến lược thử nghiệm của mình, giống như nhà giả kim của yore, người tiếp tục tinh chỉnh các tinh chế chrysocosmic của mình.

- Edsger W. Djikstra . (Được viết vào năm 1988, vì vậy giờ đã gần 4,5 thập kỷ.)

Xem thêm câu trả lời này .


1
Đó là khá nhiều địa chỉ những gì tôi đã quan tâm. Tôi cảm thấy rằng tôi không nên thử nghiệm mọi phương pháp như tôi, nhưng không chắc chắn. Có vẻ như tôi vẫn cần đọc thêm một chút về TDD.
cgasser

@kevincline Hầu setXXX/getXXXnhư không cần thiết chút nào :)
Chip

1
Khi bạn ghi nhớ getXXX tầm thường đó và hiểu sai, hoặc giới thiệu tải lười biếng trong getXXX của bạn và hiểu sai, thì bạn sẽ biết rằng đôi khi bạn thực sự muốn kiểm tra getters của mình.
Frank Shearar

13

Bạn đang rất thân thiết. Hãy thử suy nghĩ theo cách hơi khác nhau này.

  1. Hãy nghĩ về một hành vi mới mà tôi cần.
  2. Tạo một bài kiểm tra cho hành vi đó.
  3. Kiểm tra thất bại.
  4. Viết phương pháp mới hoặc mở rộng.
  5. Thi đỗ.
  6. Mã tái cấu trúc.
  7. Nói lại.

Đừng tự động tạo getters và setters cho mọi thuộc tính . Đừng nghĩ về toàn bộ phương pháp và viết (các) bài kiểm tra để bao quát tất cả các chức năng . Cố gắng gói gọn các thuộc tính bên trong lớp và viết các phương thức để cung cấp hành vi mà bạn cần. Hãy để phương pháp của bạn phát triển thành một thiết kế tốt thay vì cố gắng lên kế hoạch trước. Hãy nhớ rằng TDD là một quy trình thiết kế, không phải là quá trình thử nghiệm. Ưu điểm của nó so với các quy trình thiết kế khác là bỏ lại một loạt các bài kiểm tra hồi quy tự động phía sau, thay vì một mảnh giấy bạn ném vào thùng.

Ngoài ra, hãy nhớ ba quy tắc TDD của chú Bob .

  1. Bạn không được phép viết bất kỳ mã sản xuất nào trừ khi nó không vượt qua bài kiểm tra đơn vị.
  2. Bạn không được phép viết thêm bất kỳ bài kiểm tra đơn vị nào là đủ để thất bại; và thất bại biên dịch là thất bại.
  3. Bạn không được phép viết thêm bất kỳ mã sản xuất nào đủ để vượt qua bài kiểm tra đơn vị thất bại.

1
@Zexanima: Bạn đang làm tốt hơn hầu hết chúng ta sau một năm. Chỉ cần cố gắng chỉ cho bạn bước tiếp theo.
pdr

2
Tôi nghĩ rằng 3 quy tắc bạn liên kết đến; bình dị như họ có thể nghe, đặc biệt giáo điều và rất cứng nhắc phi thực tế trong 99% của tất cả các cửa hàng sản xuất mà bất cứ ai sẽ gặp phải.

1
@FrankShearar hoặc nó có thể được coi là sự phỉ báng phi thực tế của một phần tử cực đoan cơ bản và bán buôn bị coi thường. Tôi đã làm việc trong các cửa hàng có thái độ giáo điều này, họ đã giáo điều theo nghĩa đen và bỏ lỡ quan điểm; viết các bài kiểm tra không kiểm tra bất kỳ mã thực tế nào của họ theo cách thực tế và cuối cùng chỉ kiểm tra khả năng của Mocking và Dependency Injection framework để nhầm lẫn giữa những gì quan trọng nhất.

1
@pdr Tinh thần của một cái gì đó trái ngược với sự phong thánh chính thức giáo điều của điều đó. Đó là một điều để có một triết lý và một điều khác để biến nó thành một tôn giáo . TDD nhiều lần hơn là không nói về các điều khoản tôn giáo giáo điều đen trắng . Đó là 3 quy tắc nghe có vẻ giáo điều và tôn giáo trong cách trình bày và những gì được nghe là câu thần chú Test, Test, Test , đối với một người như OP, họ hiểu chúng theo nghĩa đen và điều đó gây hại nhiều hơn là tốt. Tôi phản bác Frank rằng các tuyên bố phân cực có thể gây hại nhiều hơn cho nguyên nhân hơn là tốt.

2
Quan điểm của tôi là chủ nghĩa giáo điều xuất phát từ việc mù quáng chấp nhận một cái gì đó là phúc âm . Lấy tuyên bố phân cực, thử nó, làm cho nó buộc bạn ra khỏi vùng thoải mái của bạn. Bạn không thể đánh giá sự đánh đổi liên quan đến TDD nếu bạn không thử phương pháp cực đoan 3 điểm hoặc không có gì, bởi vì bạn sẽ không có dữ liệu .
Frank Shearar

5

Vài điều cần thêm vào phản hồi của người khác:

  1. Có một điều như là quá thử nghiệm. Bạn muốn đảm bảo kiểm tra đơn vị của bạn chồng chéo ít nhất có thể. Không có điểm nào có nhiều thử nghiệm xác minh các điều kiện giống nhau trong cùng một đoạn mã. Mặt khác, khi bạn cấu trúc lại mã sản xuất của mình và bạn có nhiều thử nghiệm chồng lấp phần đó, bạn sẽ phải quay lại và sửa tất cả các thử nghiệm đó. Trong khi đó, nếu chúng không trùng nhau, thì một thay đổi sẽ chỉ phá vỡ một thử nghiệm.

  2. Chỉ vì bạn nghĩ ra một cách tốt hơn để viết một bài kiểm tra, tôi sẽ không quay lại đó và bắt đầu viết lại nó. Điều này sẽ quay trở lại với những cá nhân tiếp tục viết và viết lại cùng một lớp / hàm vì họ cố gắng làm cho nó hoàn hảo. Nó sẽ không bao giờ hoàn hảo, vì vậy hãy tiếp tục. Khi bạn khám phá ra một phương pháp tốt hơn, hãy giữ nó ở phía sau tâm trí của bạn (hoặc thêm vào nhận xét của bài kiểm tra). Lần tới khi bạn ở đó, và bạn thấy lợi ích ngay lập tức của việc chuyển sang cách mới, đó là thời gian để tái cấu trúc. Mặt khác, nếu tính năng được thực hiện và bạn tiếp tục và mọi thứ hoạt động, hãy để nó hoạt động.

  3. TDD tập trung vào việc cung cấp giá trị ngay lập tức, không chỉ đơn giản là đảm bảo mọi chức năng đều có thể kiểm tra được. Khi bạn thêm chức năng, hãy bắt đầu bằng cách hỏi "khách hàng cần gì". Sau đó xác định một giao diện để cung cấp cho khách hàng những gì nó cần. Sau đó thực hiện bất cứ điều gì cần thiết để làm cho bài kiểm tra vượt qua. TDD gần giống như thử nghiệm các tình huống sử dụng (bao gồm tất cả "what-ifs"), thay vì chỉ đơn giản là mã hóa các chức năng công cộng và kiểm tra từng kịch bản.

  4. Bạn đã hỏi về việc kiểm tra mã GUI. Tra cứu các mẫu "Hộp thoại khiêm tốn" và "MVVM". Ý tưởng đằng sau cả hai điều này là bạn tạo ra một tập hợp các lớp "mô hình xem", thực sự không có logic dành riêng cho UI. Tuy nhiên, các lớp này sẽ có tất cả logic nghiệp vụ thường là một phần của giao diện người dùng của bạn và các lớp này phải được kiểm tra 100%. Những gì còn lại là lớp vỏ UI rất mỏng và vâng, điển hình là lớp vỏ đó không có phạm vi kiểm tra, nhưng tại thời điểm đó, nó gần như không có logic.

  5. Nếu bạn có một phần lớn mã hiện có, như một số người khác đề xuất, bạn không nên bắt đầu thêm các bài kiểm tra đơn vị ở mọi nơi. Nó sẽ đưa bạn mãi mãi và bạn sẽ không nhận được lợi ích từ việc thêm các bài kiểm tra đơn vị vào 80% các lớp ổn định và sẽ không thay đổi trong tương lai gần (hoặc không quá gần). Tuy nhiên, đối với công việc mới, tôi thấy việc sử dụng phát triển TDD với mã TẤT CẢ là cực kỳ có lợi. Bạn không chỉ kết thúc với một bộ với các bài kiểm tra tự động khi bạn hoàn thành, mà sự phát triển thực tế có lợi ích rất lớn:

    • Bằng cách xem xét khả năng kiểm tra, bạn sẽ viết mã ít được ghép nối và mô đun hơn
    • Bằng cách xem xét hợp đồng công khai của bạn trước bất cứ điều gì khác, bạn sẽ kết thúc với các giao diện công cộng sạch hơn nhiều
    • Khi bạn đang viết mã, việc xác minh chức năng mới mất một phần nghìn giây so với việc chạy toàn bộ ứng dụng của bạn và cố gắng thực thi theo đúng đường dẫn. Nhóm của tôi vẫn phát hành mã xử lý lỗi thậm chí chưa được thực thi ONCE chỉ vì họ không thể có được các điều kiện phù hợp để xảy ra. Thật đáng ngạc nhiên khi chúng ta lãng phí bao nhiêu thời gian khi sau này trong QA những điều kiện đó xảy ra. Và vâng, rất nhiều mã này là điều mà ai đó sẽ coi là "không phải là khu vực cho nhiều thay đổi trong tương lai sau khi thử nghiệm khói được thực hiện".

1

Có một số phương pháp không được thử nghiệm, cụ thể là những thử nghiệm đó. Tuy nhiên, có một điều cần nói đối với một số thử nghiệm được thêm vào sau khi mã ban đầu được viết, chẳng hạn như các điều kiện biên và các giá trị khác để có thể có nhiều thử nghiệm trên một phương thức.

Mặc dù bạn có thể kiểm tra mã của mình, nhưng điều đó thường xảy ra khi ai đó muốn kiểm tra mọi hoán vị có thể có của các đầu vào không giống với những gì bạn đang làm. Ví dụ: nếu bạn có một phương thức lấy một ký tự, bạn có viết một bài kiểm tra cho mọi giá trị có thể có thể được nhập không? Đó sẽ là nơi bạn sẽ vượt qua, IMO.


À được rồi. Đó không phải là những gì tôi đang làm. Tôi thường chỉ nghĩ đến một tình huống khác mà tôi có thể kiểm tra các phương pháp của mình theo cách sau khi tôi đã thực hiện thử nghiệm ban đầu. Tôi chỉ chắc chắn rằng những bài kiểm tra 'thêm' đó đáng để thực hiện, hoặc nếu nó đã kết thúc.
cgasser

Nếu bạn làm việc với mức tăng đủ nhỏ, bạn thường có thể chắc chắn chắc chắn rằng bài kiểm tra của bạn thực sự hoạt động. Nói cách khác, có một bài kiểm tra thất bại (vì lý do chính đáng!) Chính là kiểm tra bài kiểm tra. Nhưng mức độ "chắc chắn hợp lý" đó sẽ không cao bằng mã được thử nghiệm.
Frank Shearar

1

Nói chung là bạn đang làm đúng.

Các xét nghiệm là mã. Vì vậy, nếu bạn có thể cải thiện bài kiểm tra, hãy tiếp tục và cấu trúc lại nó. Nếu bạn nghĩ rằng một bài kiểm tra có thể được cải thiện hãy tiếp tục và thay đổi nó. Đừng ngại thay thế một bài kiểm tra bằng một bài kiểm tra tốt hơn.

Tôi khuyên bạn nên kiểm tra mã của mình, tránh chỉ định cách mã được cho là làm những gì nó đang làm. Các xét nghiệm nên xem kết quả của các phương pháp. Điều này sẽ giúp tái cấu trúc. Một số phương pháp không cần phải được kiểm tra rõ ràng (nghĩa là các getters và setters đơn giản) bởi vì bạn sẽ sử dụng chúng để xác minh kết quả của các thử nghiệm khác.


Tôi đã viết bài kiểm tra cho getters và setters quá vì vậy cảm ơn cho mẹo đó. Điều đó sẽ giúp tôi tiết kiệm một số công việc không cần thiết.
cgasser

"Một số phương thức không cần phải được kiểm tra rõ ràng (ví dụ: getters và setters đơn giản)" - Bạn chưa bao giờ sao chép / dán một getter và setter và quên thay đổi tên trường đằng sau nó? Điều đơn giản về mã đơn giản là nó yêu cầu các bài kiểm tra đơn giản - bạn thực sự tiết kiệm được bao nhiêu thời gian?
pdr

Tôi không có nghĩa là phương pháp này không được thử nghiệm. Nó chỉ được kiểm tra thông qua xác nhận rằng các phương thức khác được thiết lập hoặc trong quá trình thiết lập thử nghiệm thực tế. Nếu getter hoặc setter không hoạt động chính xác, thử nghiệm sẽ thất bại vì các thuộc tính không được đặt chính xác. Bạn được họ kiểm tra miễn phí, ngầm.
Schleis

Các bài kiểm tra Getter và setter không mất nhiều thời gian, vì vậy tôi có thể sẽ tiếp tục thực hiện chúng. Tuy nhiên, tôi không bao giờ sao chép và dán bất kỳ mã nào của mình để tôi không gặp phải vấn đề đó.
cgasser

0

Ý kiến ​​của tôi về TDD là công cụ đã tạo ra một thế giới của các nhà phát triển kiểu 'điểm và nhấp chuột'. Chỉ vì các công cụ tạo sơ đồ kiểm tra cho mỗi phương thức không có nghĩa là bạn nên viết bài kiểm tra cho mọi phương thức. Một số người đang 'đổi tên' TDD thành BDD (phát triển theo hướng hành vi) trong đó các bài kiểm tra có chi tiết lớn hơn nhiều và nhằm kiểm tra hành vi của lớp, chứ không phải từng phương thức nhỏ.

Nếu bạn thiết kế các bài kiểm tra của mình để kiểm tra lớp theo dự định sẽ sử dụng, thì bạn bắt đầu đạt được một số lợi ích, đặc biệt là khi bạn bắt đầu viết các bài kiểm tra thực hiện nhiều hơn một chút so với mỗi phương thức, đặc biệt là khi bạn bắt đầu kiểm tra sự tương tác của các phương thức đó phương pháp. Tôi cho rằng bạn có thể nghĩ về nó như viết bài kiểm tra cho một lớp, hơn là các phương thức. Trong mọi trường hợp, bạn vẫn phải viết 'kiểm tra chấp nhận' thực hiện kết hợp các phương pháp để đảm bảo không có mâu thuẫn hoặc xung đột trong cách chúng được sử dụng cùng nhau.

Đừng nhầm lẫn TDD với thử nghiệm - không phải vậy. TDD được thiết kế để bạn viết mã để thực hiện các yêu cầu của bạn, không phải để kiểm tra các phương thức. Đó là một điểm tinh tế nhưng quan trọng thường bị mất đối với những người mù quáng viết mã kiểm tra cho mọi phương thức. Bạn nên viết các bài kiểm tra để đảm bảo mã của bạn thực hiện những gì bạn muốn, chứ không phải mã mà bạn đã viết hoạt động như được yêu cầu.

Có một số liên kết tốt ở bên phải về BDD v TDD. Kiểm tra chúng ra.


0

Khi bạn bắt đầu học TDD, vâng, bạn nên mù quáng tuân theo cách tiếp cận giáo điều là không viết một dòng mã nào ngoại trừ việc vượt qua bài kiểm tra thất bại và chỉ viết đủ bài kiểm tra để thất bại (và thất bại vì lý do đúng / dự kiến) .

Một khi bạn đã học được TDD nói về cái gì, THÌ bạn có thể quyết định rằng một số thứ nhất định không đáng để thử nghiệm. Đây là cách tiếp cận tương tự mà bạn nên tuân theo đối với mọi thứ và võ thuật Nhật Bản gọi đây là " shuhari ". (Liên kết cũng giải thích cách người ta có thể tiến bộ qua các giai đoạn học mà không cần giáo viên, tôi nghi ngờ, hầu hết mọi người phải học như thế nào.)


0

Tôi tin rằng bạn đang vượt qua.

Tôi đã thực hành TDD trong nhiều năm và theo kinh nghiệm của tôi, khi TDD được thực hiện hiệu quả, bạn sẽ nhận được hai lợi ích chính:

  • Cung cấp phản hồi nhanh chóng
  • Cho phép tái cấu trúc

Cung cấp phản hồi nhanh chóng

Riêng với các ngôn ngữ động, tôi có thể thực hiện các bài kiểm tra có liên quan trong chưa đầy một giây. Và tôi có trình theo dõi hệ thống tệp tự động chạy các kiểm tra này khi tệp nguồn được thay đổi trên đĩa. Do đó, tôi hầu như không có thời gian chờ đợi để kiểm tra và ngay lập tức tôi biết liệu mã tôi viết có đúng như mong đợi hay không. Do đó TDD dẫn đến một cách làm việc rất hiệu quả.

Cho phép tái cấu trúc

Nếu bạn có một bộ kiểm thử tốt, bạn có thể tái cấu trúc một cách an toàn, khi bạn có được những hiểu biết mới về cách hệ thống nên được thiết kế.

Một bộ kiểm tra tốt cho phép bạn di chuyển trách nhiệm trong mã của mình và vẫn tự tin rằng mã hoạt động như mong đợi sau khi di chuyển. Và bạn sẽ có thể làm điều này với một vài thay đổi đối với mã kiểm tra.

Nếu bạn viết kiểm tra cho mọi phương thức trong hệ thống của mình, thì tỷ lệ cược là bạn không thể dễ dàng cấu trúc lại mã của mình, mọi mã cấu trúc lại mã của bạn sẽ yêu cầu thay đổi lớn đối với mã kiểm tra. Và bạn thậm chí có thể chắc chắn rằng mã kiểm tra vẫn hoạt động như mong đợi? Hoặc bạn đã vô tình giới thiệu một lỗi trong mã kiểm tra, do đó dẫn đến một lỗi trong mã sản xuất?

Tuy nhiên, nếu bạn cũng được đề xuất trong câu trả lời của pdr , hãy tập trung vào hành vi thay vì các phương pháp khi viết bài kiểm tra, bạn sẽ có các bài kiểm tra sẽ yêu cầu ít thay đổi hơn khi tái cấu trúc hệ thống.

Hoặc như Ian Cooper nói trong bài trình bày này (tôi đã trích dẫn từ bộ nhớ, vì vậy có thể không được trích dẫn chính xác):

Lý do của bạn để viết một bài kiểm tra mới nên thêm một hành vi mới, không thêm một lớp mới


-2

Bạn nên kiểm tra mọi phương pháp công khai .

Điều hấp dẫn ở đây là nếu các phương thức công khai của bạn rất nhỏ thì có lẽ bạn đang tiết lộ quá nhiều thông tin. Thực tế phổ biến của việc phơi bày mọi tài sản như getXXX()thực sự phá vỡ đóng gói.

Nếu các phương thức công khai của bạn thực sự là hành vi của lớp, thì bạn nên kiểm tra chúng. Nếu không, chúng không phải là phương pháp công cộng tốt.

EDIT: câu trả lời của pdr hoàn chỉnh hơn nhiều so với của tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.