Computer >> Hướng Dẫn Máy Tính >  >> Hệ Thống >> Android

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm

Bởi Siamak Mahmoudi

TDD, hay Phát triển dựa trên thử nghiệm, là một phương pháp phát triển phần mềm trong đó các bài kiểm tra được viết trước khi mã thực tế được triển khai.

Nó đòi hỏi sự hiểu biết rõ ràng về “Cái gì” và “Như thế nào” trong các yêu cầu của dự án/tính năng.

TDD giúp viết ít hơn nhưng đủ mã. Nó giúp ngăn ngừa các lỗi phát triển phần mềm phổ biến, chẳng hạn như kỹ thuật quá mức, phạm vi kiểm thử quá nhiều, thiếu các yêu cầu chính, các hàm và lớp quá lớn cũng như quá nhiều câu lệnh mã phức tạp.

Nhìn chung, việc có một cơ sở mã rõ ràng, ngắn gọn, đã được kiểm thử đơn vị sẽ giúp ích. Theo thời gian, nó cũng tiết kiệm chi phí phát triển và bảo trì mã.

Trong bài viết này chúng ta sẽ thảo luận về hoạt động của TDD.

Bối cảnh là môi trường phát triển Android, vì vậy chúng tôi sẽ sử dụng Kotlin và JUnit5 cùng với một dự án mẫu để minh họa các bước.

Tuy nhiên, các hướng dẫn và kỹ thuật ở đây cũng có thể được thực hành bằng các ngôn ngữ lập trình khác.

Điều kiện tiên quyết

  • Kiến thức cơ bản về Kotlin
  • Kiến thức cơ bản về viết Unit test
  • Kiến thức về giả định và khẳng định

Chúng tôi sẽ sử dụng Kotlin làm ngôn ngữ lập trình và JUnit5 để viết bài kiểm tra đơn vị.

Mockito sẽ được sử dụng để làm việc với các mô hình và gián điệp.

Đối tượng mục tiêu là bất kỳ nhà phát triển phần mềm nào từ bất kỳ nền tảng nào đang tìm kiếm một chương mới trong sự nghiệp của họ.

Mặc dù bối cảnh là Android nhưng nội dung không nói về các thuộc tính cụ thể của nền tảng. Thay vào đó, chúng tôi tập trung vào các kỹ thuật, lưu ý và thách thức khi phát triển với TDD.

Nếu bạn đồng ý với những điều trên thì hãy bắt đầu.

Cách thức hoạt động của quá trình phát triển dựa trên thử nghiệm

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Chu trình TDD

Quá trình phát triển tuân theo một chu kỳ:

  1. Viết một bài kiểm tra trượt (hình vuông màu hồng).
  2. Triển khai mã để vượt qua bài kiểm tra (hình vuông màu xanh lá cây)
  3. Tái cấu trúc mã (hình vuông màu xanh lam) nếu cần trong khi vẫn đảm bảo rằng các bài kiểm tra tiếp tục vượt qua (hình vuông màu xanh nhạt).
  4. Viết một bài kiểm thử thất bại mới (bắt đầu lại quy trình)

Viết một bài kiểm tra thất bại (Hình vuông màu hồng)

Ở bước này, bạn bắt đầu bằng việc mô tả những gì bạn muốn mã của mình thực hiện.

Hãy tưởng tượng bạn đang kiểm tra mã của mình để kiểm tra xem nó có hoạt động chính xác hay không. Bài kiểm tra này giống như một câu hỏi mà bạn đặt ra cho mã của mình, chẳng hạn như "Bạn có thể thực hiện nhiệm vụ này không?"

Lúc đầu, mã của bạn không biết câu trả lời, vì vậy bạn viết một bài kiểm tra có thể thất bại vì mã của bạn chưa biết cách thực hiện nhiệm vụ. Bài kiểm tra thất bại này giống như một dấu hiệu cảnh báo màu hồng cho bạn biết có điều gì đó không ổn.

Sau khi bạn hoàn thành giai đoạn này, JUnit5 sẽ tạo báo cáo toàn diện về các thử nghiệm bạn đã thực hiện. Những bài kiểm tra này sẽ đại diện hữu hình cho công việc của bạn.

Bây giờ, hãy tưởng tượng rằng người quản lý dự án của bạn đang đọc các trường hợp thử nghiệm này để đánh giá cả mức độ bao phủ của chúng cũng như độ chính xác trong hiểu biết của bạn về tính năng hoặc sản phẩm. Việc nắm bắt quan điểm này giúp bạn hiểu rõ hơn về tầm quan trọng của giai đoạn phát triển này.

Chuyển trọng tâm của bạn từ những vấn đề kỹ thuật phức tạp sang hoạt động của phần mềm.

Thay vì bị cuốn vào các khía cạnh kỹ thuật phức tạp, hãy hướng sự chú ý của bạn đến cách phần mềm hoạt động và tương tác với người dùng cũng như các thành phần khác.

Sự thay đổi về quan điểm này cho phép bạn ưu tiên các hành động và kết quả dự định của phần mềm, dẫn đến các thử nghiệm phản ánh chính xác hành vi trong thế giới thực của phần mềm.

Bằng cách tập trung vào hành vi thay vì các chi tiết kỹ thuật vụn vặt, bạn đảm bảo rằng các thử nghiệm của mình phù hợp chặt chẽ với mục đích của phần mềm và mong đợi của người dùng.

