Ta có thể thêm Widget dùng để tách các Widget con qua separatorBuilder
Sử dụng ListView.custom
Đây là cách xây dựng ListView giúp bạn có thể tùy chỉnh nhiều hơn cho các model con. Ví dụ: một model con tùy chỉnh có thể kiểm soát thuật toán được sử dụng để ước tính kích thước của các mô hình con không thực sự hiển thị.
Phần này sẽ nói về các kiểu border, enabled và combined effects bên trong Input Decoration của TextField Widget.
Border
None
Underline
Tạo đường viền gạch dưới cho TextField. Ta có thể sử dụng thuộc tính borderRadius bên trong thuộc tính UnderlineInputBorder này.
Outline with border radius
Tạo một đường viền hình chữ nhật được bo các góc cho TextField và thuộc tính borderRadius bên trong OutlineInputBorder có chức năng thay đổi độ bo tròn viền của hình chữ nhật.
Specific border behavior
Border là một trường hợp chung nhưng bạn cũng có thể sửa đổi điều đó cho các tình huống cụ thể thiết lập focusedBorder, enabledBorder, disabledBorder, errorBorder và focusedBorder.
Trường hợp enabledBorder.
Trường hợp focusedBorder.
Filled with color
Thuộc tính filled giúp lấp đầy vùng trang trí bằng fillColor nếu filled có giá trị là true.
Filled with color and no underline
Thuộc tính BorderSide.none dùng để xóa hết Border khỏi TextField.
Ta có thể sử dụng “decoration:null” hoặc InputDecoration.collapsed để loại bỏ underline mặc định của TextField.
Hover color
Thuộc tính này sẽ thay đổi màu của TextField khi mà người dùng di chuyển chuột hoặc con trỏ qua TextField.
Enabled
Nếu là false thì helperText, errorText và counterText sẽ không được hiển thị và độ mờ của các thành phần hình ảnh còn sẽ lại sẽ giảm.
Thuộc tính này mặc định là true.
True
False
Combined effects
None
Dense
Giúp cho TextField sử dụng ít khoảng trắng hơn nếu là true.
Thuộc tính isDense có giá trị mặc định là false.
Content padding
Là phần khoảng cách để nội dung không bị dí sát vào viền của TextField.
Nếu isCollapsed là True thì thuộc tính contentPadding sẽ mặc định là zero (EdgeInsets.zero).
Nếu thuộc tính isOutline là false và thuộc tính filled là true thì thuộc tính contentPadding sẽ có giá trị là EdgeInsets.fromLTRB(12, 8, 12, 8) khi isDense là true và có giá trị là EdgeInsets.fromLTRB(12, 8, 12, 8) khi isDense là false.
Nếu isOutline và filled là false thì contentPadding sẽ có giá trị là EdgeInsets.fromLTRB(0, 8, 0, 8) khi isDense là true và có giá trị là EdgeInsets.fromLTRB(0, 12, 0, 12) khi isDense là false.
Tiếp tục với nội dung của phần 1, phần 2 sẽ nói về các thuộc tính counter, style, max lines, hintTextDirection, floating label behavior và thuộc tính isCollapsed trong Input Decoration.
Counter
Bạn có thể thực hiện thay đổi widget dựa trên số lượng ký tự đã được nhập. Nếu thuộc tính này không rỗng thì counterText sẽ bị bỏ qua.
Style
Thuộc tính này giúp thay đổi TextStyle cho các loại Text bên trong InputDecoration.
Ví dụ bên dưới hiển thị thay đổi TextStyle của hintStyle, nhưng nó cũng làm tương tự nhau để đặt TextStyle cho labelStyle, counterStyle, errorStyle, prefixStyle, suffixStyle và helperStyle.
Max lines
Nếu không có MaxLines thì mặc định sẽ là 1 dòng.
Dưới đây là ví dụ hintMaxLines cho hintText, nhưng còn có errorMaxLines và helperMaxlines đều sử dụng giống như hintMaxlines.
Hint text direction
Hint text direction có nhiệm vụ điều hướng văn bản cho hintText.
Left to right
Right to left
Floating label behavior
Never
Khi người dùng tương tác với TextField, nhãn mác sẽ không xuất hiện ở bên trên trường nhập liệu.
Auto
Nhãn mác của TextField sẽ được đẩy lên và xuất hiện ở bên trên trường nhập liệu khi ta tương tác với TextField để nhập liệu.
Always
Nhãn mác của TextField sẽ luôn luôn được xuất hiện ở bên trên trường nhập liệu.
Condensed
Thay đổi kích thước của TextField giúp cho giảm bớt khoảng trắng trong trường nhập.
TextField là một Widget nhập văn bản cho phép người dùng thu thập dữ liệu vào từ bàn phím mobile vào ứng dụng. Chúng ta có thể sử dụng Widget TextField trong việc xây dựng biểu mẫu, gửi tin nhắn, tạo trải nghiệm tìm kiếm và nhiều hơn nữa. Theo mặc định của Flutter, TextField được trang trí bằng một gạch dưới.
Dưới đây là một số cách để trang trí cho TextField.
None
Icon
Một icon để hiện thị phía trước trường nhập và bên ngoài vùng chứa của trang trí.
Kích thước và màu sắc của icon được định cấu hình tự động bằng cách sử dụng IconTheme.
Prefix icon
Một biểu tượng xuất hiện ở trước phần có thể chỉnh sửa của trường nhập văn bản, bên trong decoration’s container. PrefixIcon bị hạn chế với kích thước tối thiểu là 48px x 48px, nhưng có thể được mở rộng hơn thế.
Suffix icon
Một biểu tượng xuất hiện ở sau phần có thể chỉnh sửa của trường nhập văn bản, bên trong decoration’s container. SuffixIcon bị hạn chế với kích thước tối thiểu là 48px x 48px, nhưng có thể được mở rộng hơn thế.
Prefix text
Là tiền tố văn bản được chọn để đặt trên dòng trước đầu vào.
Hint text
Là văn bản dùng để gợi ý loại đầu vào nào mà trường nhập chấp nhận.
Suffix text
Là hậu tố văn bản được tùy chọn để đặt trên dòng sau phần nhập.
Label text
Văn bản mô tả trường nhập cho người dùng biết.
Khi trường nhập trống hoặc không được nhấn vào, nhãn sẽ hiển thị bên trên đầu trường nhập. Khi trường nhập được nhấn vào hoặc nhận bất cứ giá trị nào thì nhãn sẽ di chuyển lên trên theo chiều dọc liền kề trường nhập.
Khi người dùng chưa tương tác với TextField.
Khi người dùng tương tác với TextField.
Error text
Văn bản xuất hiện bên dưới InputDecorator.child và đường viền, văn bản thể hiện lỗi khi nhập liệu của người dùng.
Helper text
Văn bản cung cấp ngữ cảnh về giá trị của InputDecorator.child, chẳng hạn như cách giá trị sẽ được sử dụng như thế nào. Văn bản ở cùng vị trí với errorText.
Nếu một giá trị errorText không phải null thì helperText sẽ không được hiển thị.
Counter text
Văn bản được đặt bên phải phía bên dưới trường nhập dưới dạng số ký tự.
Trong hầu hết các app hiện nay việc lấy dữ liệu từ server hay data base là việc hết sức bình thường để app của bạn có thể cập nhập các data mới nhất cũng như tương tác của người dùng . Trong bài viết này mình sẽ call data từ server một cách đơn giản nhất để những ai chưa biết có thể call được 1 cách dễ dàng . flutter có hỗ trợ nhiều thư viện nhưng ở bài viết này mình sẽ sử dụng packet http . http là một thư viện Future-based sử dụng tính năng await và async. Nó cung cấp phương thức cấp cao và đơn giản để phát triển REST trên ứng dụng di động.
Một vài phương thức chính :
– read : gởi yêu cầu lên sever thông qua phương thức GET và trả về Future<String>
– get : gởi yêu cầu lên sever thông qua phương thức GET và trả về Future<Response>. Response là lớp giữ lại các thông tin phản hồi
– post : gởi yêu cầu lên sever thông qua phương thức POST bằng việc đưa giá trị lên sever và phản hồi Future<Response>
– put : gởi yêu cầu lên sever thông qua phương thức PUT và trả về phản hồi như Future<Response>
– head : gởi yêu cầu lên sever thông qua phương thức HEAD và trả về phản hồi như Future<Response>
– delete : gởi yêu cầu lên sever thông qua phương thức DELETE và trả về phản hồi như Future<Response>
Ở bài viết này sẽ chỉ tập trung vào phương thức GET và hiển thị data dưới dangj list view cơ bản .
Step 1 : Bạn cần add thư viện http ở pub.dev vào file pubspec.yaml
Viết factory constructor trong lớp model , Post.fromMap dùng để chuyển đổi dữ liệu map trong đối tượng Post . Thông thường, tệp JSON sẽ được chuyển đổi bên trong đối tượng Dart Map và sau đó chuyển đổi sang đối tượng liên qua (Post)
Step 3 : Bây giờ ta sẽ viết 2 phương thức – parsePost và fetchPost – trong lớp chính để lấy và tải thông tin sản phẩm từ web server(máy chủ) trong List<Post>
Step 4 : Ta có thể hiển thị data lên màn hình ở main.dart
Và đây là kết quả :
Hi vọng qua bài viết này các anh em có thể nắm được cách get data từ server một cách dễ dàng
Trong hầu hết các app hiện nay việc lấy dữ liệu từ server hay data base là việc hết sức bình thường để app của bạn có thể cập nhập các data mới nhất cũng như tương tác của người dùng . Trong bài viết này mình sẽ call data từ server một cách đơn giản nhất để những ai chưa biết có thể call được 1 cách dễ dàng . flutter có hỗ trợ nhiều thư viện nhưng ở bài viết này mình sẽ sử dụng packet http . http là một thư viện Future-based sử dụng tính năng await và async. Nó cung cấp phương thức cấp cao và đơn giản để phát triển REST trên ứng dụng di động.
Một vài phương thức chính :
– read : gởi yêu cầu lên sever thông qua phương thức GET và trả về Future<String>
– get : gởi yêu cầu lên sever thông qua phương thức GET và trả về Future<Response>. Response là lớp giữ lại các thông tin phản hồi
– post : gởi yêu cầu lên sever thông qua phương thức POST bằng việc đưa giá trị lên sever và phản hồi Future<Response>
– put : gởi yêu cầu lên sever thông qua phương thức PUT và trả về phản hồi như Future<Response>
– head : gởi yêu cầu lên sever thông qua phương thức HEAD và trả về phản hồi như Future<Response>
– delete : gởi yêu cầu lên sever thông qua phương thức DELETE và trả về phản hồi như Future<Response>
Đối với phép chia, ngay cả với số nguyên, Dart suy ra biến kết quả là a double. Đó là lý do tại sao bạn nhận được 42.0 thay vì 42 cho print câu lệnh cuối cùng.
So sánh bằng
Dart sử dụng toán tử double-equals ( ==) và not-equals ( !=):
Ngoài ra, nó cũng sử dụng các toán tử số học / phép gán ghép thông thường:
var value = 42.0;
value += 1; print(value); // 43.0
value -= 1; print(value); // 42.0
value *= 2; print(value); // 84.0
value /= 2; print(value); // 42.0
Toán tử số học / phép gán ghép thực hiện hai nhiệm vụ. +=thêm giá trị ở bên phải vào biến ở bên trái và sau đó gán kết quả cho biến.
Một dạng rút gọn của += 1 là ++:
value++;
print(value); // 43.0
Và Dart có toán tử modulo thông thường ( %) để trả về phần còn lại sau khi một số đã được chia cho một số khác:
print( 392 % 50 ); // 42
392 ÷ 50 = 7,84 nhưng 42 đó trong ngăn kết quả đến từ đâu? Nó dễ dàng hơn để xem với sự phân chia dài.
Toán tử logic
Dart sử dụng các toán tử logic giống như các ngôn ngữ khác, bao gồm và có kí hiệu là &&, OR có kí hiệu là ||
Loại chuỗi Dart là String. Các chuỗi được thể hiện trong Dart bằng cách sử dụng văn bản được bao quanh bởi dấu ngoặc kép đơn hoặc kép.
Bạn có thể sử dụng var và nhập suy luận hoặc String để tạo biến chuỗi, giống như các kiểu khác mà bạn đã thấy:
var firstName = 'Albert' ; String lastName = "Einstein" ;
Tương tự như các ngôn ngữ như Kotlin và Swift, bạn có thể nhúng giá trị của một biểu thức bên trong một chuỗi bằng cách sử dụng ký hiệu ký hiệu đô la: $ { biểu thức }.
Nếu biểu thức là một số nhận dạng, bạn có thể bỏ qua dấu {} . Thêm những điều sau:
var physicist = "$firstName $lastName likes the number ${84 / 2}";
print(physicist); // Albert Einstein
$firstName và $lastName được thay thế bằng các giá trị biến. Trả về kết quả được tính toán.
Escaping Strings
Các trình tự thoát được sử dụng trong Dart tương tự như các trình tự được sử dụng trong các ngôn ngữ giống C khác. Ví dụ, bạn sử dụng \n cho một dòng mới.
Nếu có các ký tự đặc biệt trong chuỗi, hãy sử dụng \ để thoát khỏi chúng:
var quote = 'If you can\'t explain it simply\nyou don\'t understand it well enough.';
print(quote);
// If you can't explain it simply
// you don't understand it well enough.
Ví dụ này sử dụng các dấu ngoặc kép, vì vậy nó cần một chuỗi thoát \', để nhúng các dấu nháy đơn cho can’tvà don’t vào chuỗi. Bạn sẽ không cần phải thoát khỏi dấu nháy đơn nếu thay vào đó bạn sử dụng dấu ngoặc kép.
Nếu bạn cần hiển thị trình tự thoát trong chuỗi, bạn có thể sử dụng raw strings , có tiền tố là r.
var rawString = r"If you can't explain it simply\nyou don't understand it well enough.";
print(rawString);
// If you can't explain it simply\nyou don't understand it well enough.
Ở đây, Dart được coi `\n`là văn bản bình thường vì chuỗi bắt đầu bằng r.
Nhấp vào RUN trong DartPad để xem tất cả các chuỗi của bạn trong bảng điều khiển.
Immutability
Dart sử dụng các từ khóa constvà finalcác giá trị không thay đổi.
Sử dụng constcho các giá trị đã biết tại thời điểm biên dịch. Sử dụng finalcho các giá trị không cần biết tại thời điểm biên dịch nhưng không thể gán lại sau khi được khởi tạo.Lưu ý : finalhoạt động giống như valtrong Kotlin hoặc lettrong Swift.
Bạn có thể sử dụng constvà finalthay cho varvà để phép suy luận kiểu xác định kiểu:
const speedOfLight = 299792458 ;
in (speedOfLight); // 299792458
Ở đây, Dart cho rằng speedOfLight là một biến kiểu int, như bạn có thể thấy trong bảng thông tin của DartPad.
final cho biết rằng một biến là không thể thay đổi , nghĩa là bạn không thể gán lại final các giá trị. Bạn có thể nêu rõ loại với một trong hai final hoặc const:
final planet = 'Jupiter';
// planet = 'Mars';
// error: planet chỉ có thể khởi tạo 1 lần
final String moon = 'Europa';
print('$planet has a moon, $moon');
// Jupiter has a moon, Europa
Nullability
Trước đây, nếu bạn không khởi tạo một biến, Dart đã cấp cho nó giá trị null, có nghĩa là không có gì được lưu trữ trong biến. Tuy nhiên, kể từ Dart 2.12, Dart kết hợp với các ngôn ngữ khác, như Swift và Kotlin, để không thể null theo mặc định.
Ngoài ra, Dart đảm bảo rằng kiểu không thể null sẽ không bao giờ chứa giá trị null. Điều này được gọi là sound null safety. .
Thông thường, nếu bạn muốn khai báo một biến, bạn phải khởi tạo nó:
String middleName = 'May';
print(middleName); // May
Tuy nhiên, không phải tất cả mọi người đều có tên đệm, vì vậy việc tạo middleName một giá trị vô hiệu là rất hợp lý. Để nói với Dart rằng bạn muốn cho phép giá trị null, hãy thêm ? vào sau kiểu giữ liểu.
Giá trị mặc định cho kiểu nullable là null, vì vậy bạn có thể đơn giản hóa biểu thức thành như sau:
String? middleName;
print(middleName); // null
Run nó và null in ra bảng điều khiển.
Toán tử Null-Aware
Dart có một số toán tử nhận biết null mà bạn có thể sử dụng khi làm việc với các giá trị null.
Toán tử dấu hỏi kép ?? giống như toán tử Elvistrong Kotlin: Nó trả về toán hạng bên trái nếu đối tượng không phải là null. Nếu không, nó trả về giá trị bên phải:
var name = middleName ?? 'none';
print(name); // none
Vì middleName là null, Dart chỉ định giá trị bên phải của 'none'.
Toán tử ?. bảo vệ bạn khỏi việc truy cập các thuộc tính của các đối tượng null. Nó trả về null nếu bản thân đối tượng là null. Nếu không, nó trả về giá trị của thuộc tính ở phía bên phải:
print(middleName?.length); // null
Ngày trước khi chưa có null safety, nếu bạn quên dấu chấm hỏi và viết middleName.length, ứng dụng của bạn sẽ gặp sự cố trong thời gian chạy nếu middleNametrống. Đó không còn là vấn đề nữa, vì Dart giờ đây sẽ cho bạn biết ngay lập tức khi nào bạn cần xử lý các giá trị null.
Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động, trên con đường trở thành master fluter thì Widget chính là chìa khóa. Trong Flutter, mọi thứ đều là widget. Và vì vậy điều quan trọng là phải hiểu cách chúng hoạt động và cách Flutter hiển thị các Widget. Trong bài viết này chúng ta sẽ tìm hiểu về Element Tree, Widget Tree và Render Tree.
Widget tree
Đầu tiên là Widget tree. Widget tree được sử dụng để cấu hình Giao diện người dùng. Ở đó, bạn có thể định cấu hình các thuộc tính của Widget và định nghĩa nó sẽ hiển thị như thế nào. Tức là code mà bạn viết sẽ tạo lên widget tree.
Element tree
Cây thứ hai được gọi là Element tree. Element tree được sử dụng để quản lý và cập nhật mọi thứ. Element tree liên kết vào Widget tree, một phần tử trong Element tree là một thể hiện cụ thể của một widget. Điều này nghe có vẻ quen thuộc nếu bạn biết OOP (Lập trình hướng đối tượng) nơi bạn có các class và object trong đó các object chỉ là các thể hiện của một class.
Render Tree
Mảnh ghép còn thiếu cuối cùng là Render Object. Bên trong Render tree là các render object và về cơ bản đây là những gì bạn đang thấy trên màn hình. Bạn không thấy các widget mà bạn thấy các render object. Render object sẽ quan tâm đến kích thước, bố cục và bức tranh thực tế trên màn hình.
Cách Flutter render các widget – 3 trạng thái
Flutter trải qua 3 giai đoạn khi hiển thị các widget ra màn hình. Như chúng ta đã biết, đó không phải là những widget mà chúng ta thấy trên màn hình. Đó là các render object. Nhưng làm thế nào Flutter có thể xử lý điều này?
1. Cấu hình
Như tôi đã đề cập ở trên, widget tree chứa các widget và ở trạng thái này, tất cả là về cấu hình của các widget của chúng ta. Thông qua một API, bạn chỉ định các thuộc tính và giá trị cho các tiện ích con của mình mà cây tiện ích con sẽ nắm giữ.
2. Vòng đời
Tại đây, toàn bộ vòng đời của giao diện người dùng được quản lý. Cũng ở đây, nó xác định các thành phần hiện có trong hệ thống phân cấp giao diện người dùng của bạn. Bạn có thể hình dung trạng thái này giống như chất keo kết dính giữa trạng thái 1 và 3.
3. Vẽ
Ở đây Render tree phát huy tác dụng. Tất cả những điều liên quan về vẽ UI sẽ được thực hiện ở đây. Nó quan tâm đến các ràng buộc, phần con của các widget sẽ thực sự trông như thế nào, kích thước của chúng ra sao. Đây là nơi các render object được vẽ trên màn hình.
Ok, nhưng tại sao lại là 3 cây?
Bây giờ bạn có thể tự hỏi mình “OK Nhưng tại sao Flutter không chỉ sử dụng các widget và chỉ có một cây?”.
Thực ra đây là một câu hỏi rất hay.
Đó là về hiệu năng
Ok, điều đầu tiên.
Các widget là bất biến.
Bạn không thể thay đổi các widget. Và về cơ bản đây là lý do tại sao một widget tree là không đủ. Hãy tưởng tượng bạn thay đổi một widget Text chẳng hạn. Bạn sẽ cần một widget hoàn toàn mới bởi vì bạn không thể thay đổi cùng một widget. Và nếu bạn đang code Flutter, bạn có thể biết UI có thể thay đổi thường xuyên như thế nào.
Điều này có nghĩa là khi một widget cần thay đổi, đối tượng widget bên trong widget tree sẽ được thay thế hoàn toàn và không thể cập nhật được.
Và tại đây 2 cây còn lại phát huy tác dụng.
Cả Element và Render Tree đều được cập nhật bất cứ khi nào có thể thay vì được tạo hoàn toàn mới. Và đây là một sự cải thiện hiệu suất lớn!
Cập nhật hay không cập nhật?
Và làm thế nào Flutter quyết định xem Element và Render Object có thể được cập nhật hay không?
Hãy tưởng tượng bạn có một Widget Text, nơi bạn chỉ cần thay đổi nội dụng văn bản. Lần thứ hai bạn thay đổi nó, Phương thức CanUpdate của Widget sẽ kiểm tra hai thứ và nếu một trong số chúng là đúng, thì Render Object và Element Object sẽ được cập nhật thay vì được tạo ra các đối tượng hoàn toàn mới.
RuntimeType (kiểu widget như Text, Column, Container, v.v.) có còn giống nhau không?
Key có giống nhau không?
Trong ví dụ của chúng ta, runtimeType vẫn là Text và vì vậy nó đúng và Đối tượng phần tử sẽ gọi Phương thức updateRenderObject để đảm bảo Render Object được cập nhật thay vì được tạo lại.
Key là một thuộc tính mà bạn có thể cấp cho hầu hết mọi widget con và giúp Flutter xác định một widget con. Khá hữu ích cho mục đích này.
Và bạn có thể kiểm tra hành vi này khá dễ dàng. Khi bạn chạy một Ứng dụng Flutter bên trong IDE của mình và mở Flutter DevTools, bạn sẽ thấy chế độ xem sau:
Ở đây tôi đã chọn một widget Text và bên trong hộp màu đỏ, bạn sẽ thấy Render Object. Và Render Object này có ID bắt đầu bằng ký hiệu #. Khi bạn thay đổi nội dung văn bản cho Widget Text này, ID sẽ KHÔNG thay đổi.
Tóm lại
Flutter sẽ cố gắng sử dụng lại càng nhiều tài nguyên (các object trong cây) càng tốt trong khi cố gắng render các đối tượng mới càng ít càng tốt.
Kết luận
Flutter khá thông minh trong cách hiển thị các widget!
Bài viết này đã cung cấp và đi sâu hơn vào đặc tính và cách render UI của Flutter. Tôi hy vọng bây giờ bạn đã có thể hiểu rõ hơn về những gì xảy ra đằng sau quá trình render các Widget. Mong là bạn sẽ thích bài viết này.
[FLUTTER] Cách viết Unit Test trong Flutter (Phần 1)
Lời ngỏ
Unit Test là một phần rất quan trọng trong quá trình phát triển phần mềm, tuy nhiên nó thường xuyên bị lãng quên với một lập trình viên mới vào nghề hoặc chưa có nhiều kinh nghiệm. Mong rằng bài viết sẽ giúp bạn có cái nhìn trực quan hơn về Unit Test tron phát triển phầm mềm, đặc biệt là trong Flutter.
A. Đôi điều về Unit Test
1. Unit Test là gì?
1.1 Ảnh unit test
Unit Test là một loại kiểm thử phần mềm trong đó các đơn vị hay thành phần riêng lẻ của phần mềm được kiểm thử. Kiểm thử đơn vị được thực hiện trong quá trình phát triển ứng dụng. Mục tiêu của Kiểm thử đơn vị là cô lập một phần code và xác minh tính chính xác của đơn vị đó.
Nếu khái niệm trên vẫn còn khá khó hiểu thì hãy thử tách nghĩa từng từ ra một nhé:
Unit là một thành phần Phần mềm nhỏ nhất mà ta có thể kiểm tra được như các hàm (Function), thủ tục (Procedure), lớp (Class), hoặc các phương thức (Method).
Test thì là kiểm thử, kiểm tra tính chính xác của một cái gì đó.
Đến đây thì chắc các bạn cũng đã có cho mình một chút khái niệm về Unit Test rồi đúng không nhỉ.
Thường các lập trình viên khi nghe về một khái niệm mà trong đó có từ “Test” thì điều mọi người sẽ nghĩ đến ngay đó là “Test là công việc của Tester đâu phải việc của mình nên mình không cần quan tâm :v”.
Nhưng KHÔNG, các bạn đã nhầm to. Unit Test sẽ phải được viết bởi các lập trình viên, bởi chính những người viết ra những dòng code đó.
2. Vòng đời của Unit Test
Vòng đời của Unit Test gồm 3 giai đoạn:
Fail (trạng thái lỗi).
Ignore (tạm ngừng thực hiện).
Pass (trạng thái làm việc).
Ba giai đoạn này sẽ thay phiên nhau làm việc khi một Unit Test được chạy tự động.
3. Unit Test quan trọng không và khi nào thì cần viết Unit Test?
Unit Test là một phần không thể thiếu trong quá trình phát triển phần mềm. Unit Test đem lại cho chúng ta rất nhiều lợi ích:
Tạo ra một môi trường để kiểm tra bất kỳ đoạn code nào, duy trì sự ổn định của phần mềm. Unit Test giúp chúng ra kiểm tra những kết quả trả về mong muốn cũng như những ngoại lệ mong muốn.
Phát hiện các lỗi, các xử lý không hiệu quả trong code, các vấn đề về thiết kế.
Việc viết Unit Test có thể coi như việc tạo một người dùng đầu tiên cho ứng dụng, từ đó chúng ta có thể biết được những vấn đề mà trong quá trình sử dụng ứng dụng người dùng có thể gặp phải.
Giúp cho quá trình phát triển phần mềm trở nên nhanh hơn, số lượng test case khi được test cũng sẽ pass nhiều hơn. Điều này giúp cho các bộ phận khác như QA, Tester làm việc sẽ nhàn hơn. Và trên hết đối với những coder chúng ta, việc ít phải đối mặt với Tester cũng làm cho buổi làm việc “bớt sóng gió” hơn đúng không nào?
Note
Viết Unit Test càng sớm càng tốt trong giai đoạn viết code và xuyên suốt chu kỳ Phát triển phần mềm.
4. Như nào là một Unit Test có giá trị?
Muốn viết một Unit Test hiệu quả, đem lại nhiều lợi ích nhất cho bản thân cũng như dự án thì cần chú ý những điểm sau:
Unit Test chạy nhanh, sử dụng dữ liệu dễ hiểu, dễ đọc.
Hãy làm cho mỗi test độc lập với những phần khác. Mỗi test chỉ nên liên quan đến một hàm, thủ tục, … Điều này sẽ giúp chúng ta dễ dàng hơn trong quá trình quản lý unit test, cũng như đáp ứng được các thay đổi trong code.
Giả lập tất cả các dịch vụ và trạng thái bên ngoài. Ví dụ: Nếu bạn có làm việc với Database, thì KHÔNG nên sử dụng database thật của ứng dụng để viết Unit Test, bởi vì giá trị trong đó sẽ có thể thay đổi và ảnh hưởng đến các kết quả mong đợi của bạn. Thay vào đó hay tự vào cho mình một fake database, với dự liệu có sẵn và chỉ sử dụng các hàm, thủ tục để làm việc với nó.
Nên đặt tên các đơn vị kiểm thử rõ ràng và nhất quán với nhau để đảm bảo rằng test case dễ đọc. Để bất kỳ ai cũng có thể khởi chạy test case mà không gặp phải trở ngại.
Triển khai Unit Test bao quát hết tất cả các ngoại lệ, các test case.
B. Cách triển khai Unit Test trong Flutter (Còn tiếp)
Các unit tests có thể sẽ phụ thuộc vào các class có fetch data từ web và cơ sở dữ liệu. Điều này bất tiền do một vài lý do:
Việc fetch data từ web và cơ sở dữ liệu về sẽ chậm để thực hiện kiểm tra.
Việc kiểm tra có thể bắt đầu lỗi nếu kết quả từ web và cơ sở dữ liệu là kết quả không được mong đợi. Điều này gọi là “flaky test”.
Khó để kiểm tra tất cả các trường hợp thành công hay lỗi các hành động sử dụng web và cơ sở dữ liệu.
Vì vậy, chúng ta có thể sử dụng “mock” dependencies. Mocks cho phép mô phỏng web và cơ sở dữ liệu và trả về kết quả cụ thể tùy thuộc vào tình huống.
2. Các bước sử dụng Mockito package để kiểm tra.
Các bước bao gồm:
Thêm các package vào pubspec.yaml.
Tạo function để kiểm tra.
Tạo file test với mock http.Client.
Viết các test cho mỗi điều kiện.
Chạy kiểm tra.
2.1. Thêm các package dependencies vào pubspec.yaml.
Thêm các package dependencies: http, mockito, build_runner với version mới nhất ( tham khảo trên trang pub.dev) vào file pubspec.yaml.
2.2. Tạo function để kiểm tra.
Ở ví dụ này, chúng ta tạo một unit test fetchAlbum function để lấy dữ liệu từ internet. Để kiểm tra function này, chúng ta cần 2 thay đổi đó là:
Tham số của function phải có http.Client. Điều này cho phép cung cấp chính xác http.Client phụ thuộc trong trường hợp. Đối với Flutter và dự án web và cơ sở dữ liệu-side cung cấp http.IOClient. Đối với Browse apps, cung cấp http.BrowserClient. Đối với tests, cung cấp mock http.Client.
Sử dụng client để lấy dữ liệu từ internet điều này tốt hơn là phương pháp http.get() trong việc làm giả.
2.3. Tạo file test với mock http.Client.
Tạo file fetch_album_test.dart trong folder test.
Thêm annotation @GenerateMocks([http.Client]) để main function tạo ra MockClient class với mockito. MockClient class được tạo ra sẽ thể hiện http.Client class. Điều này cho phép chúng ta có thể thực hiện fetchAlbum function và trả lại các kết quả khác nhau cho mỗi trường hợp test trong MockClient.
Các mocks được sinh ra sẽ được để trong .
Sau đó, trong command chạy: flutter pub run build_runner build để sinh ra mocks.
2.4. Viết các test cho mỗi điều kiện.
Ở ví dụ này, fetchAlbum() function sẽ trả về:
Nếu http gọi thành công sẽ trả về Album()
Nếu lỗi thì sẽ trả về Exception.
2.5. Chạy file tests.
Chúng ta chỉ việc ấn Run trong file tests trong thư mục test: …/test/fetch_album_test.dart.
3. Tổng kết
Trên đây là một số chia sẻ về việc sử dụng Unit test: Mock dependencies using Mockito cơ bản giúp cho việc kiểm tra các function thao tác với web và cơ sở dữ liệu dễ dàng hơn. Mong rằng qua bài viết sẽ giúp ích cho các bạn phần nào đó.