Trong một số trường hợp, Bạn có thể kết thúc với không quá một vài trường hợp thử nghiệm cho mỗi thành phần (đó là mục đích:ít công việc hơn nhưng có mục tiêu) và điều đó hoàn toàn ổn, miễn là bạn đáp ứng tất cả các yêu cầu về hành vi của dự án.

Mẹo

  • Hãy cụ thể: Viết các trường hợp kiểm thử rõ ràng và cụ thể tập trung vào một khía cạnh trong hành vi mã của bạn.
  • Bắt đầu đơn giản:Bắt đầu với trường hợp thử nghiệm đơn giản nhất bao gồm chức năng cơ bản mà bạn cần.
@Test fun `a sum is calculated from two input numbers`() {}
  • Sử dụng tên có ý nghĩa:Đặt tên cho bài kiểm tra của bạn một cách mô tả để bất kỳ ai đọc chúng đều biết bài kiểm tra đang kiểm tra cái gì.
@Test fun `Font Ratio is fetched from data source INITIALLY`() {}

Những lỗi thường gặp cần tránh

  • Thử nghiệm quá nhiều cùng một lúc: Tránh thử nghiệm nhiều thứ trong một thử nghiệm duy nhất. Điều này có thể khiến bạn khó xác định được điều gì đang không đạt.
// Don't do this
@Test fun `pixelSize fits the standart sizes while fontSize is bigger than minumum supported font size but matches the list of special levels of size`() {}
  • Dựa vào chi tiết triển khai: Đừng viết các bài kiểm tra gắn chặt với hoạt động bên trong của mã. Kiểm thử nên tập trung vào hành vi chứ không phải việc triển khai.
// Don't do this
@Test fun `pixelSize is Long and Non-Null and fits the standart sizes then calculated font size is non-null and of type Dimention`() {}

Triển khai mã để vượt qua bài kiểm tra (Hình vuông xanh)

Bây giờ bạn đã hoàn thành bài kiểm tra của mình, đã đến lúc hướng dẫn mã của bạn cách thực hiện nhiệm vụ một cách chính xác.

Bạn viết mã thực tế để vượt qua bài kiểm tra và mã của bạn trả lời chính xác câu hỏi.

Khi mã của bạn vượt qua bài kiểm tra, nó giống như đèn xanh thông báo:"Có, tôi có thể thực hiện nhiệm vụ ngay bây giờ!".

Bước này nhằm đảm bảo mã của bạn hiểu và có thể giải quyết vấn đề mà bạn yêu cầu.

Mẹo

  • Viết mã tối thiểu:Viết mã đơn giản nhất giúp vượt qua bài kiểm tra thất bại. Bạn có thể đọc thêm về cách tránh sử dụng kỹ thuật quá mức tại đây.
// Test Case 
@Test fun `Storage stores font ratio in key-value`() { 
 // Given
 val fontRatio = 2.0f
 val mockEditor = mockk<SharedPreferences.Editor>(relaxed = true)
 every { mockSharedPreference.edit() } returns mockEditor 
 every { mockEditor.putFloat(any(), any()) } returns mockEditor 
 every { mockEditor.apply() } just Runs 
 // When 
 storage.saveFontRatio(fontRatio) 
 // Then
 verify(exactly = 1) { 
 mockEditor.apply() 
 }
}
// Correct Implementation - Avoid extra implementation
class SharedPreferenceHelper( 
 private val sharedPreferences: SharedPreferences 
) { 
 fun saveFontRatio(fontRatio: Float) {
 sharedPreferences.edit().putFloat("font-ratio", fontRatio).apply() 
 } 
}
// Wrong Implementation 
class SharedPreferenceHelper(
 private val sharedPreferences: SharedPreferences
) { 
 fun saveFontRatio(fontRatio: Float) { 
 if (fontRatio <= 0.0f) 
 throw IllegalArgumentException("Font ratio must be greater than 0.0f") 
 storeValue(key = FONT_RATIO_KEY, value = fontRatio) 
} 
 private fun storeValue(key: String, value: Float){
 val editor = sharedPreferences.edit() editor.putFloat(key, value)
 editor.apply() 
 }
 fun getFontRatio(): Float { 
 return sharedPreferences.getFloat("font_ratio", 1.0f) } 
}
  • Tránh trùng lặp: Đừng lặp lại mã. Nếu bạn thấy mình viết logic tương tự ở nhiều nơi, hãy xem xét việc tái cấu trúc. Việc cải tiến mã chính này có thể được thực hiện trong giai đoạn này, nhưng nếu việc sửa đổi có thể gây ra tác dụng phụ thì hãy bỏ qua nó.
class ... {
 override fun getDefaultFontSize(): Float {
 val zoomRatio = DEFAULT_SSPEED * DEFAULT_FONT_RATIO / deviceDensity 
 val fontSize = zoomRatio * standardFontSize 
 return fontSize 
 }
 override fun getFontSizeBySSpeed(speed: Int): Float {
 val zoomRatio = speed * DEFAULT_FONT_RATIO / deviceDensity 
 val fontSize = zoomRatio * standardFontSize 
 return fontSize 
 }
}
class ... { 
 override fun getDefaultFontSize(): Float = calculate(DEFAULT_AGE)
 override fun getFontSizeByAge(age: Int): Float = calculate(age) 
 private fun calculate(age: Int): Float {
 val zoomRatio = age * DEFAULT_FONT_RATIO / deviceDensity 
 val fontSize = zoomRatio * standardFontSize 
 return fontSize 
 }
}

Những lỗi thường gặp cần tránh:

  • Vượt lên phía trước:Không viết nhiều mã hơn mức cần thiết để vượt qua bài kiểm tra. TDD là về sự phát triển gia tăng. TDD khuyến khích cách tiếp cận phát triển gia tăng và từng bước. Khi bạn tiến về phía trước, về cơ bản bạn đang cố gắng giải quyết các vấn đề chưa liên quan trực tiếp đến bài kiểm tra hiện tại mà bạn đang làm. Mục tiêu chính là tập trung vào nhiệm vụ trước mắt – vượt qua bài kiểm tra hiện tại – mà không bị xao nhãng bởi các chức năng trong tương lai.
  • Bỏ qua các lỗi kiểm tra:Nếu ban đầu một thử nghiệm không thất bại, bạn có thể đang bỏ sót một trường hợp quan trọng. Điều này thoạt nhìn có vẻ khó xảy ra, nhưng sau khi phát triển một số thành phần kiểm thử, bạn sẽ bắt đầu viết nhiều bài kiểm thử cho một phương pháp duy nhất để kiểm tra các khía cạnh khác nhau của logic. Đây là lúc bạn không nên vui nếu logic chưa được triển khai của bạn vượt qua bài kiểm tra. Nói một cách đơn giản, đây là cách bạn phát hiện lỗi trong giai đoạn phát triển. Vì vậy, hãy chờ đợi sự thất bại khi nó xảy ra.

Tái cấu trúc mã (Hình vuông màu xanh lam) và Đảm bảo thử nghiệm thành công (Hình vuông màu xanh nhạt)

Sau khi mã của bạn vượt qua bài kiểm tra, đã đến lúc dọn dẹp mọi thứ.

Bạn có thể tìm cách làm cho mã của mình có tổ chức hơn, dễ hiểu hơn hoặc thậm chí nhanh hơn. Hãy coi việc này như việc dọn dẹp phòng của bạn sau khi chơi xong, sắp xếp mọi thứ gọn gàng và ngăn nắp sau khi bạn chơi xong. Bạn cải thiện mã của mình mà không thay đổi chức năng của nó.

Khi thực hiện việc này, bạn tiếp tục chạy tất cả các bài kiểm tra của mình để đảm bảo chúng vẫn vượt qua. Nếu quá trình kiểm tra không thành công trong bước này, thì đó giống như một dấu hiệu cảnh báo màu xanh nhạt cho bạn biết rằng nội dung nào đó bạn đã dọn dẹp có thể đã vô tình làm hỏng mã.

Bạn có thể coi phần này là một giai đoạn bảo trì mã riêng biệt.

Giả sử rằng bạn được giao nhiệm vụ dọn sạch một mã cũ và đảm bảo rằng nó tuân theo các nguyên tắc về chất lượng mã của nhóm cũng như các yêu cầu về sản phẩm.

Trong suốt quá trình này, điều quan trọng là duy trì sự cân bằng cẩn thận – tinh chỉnh trong khi vẫn đảm bảo các thử nghiệm của bạn tiếp tục thành công.

Dưới đây là một số ý tưởng và chiến lược cho giai đoạn tái cấu trúc:

  • Mã rõ ràng:Đơn giản hóa các phần phức tạp, thay thế các tên biến không rõ ràng và nâng cao nhận xét để giúp người khác (và bản thân bạn trong tương lai) hiểu mã dễ dàng hơn.
  • Tính mô-đun:Chia các chức năng lớn thành các chức năng nhỏ hơn và tập trung hơn. Điều này làm cho mã của bạn trở nên mô-đun hơn và cho phép bảo trì và thử nghiệm dễ dàng hơn.
  • Xóa phần dư thừa:Xác định mã trùng lặp và hợp nhất mã đó thành các hàm hoặc lớp có thể sử dụng lại. Điều này giúp loại bỏ sự lặp lại và đảm bảo tính nhất quán.
  • Tối ưu hóa:Xác định các lĩnh vực có thể cải thiện hiệu suất. Tuy nhiên, chỉ tối ưu hóa nếu bạn có mục tiêu hiệu suất cụ thể và có bằng chứng cho thấy mã là một nút cổ chai. Tối ưu hóa ở đây là để tránh tiêu hao tài nguyên và không làm cho mã hoạt động hiệu quả.
  • Định dạng nhất quán:Duy trì kiểu mã nhất quán, tuân thủ các quy ước của nhóm hoặc dự án của bạn.
  • Mã không được sử dụng:Loại bỏ mọi biến, hàm hoặc nội dung nhập không được sử dụng làm lộn xộn cơ sở mã.
  • Cải tiến bài kiểm tra:Không giống như nhận thức thông thường về TDD, bạn có thể thêm bài kiểm tra bất cứ khi nào có nhu cầu. Nâng cao bộ thử nghiệm bằng cách thêm các trường hợp thử nghiệm mới để bao gồm các tình huống chưa được giải quyết trước đó. Điều này giúp duy trì phạm vi kiểm tra toàn diện.
  • Tài liệu:Nếu mục đích mã của bạn không rõ ràng ngay từ chính mã đó, hãy cân nhắc việc bổ sung hoặc cải thiện tài liệu để giải thích mục đích và cách sử dụng mã đó. Tránh biến nó thành thói quen. Đây nhằm mục đích giải thích bổ sung cho những trường hợp quan trọng để tránh nhầm lẫn.

Lưu ý rằng mã TDD phải tự thể hiện và độc lập với tài liệu.

Hãy nhớ rằng, trong khi tái cấu trúc, điều quan trọng là phải tiếp tục chạy tất cả các thử nghiệm của bạn để đảm bảo chúng tiếp tục vượt qua.

Mẹo

  • Giữ cho các thử nghiệm luôn toàn diện:Đảm bảo các thử nghiệm của bạn bao gồm nhiều tình huống khác nhau để phát hiện các tác dụng phụ ngoài ý muốn trong quá trình tái cấu trúc.
  • Tái cấu trúc dần dần:Thực hiện những thay đổi nhỏ đối với mã của bạn và chạy thử nghiệm thường xuyên để sớm phát hiện bất kỳ sự hồi quy nào.

Những lỗi thường gặp cần tránh:

  • Tái cấu trúc mà không cần kiểm tra: Tái cấu trúc mà không thực hiện kiểm tra tại chỗ có thể dẫn đến hành vi không mong muốn. Nếu có cơ hội bỏ lỡ một phần logic thì hãy cân nhắc việc viết bài kiểm tra cho phần đó.
  • Thay đổi mã lớn:Đôi khi chúng tôi thay đổi nhiều dòng hơn số lượng chúng tôi đã phát triển để vượt qua bài kiểm tra. Luôn xem xét giai đoạn tái cấu trúc riêng biệt thay vì thực hiện quá nhiều thay đổi trong giai đoạn phát triển vì đây là lựa chọn an toàn hơn và ít tốn kém hơn.

Viết một bài kiểm tra thất bại mới (Khởi động lại quy trình)

Bây giờ, bạn nghĩ đến điều tiếp theo mà bạn muốn mã của mình thực hiện.

Bạn bắt đầu bằng cách viết một bài kiểm thử mới nhưng bài kiểm thử này sẽ thất bại vì mã của bạn chưa biết cách thực hiện tác vụ mới. Điều này giống như đưa ra cho mã của bạn một thử thách mới để giải quyết.

Sau đó, bạn lặp lại toàn bộ chu trình:vượt qua bài kiểm tra bằng mã (hình vuông màu xanh lá cây), dọn dẹp nếu cần (hình vuông màu xanh lam) và tiếp tục kiểm tra để đảm bảo mọi thứ đều hoạt động (hình vuông màu xanh lục nhạt).

Bằng cách này, bạn luôn tiến về phía trước và xây dựng mã của mình từng bước một.

Mẹo

  • Các bước tăng dần:Thêm các thử nghiệm mới cho chức năng mới theo từng bước nhỏ để duy trì lộ trình phát triển rõ ràng. Thay vì cố gắng triển khai một tính năng phức tạp cùng một lúc, bạn chia nó thành các phần nhỏ hơn, dễ quản lý hơn và tạo các thử nghiệm cho từng phần này. Cách tiếp cận này duy trì lộ trình phát triển rõ ràng và ổn định, giúp bạn tập trung, giảm thiểu rủi ro và đảm bảo rằng mỗi phần bổ sung vào phần mềm của bạn đều được kiểm tra kỹ lưỡng.
  • Vòng phản hồi:Sử dụng phản hồi từ việc viết các bài kiểm tra thất bại để hướng dẫn bạn thực hiện. Vòng phản hồi nêu bật tính chất lặp lại của TDD. Khi bạn thực hiện các thử nghiệm mới và quan sát chúng thất bại, bạn sẽ thu được những hiểu biết có giá trị giúp hướng dẫn bạn triển khai.

Đây là cách vòng phản hồi hoạt động:

  • Cài đặt kỳ vọng:Khi viết một bài kiểm thử mới, bạn xác định những kỳ vọng của mình về cách hoạt động của mã. Điều này làm rõ mục tiêu bạn muốn đạt được bằng tính năng mới.
  • Lỗi ban đầu:Thử nghiệm thất bại lúc đầu vì mã tương ứng để đáp ứng mong đợi của nó bị thiếu hoặc không đầy đủ. Thất bại ban đầu này là một phần tự nhiên của quy trình TDD.
  • Hướng dẫn bạn triển khai:Phản hồi từ những thất bại của thử nghiệm sẽ chỉ cho bạn hướng nên viết hoặc sửa đổi mã nào. Nó trở thành một lộ trình cho sự phát triển của bạn, phác thảo chức năng mới sẽ như thế nào.
  • Tiến bộ tăng dần:Khi bạn triển khai mã cần thiết để vượt qua bài kiểm tra, bạn đang dần dần xây dựng chức năng mong muốn. Mỗi bước được hướng dẫn bởi phản hồi do bài kiểm tra thất bại cung cấp.
  • Xác minh:Sau khi quá trình triển khai hoàn tất, bạn sẽ chạy lại thử nghiệm. Nếu vượt qua, nó sẽ xác minh rằng mã mới của bạn đáp ứng những mong đợi mà bạn đặt ra ban đầu.

Vòng phản hồi đảm bảo rằng quá trình phát triển của bạn được liên kết chặt chẽ với các mục tiêu dự định của phần mềm.

Những lỗi thường gặp cần tránh:

  • Viết bài kiểm tra sau khi triển khai:Không viết bài kiểm tra sau khi bạn đã triển khai tính năng này. TDD trước hết là viết bài kiểm tra. Ngay cả một đoạn logic nhỏ được thêm vào trước mã kiểm tra cũng có nghĩa là có thể có lỗi/lỗi tài nguyên trong mã. Vấn đề là không thêm bất kỳ logic nào trừ khi có nhu cầu từ bộ thử nghiệm.
  • Bỏ qua các bài kiểm tra không đạt:Đừng bỏ qua bước này ngay cả khi bạn cho rằng mình biết cách triển khai tính năng này.

Đây là lý do tại sao bạn không nên bỏ qua bước kiểm tra thất bại ngay cả khi bạn tự tin:

  • Mục đích rõ ràng:Việc viết một bài kiểm tra thất bại sẽ làm rõ ý định của bạn đối với tính năng này. Nó buộc bạn phải xem xét hành vi và kết quả chính xác mà bạn hướng tới trước khi bắt tay vào triển khai.
  • Xác minh các giả định:Ngay cả khi bạn cho rằng mình hiểu tính năng này, việc tạo thử nghiệm sẽ đảm bảo rằng các giả định của bạn là hợp lệ. Sự hiểu biết của bạn có thể đúng nhưng bài kiểm tra đã xác nhận điều đó.
  • Mạng lưới an toàn:Bằng cách viết một bài kiểm tra thất bại, bạn thiết lập một mạng lưới an toàn nhằm ngăn chặn sự hồi quy trong tương lai. Nó hoạt động như một thông số kỹ thuật cho tính năng và giúp phát hiện các tác dụng phụ ngoài ý muốn.
  • Phát triển tăng dần:TDD khuyến khích sự phát triển tăng dần. Mỗi tính năng mới được xây dựng từng bước, với sự tiến triển rõ ràng từ thử nghiệm thất bại đến triển khai hoạt động. Bỏ qua bước này sẽ làm gián đoạn tiến trình đó.
  • Tài liệu:Thử nghiệm thất bại ghi lại hành vi dự kiến của tính năng. Tài liệu này có giá trị đối với bạn và nhóm của bạn, đặc biệt khi xem lại mã trong tương lai. Luôn nhớ rằng có các hệ thống tạo báo cáo bằng cách liệt kê tất cả mã kiểm tra của bạn cho người quản lý sản phẩm và QA. Những báo cáo đó tiết lộ những chi tiết bạn phát hiện ra trong sản phẩm. Vì vậy, hãy cố gắng thuyết phục họ rằng bạn hiểu rõ vấn đề.

Cách phát triển bằng TDD

TDD nhấn mạnh tầm quan trọng của việc viết các bài kiểm tra tự động để thúc đẩy việc thiết kế và phát triển phần mềm. Điều này dẫn đến mã đáng tin cậy hơn, dễ bảo trì hơn và dễ thay đổi hơn theo thời gian.

Nhưng làm thế nào chúng ta có thể áp dụng nó vào thực tế? Bằng cách dùng thử và làm quen dần dần.

Hãy thử TDD trong khi phát triển một tính năng mới để chứng minh cách chúng ta có thể bắt đầu sử dụng nó trong thế giới thực.

Chúng tôi sẽ triển khai tính năng tự động cài đặt kích thước phông chữ.

Chúng tôi có một ứng dụng tin tức và người dùng có thể đặt tốc độ cuộn tự động cho nguồn cấp tin tức.

Chúng tôi muốn triển khai tính năng điều chỉnh kích thước phông chữ màn hình theo tốc độ cuộn được đặt trong trang Hồ sơ người dùng.

Nếu người dùng đặt tốc độ cuộn từ 0 đến 1 thì kích thước phông chữ sẽ tăng thêm 1,3.

Bất kỳ tốc độ cuộn nào tăng lên trên 1 sẽ dẫn đến tăng kích thước phông chữ lên 1,2.

Tính năng này cho phép người dùng có trải nghiệm tốt hơn khi đọc tin tức.

Tôi cũng đã chia sẻ mã mà chúng tôi khám phá trong kho lưu trữ GitHub này.

Hãy thoải mái sao chép và chơi với nó.

Cố gắng làm theo các bước khi chúng tôi tiến bộ trong quá trình phát triển. Điều này sẽ giúp bạn nắm bắt được các kỹ thuật và cách suy nghĩ trong bối cảnh TDD một cách thực tế.

Vì vậy, hãy mở Android Studio và tạo một dự án mới.

Biểu đồ luồng dữ liệu tính năng tự động cài đặt kích thước phông chữ

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm DFD của một tính năng mẫu trong ứng dụng Android

Trên đây là sơ đồ về luồng dữ liệu cuối cùng sẽ trông như thế nào.

Dưới đây là tóm tắt về từng thành phần hấp dẫn:

AutoScrollSettingsUseCase Lớp sẽ xử lý logic tính toán và lưu trữ FontRatio dựa trên Tốc độ cuộn đã chọn.

Ca sử dụng này sẽ phụ thuộc vào UserRepository lưu trữ FontRatio giá trị.

Trong UserRepository , có các phương pháp để lưu trữ và truy xuất FontRatio giá trị bằng cách sử dụng Storage cơ chế. Bất cứ khi nào có FontRatio mới được gửi đến bộ lưu trữ, mọi vật thể quan sát sẽ nhận được bức xạ có giá trị mới nhất.

Trong UserProfileViewModel , có một phiên bản của AutoScrollSettingsUseCase đang được gọi bất cứ khi nào người dùng cập nhật Tốc độ cuộn. Điều này sẽ kích hoạt việc tính toán lại FontRatio và nó được lưu trữ thông qua kho lưu trữ.

Chúng tôi sẽ có các thành phần UI cần thiết trong phần Cài đặt người dùng để cho phép người dùng nhập tốc độ cuộn mong muốn của họ. Điều này có thể được thực hiện bằng cách sử dụng các thành phần giao diện người dùng Android tiêu chuẩn như NumberPicker hoặc các thành phần giao diện người dùng tùy chỉnh (Chúng tôi sẽ không thảo luận về những phần này).

Đây là bản phân tích cho một tính năng đơn giản và mỗi khi bạn xem qua nó, bạn sẽ thấy rõ hơn các bước và kết quả cuối cùng là gì. Điều quan trọng là phải làm điều đó cho những thay đổi của bạn.‌ ‌

Cách viết bài kiểm tra

Bước đầu tiên là luôn tự tạo lớp kiểm tra. Trong trường hợp này, chúng ta sẽ có ít nhất các lớp kiểm tra sau:

  • UserRepositoryTest
  • AutoScrollSettingTest
  • UserSettingsViewModelTest

Tôi thích bắt đầu với phần ViewModel hơn.

ViewModel là một thành phần Kiến trúc Android giúp thoát khỏi những thay đổi trong vòng đời (chẳng hạn như nền trước, nền sau, tiêu điểm). Vì vậy, đó là một nơi tốt để lưu trữ các trạng thái của chúng ta.

Hãy tạo tệp thử nghiệm bên trong unitTest thư mục của mã nguồn theo cùng đường dẫn gói với mã tính năng thực.

TDD trong thực tế không giống với việc phát triển công việc kế thừa.

Với TDD, chúng tôi sử dụng IDE để tăng cường quá trình tạo tệp và thuộc tính (trường). Nhưng chúng tôi tạo tệp Kiểm tra theo cách thủ công! Sau một vài lần thử, khía cạnh này của IDE sẽ trở nên hữu ích.

Tạo cấu trúc mã (gói) rồi tạo lớp kiểm tra của bạn bằng cách nhấp chuột phải vào gói và chọn Class loại.

Chọn một tên mô tả cho nó (có thể bạn nên tuân theo quy ước cho nó nếu chưa làm như vậy).

Ví dụ:xxx is tested for , trong đó xxx là tên của thành phần được kiểm tra.

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Sử dụng IDE để tạo tệp

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm

Bây giờ, hãy tạo các bài kiểm tra trống. Cố gắng càng rộng càng tốt.

Theo sơ đồ, chúng ta sẽ không có nhiều logic cho tính năng này.

Có hai chiến lược chính để viết các hàm kiểm thử đơn vị:

  • AAA
  • Cho/Khi/Sau đó
@Test fun `strategy A`(){ 
 // Arrange
 // Act 
 // Assert 
}
@Test fun `strategy B`(){ 
 // Given 
 // When 
 // Then 
}

Hãy chọn một và làm theo nó cho tất cả các bài kiểm tra của bạn.

Khái niệm này giống nhau:nhóm mã kiểm tra của bạn lại để dễ đọc và bảo trì.

Đây là những gì tôi có hiện tại:

class UserProfileViewModel is tested for` {
 // Unimplemented Class 
 val viewModel = UserProfileViewModel()
 @Test
 fun Font Ratio is fetched from data source`(){}
 @Test
 fun `Scroll Speed update is called so fontSize calculations are triggered`() {}
 @Test
 fun `Font Ratio is updated with new emissions from data source`() {}
}

Hãy chạy thử nghiệm!

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Thử nghiệm thất bại do thiếu mục tiêu thử nghiệm

Nó đang thất bại. Trên thực tế, quá trình xây dựng đã thất bại- Không phải thử nghiệm.

Xin chúc mừng! Chúng ta vừa mới thực hiện được bước đầu tiên trong chu trình TDD:

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Bước đầu tiên trong chu trình TDD

Vì ViewModel chưa tồn tại nên chúng tôi có màu đỏ.

Bây giờ, hãy tạo một phiên bản của ViewModel,

Vì vậy, chúng tôi sử dụng IDE để tạo một lớp bị thiếu hoặc mã chưa được triển khai.

Để làm cho hộp thoại này bật lên, tôi di chuyển con trỏ đến phần chưa được triển khai và nhấn Option + Return (trên macOS).

Sau đó, hãy làm theo các tùy chọn được cung cấp:

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Hành động TDD:Tạo tệp mục tiêu thông qua tệp UnitTest

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Chọn đích chính xác cho tệp mới

Và bây giờ hãy chạy lại thử nghiệm (bước cuối cùng):

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Hiển thị bài kiểm tra đã đạt

Vâng! Nó đã trôi qua.

Lưu ý rằng các bài kiểm tra này có nội dung trống và chúng không kiểm tra gì cả! Điều đó đúng và ổn.

Chúng ta thậm chí nên tiếp tục tạo tất cả các lớp kiểm thử (vẫn còn phần thân kiểm thử trống) cho mọi thành phần trong sơ đồ DFD - Tôi đã chia sẻ những lớp này ở đầu bài viết.

Điều này còn giúp chúng tôi hiểu rõ hơn về tính năng này trước khi triển khai.

Cuối cùng, chúng tôi sẽ có khoảng 3-4 lớp kiểm tra bao gồm các tình huống chung và bài kiểm tra Đơn vị để thực hiện.

Nó sẽ trông giống như thế này:

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Các trường hợp kiểm thử trống tối thiểu

Hãy triển khai một trong số chúng làm ví dụ.

Tuy nhiên, trước đó, chúng ta sẽ cần phải làm việc với các mô hình dữ liệu UI và Domain của tính năng này.

Vì vậy, để có thể di chuyển dữ liệu xung quanh, hãy tạo trước các lớp dữ liệu bạn cần.

Quay lại ProfileViewModel của chúng tôi lớp kiểm tra, chúng tôi có một chức năng kiểm tra đơn vị trống. Hãy thực hiện điều đó.

Lưu ý rằng điều quan trọng ở đây là đọc bài kiểm tra cẩn thận và tránh thực hiện thêm bất kỳ xác nhận nào.

Chỉ có yêu cầu mới được phép thực hiện.

Trong trường hợp này, chúng ta cần một luồng dữ liệu được kết nối với nguồn dữ liệu đã tạo trước đó(UserRepository ).

Đừng quên:Trước tiên chúng ta cần một Bài kiểm tra Thất bại.

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Thực hiện phần bên trong

Lưu ý các phần chưa được triển khai bên trong thân hàm kiểm tra (được đánh dấu bằng phông chữ màu đỏ).

Bây giờ, hãy triển khai mã, sau đó cấu trúc lại mã để mã có thể vượt qua.

Tôi đang sử dụng thư viện MockK để mô phỏng các lớp và đối tượng cũng như Turbine để kiểm tra các luồng Flow tại đây.

Nếu bạn không quen thuộc với họ, đừng hoảng sợ! Chỉ cần kiểm tra trang web chính thức của họ và thử chúng.

Trước tiên, hãy tạo phần phụ thuộc và thêm nó vào ViewModel bằng cách sử dụng Đối số được đặt tên. Đối số được đặt tên giúp chúng ta khi tạo tham số thông qua IDE để giới thiệu tên riêng thông qua mã kiểm tra.

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Tạo tham số bị thiếu bằng hộp thoại IDE

Làm tương tự với FontRatio biến bên trong Kho lưu trữ.

Cuối cùng, mã kiểm tra cuối cùng có thể giống như mã bên dưới:

class `UserProfileViewModel is tested for` {
 init {
 Dispatchers.setMain(Dispatchers.Unconfined)
 }
 val mockUserRepository = mockk<UserRepository>()
 @Test
 fun `Font Ratio is fetched from data source`() = runTest {
 // Given
 val expectedRatio = 2.0f
 every { mockUserRepository.fontRatio } returns flowOf(FontRatioUiModel(expectedRatio))
 val viewModel = UserProfileViewModel(userRepository = mockUserRepository)
 // When
 viewModel.fontRatio.test {
 val fromDataSource = expectItem()
 // Then
 assertEquals(/* expected = */ expectedRatio, /* actual = */ fromDataSource.fontRatio)
 }
 }
...
}

Lưu ý rằng chúng tôi không triển khai các phần bên trong của ViewModel hoặc Repository tại đây.

Chúng tôi chỉ tạo những phần còn thiếu để loại bỏ lỗi khỏi phần thân thử nghiệm.

Chúng tôi sẽ triển khai những chi tiết đó trong lần lặp tiếp theo.

Chạy thử nghiệm ngay bây giờ.

Tất nhiên là sẽ thất bại, vì chúng ta đã không triển khai FontRatio bên trong ProfileViewModel .

Bây giờ, hãy cấu trúc lại ViewModel để vượt qua bài kiểm tra (việc triển khai ở mức tối thiểu).

Trong trường hợp này, chỉ cần kết nối luồng trạng thái với kho lưu trữ. Chúng tôi đã thêm nó làm phần phụ thuộc trong lần lặp trước.

Đây là mã cuối cùng:

class UserProfileViewModel(
 userRepository: UserRepository
) : ViewModel() {
 val fontRatio: StateFlow<FontRatioUiModel> = userRepository.fontRatio.stateIn(
 initialValue = FontRatioUiModel(DEFAULT_FONT_RATIO),
 scope = viewModelScope,
 started = SharingStarted.Lazily
 )
 companion object {
 private const val DEFAULT_FONT_RATIO = 1.0f
 }
}

Chạy lại bài kiểm tra và Bùm! Nó trôi qua!

Làm chủ TDD Android:Hướng dẫn thực hành để phát triển dựa trên thử nghiệm Vượt qua bài kiểm tra sau khi triển khai mã chính ở mức tối thiểu

Với bài kiểm tra Đơn vị này, chúng tôi đã triển khai phần chính của UserProfileViewModel thành phần. Nhưng chỉ những phần cần thiết thôi. Làm tương tự cho các trường hợp thử nghiệm còn lại.

Đừng xử lý các trường hợp kiểm thử này giống như bạn làm với các bài kiểm thử Đơn vị thông thường (chạy và vượt qua nhanh chóng).

Trước tiên hãy dành chút thời gian để hiểu các yêu cầu kỹ thuật và sản phẩm. Sau đó, triển khai kế hoạch và bắt đầu thực hiện. Sau một vài lần thử, bạn sẽ dễ dàng suy nghĩ theo cách TDD hơn.

Mã nguồn

Mã nguồn và kho lưu trữ cho dự án này có sẵn trên trang GitHub của tôi.

Hãy kiểm tra nó và hoàn thành các bước tiếp theo. Tôi đã tách các bước lặp trong các nhánh khác nhau để bạn có thể so sánh nó với cách triển khai của riêng bạn.

Kết luận

Vì vậy, sau khi đi sâu vào Phát triển dựa trên thử nghiệm (TDD) và khám phá chi tiết về nó, tôi phải nói rằng, đó là một công cụ thay đổi cuộc chơi!

Hãy để tôi chia nhỏ nó cho bạn:

Những điểm chính:

  • TDD chủ yếu là viết bài kiểm tra trước khi viết mã thực tế. Lúc đầu nghe có vẻ hơi lạ nhưng tin tôi đi, nó có tác dụng kỳ diệu.
  • Quy trình TDD tuân theo một chu trình đơn giản:viết các bài kiểm tra thất bại, triển khai mã để vượt qua các bài kiểm tra đó, sau đó tái cấu trúc nếu cần để giữ cho mọi thứ hoạt động trơn tru.
  • Bằng cách nhấn mạnh vào các thử nghiệm tự động, TDD giúp chúng tôi thiết kế và phát triển phần mềm vững chắc, có thể bảo trì và thích ứng theo thời gian.

Chúng tôi đã bắt đầu bằng cách tạo các lớp kiểm tra và viết các hàm kiểm tra trống nhưng nhiệm vụ của bạn bây giờ là hoàn thành nó (Hoặc bạn có thể chuyển sang kho lưu trữ được chia sẻ ngay lập tức :)).

Có vẻ hơi kỳ lạ khi có những bài kiểm tra không làm được gì, nhưng đó đều là một phần của kế hoạch.

Tiếp theo, chúng tôi đặt ra một kế hoạch rõ ràng về những gì tính năng của chúng tôi nên làm, dựa trên biểu đồ luồng dữ liệu. Điều này đã giúp chúng tôi hiểu được các yêu cầu trước khi bắt tay vào triển khai.

Với kế hoạch trong tay, chúng tôi bắt đầu triển khai các thành phần cần thiết (trong trường hợp này là ViewModel), đảm bảo rằng các thử nghiệm của chúng tôi trước tiên đã thất bại. Đúng vậy, thất bại trong các bài kiểm tra thực sự là một điều tốt trong TDD!

Dần dần, chúng tôi kết nối các phần lại với nhau, như việc lập UserAutoScrollSettingsUseCase lớp để xử lý việc tính toán kích thước phông chữ dựa trên tốc độ cuộn tự động (Kiểm tra repo dự án).

Chúng tôi cũng đã xử lý các thành phần giao diện người dùng, cho phép người dùng nhập tốc độ cuộn mong muốn và đảm bảo kích thước phông chữ được điều chỉnh cho phù hợp (Kiểm tra kho lưu trữ dự án).

Trong suốt quá trình, chúng tôi đảm bảo giữ cho mã của mình rõ ràng và đơn giản, tập trung vào những gì cần thiết để vượt qua các bài kiểm tra. Không có sự phức tạp không cần thiết ở đây!

Cuối cùng, chúng tôi đã thiết lập và chạy tính năng "Tự động cài đặt kích thước phông chữ", với các bài kiểm tra đã đạt kết quả cao.

Hãy nhớ rằng, TDD không phải là việc vội vàng thực hiện các bài kiểm tra hoặc viết mã một cách điên cuồng. Đó là sự thận trọng và chu đáo trong quá trình phát triển của bạn, điều này sẽ mang lại lợi ích lớn về lâu dài.

Vì vậy, nếu bạn đang muốn nâng cao khả năng phát triển phần mềm của mình, hãy thử TDD! Đó là một cách tiếp cận mạnh mẽ giúp mã của bạn vững chắc hơn, giảm lỗi và giúp bạn trở thành nhà phát triển giỏi hơn về tổng thể.

Điều tôi đã chia sẻ là cách chúng tôi làm việc trong nhóm của mình và nó có hiệu quả với chúng tôi, nhưng đó không phải là giải pháp hoàn hảo cho mọi nhóm/công ty. Bạn cần phải tìm hiểu xem nó có phải là của bạn hay không. Hãy cho tôi biết nếu bạn nghĩ tôi có thể cải thiện giải pháp này.

Chúc mừng mã hóa! 🚀

Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu