Center of Excellence
Bài
10
Thao
tác trên tập tin văn bản (Text Files)
Kiểm thử phải trở
thành yếu tố bắt buộc trong quá trình phát triển,
và cuộc hôn phối
giữa phát triển và kiểm thử là điểm hội tụ chất
lượng
-- James A. Whittaker
I. Bài toán
Hình
1
II. Giải
pháp
Bước 1. Phát biểu lại yêu cầu bài toán
- Input: tên text file (file name)
- Output: tổng (sum)
Theo kinh nghiệm thiết kế, khi phải làm việc trên dữ liệu lưu trữ, file hoặc cơ sở dữ liệu (database), nhà thiết kế sẽ tạo ra một tầng chuyên quản lý dữ liệu (data layer hoặc data tier), bao gồm bốn thao tác cơ bản, gọi tắt là CRUD (Create – Tạo hoặc Thêm, Read – Đọc, Update – Sửa, Delete – Xóa), cùng một số tính toán đơn giản như đếm (count), tính tổng (sum), và tính trung bình (average). Data tier có một hay nhiều bộ phận chuyên trách làm đại diện để giao tiếp với bên ngoài, gọi là facade.
Với bài toán trên,
ta cần làm việc với text file để tính tổng. Do tính
tổng là một trong những thao tác cơ bản của data tier,
ta tạo một class trong data tier
đại diện cho text file này.
Hình 2
Ta có thể chia test cases thành hai trường hợp: file thực sự có trong đĩa cứng (hard disk) và file không tồn tại do sai tên file hoặc sai đường dẫn. Khi file có trong hard disk, các trường hợp sau có thể xảy ra: file hoàn toàn trống tức không có data; file chứa một hay nhiều số; hoặc file chứa data không phải là số.
sum()
|
||
---|---|---|
No | fileName | sum? |
1
|
manyNumbers.txt
(8.7 7.9 3.0 9.2 12.6)
|
41.4
|
2
|
oneNumber.txt
(8.7)
|
8.7
|
3
|
empty.txt
(file trống, không có data)
|
Không có
|
4
|
invalid.txt
(8.7 abc
7.9 3.0 9.2 12.6)
|
Không có
|
5
|
File không tồn tại
hoặc sai đường dẫn (nonexistence.txt)
|
Không có
|
Bước
4. Phát triển test class
- Project: TextFileOfNumbers
- Package: data
Ta từng bước giải
quyết từng test case. Trước hết là trường hợp file
chứa data hợp lệ.
4.1. Trường hợp
file chứa một hay nhiều số
Ta tạo hai text files
lấy tên là manyNumbers.txt
và oneNumber.txt, đầu
tiên là manyNumbers.txt.
Right-click project, chọn New,
rồi chọn File để
chuyển sang New File
window.
Hình 3
Nhập
tên file vào ô File name:,
rồi click Finish.
Hình 4
Nhập các giá trị
vào file, rồi nhấn Ctrl+S
để lưu. Ta làm tương tự các bước trên cho file
oneNumber.txt.
Đến
đây, ta phát triển test method cho trường hợp này.
package data;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class NumberFacadeTest {
@Test
public void sum() {
assertEquals(41.4, new NumberFacade("manyNumbers.txt").sum(), 0.0);
assertEquals(8.7, new NumberFacade("oneNumber.txt").sum(), 0.0);
}
}
Khi phát triển sum(),
ta sử dụng câu lệnh try-with-resources,
trong đó có Scanner
object để mở file.
package data;
import java.io.File;
import java.util.Scanner;
public class NumberFacade {
private final String fileName;
public NumberFacade(String fileName) {
this.fileName = fileName;
}
public double sum() {
try (Scanner scanner = new Scanner(new File(this.fileName))) {
return ???;
}
}
}
Tuy nhiên, Java buộc ta phải xử
lý ngoại lệ FileNotFoundException
(không tìm thấy file) khi thực hiện việc mở file. Vì vậy
ta bổ sung đoạn throws FileNotFoundException
vào sau dòng khai báo của sum()
method, để cho biết rằng khi không tìm thấy file thì việc
xử lý exception này sẽ được đùn đẩy cho nơi phát ra
lệnh gọi sum().
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class NumberFacade {
private final String fileName;
public NumberFacade(String fileName) {
this.fileName = fileName;
}
public double sum() throws FileNotFoundException {
try (Scanner scanner = new Scanner(new File(this.fileName))) {
return ???;
}
}
}
Một khi đã mở được file,
ta dùng bộ tích lũy acc
(viết tắt của accumulator) để lưu trữ kết quả tính
toán. Ban đầu acc
chưa có gì nên bằng 0.
public double sum() throws FileNotFoundException {
try (Scanner scanner = new Scanner(new File(this.fileName))) {
double acc = 0.0;
return ???;
}
}
Sau
đó ta dùng vòng lặp while
kiểm tra xem trong file có chứa data không (hasNext).
public double sum() throws FileNotFoundException {
try (Scanner scanner = new Scanner(new File(this.fileName))) {
double acc = 0.0;
while (scanner.hasNext())
???
return acc;
}
}
Nếu có thì đọc giá trị đó
ra khỏi file (nextDouble),
cộng dồn vào bộ tích lũy acc,
rồi lặp lại việc kiểm tra và cộng dồn cho đến khi
không còn data nào trong file mà chưa được đọc. Lưu ý
rằng ở đây ta đã sử dụng toán tử +=
để cộng dồn một số thực double
vào bộ tích lũy.
public double sum() throws FileNotFoundException {
try (Scanner scanner = new Scanner(new File(this.fileName))) {
double acc = 0.0;
while (scanner.hasNext())
acc += scanner.nextDouble();
return acc;
}
}
Trước
khi thi hành test class để kiểm thử kết quả, ta cần bổ
sung đoạn throws
FileNotFoundException
vào sau dòng khai báo của test method.
public class NumberFacadeTest {
@Test
public void sum() throws FileNotFoundException {
assertEquals(41.4, new NumberFacade("manyNumbers.txt").sum(), 0.0);
assertEquals(8.7, new NumberFacade("oneNumber.txt").sum(), 0.0);
}
}
4.2. Trường hợp
file không tồn tại hoặc sai đường dẫn
Khi phát triển test
method cho trường hợp này, ta cần nghĩ ra một tên file
rất đặc biệt sao cho file này khó có khả năng tồn tại
trong hard disk, chẳng hạn nonexistence.txt.
public class NumberFacadeTest {
@Test public void sum() throws FileNotFoundException {
assertEquals(41.4, new NumberFacade("manyNumbers.txt").sum(), 0.0);
assertEquals(8.7, new NumberFacade("oneNumber.txt").sum(), 0.0);
}
@Test(expected=NoSuchElementException.class)
public void sumEmptyFile() throws FileNotFoundException {
new NumberFacade("empty.txt").sum();
}
@Test(expected=FileNotFoundException.class)
public void sumNonxistentFile() throws FileNotFoundException {
new NumberFacade("nonexistence.txt").sum();
}
}
4.3. Trường hợp
file trống
public class NumberFacadeTest {
@Test public void sum() throws FileNotFoundException {
assertEquals(41.4, new NumberFacade("manyNumbers.txt").sum(), 0.0);
assertEquals(8.7, new NumberFacade("oneNumber.txt").sum(), 0.0);
}
@Test(expected=NoSuchElementException.class)
public void sumEmptyFile() throws FileNotFoundException {
new NumberFacade("empty.txt").sum();
}
}
Khi
đó, trong NumberFacade, ta bổ sung đoạn code kiểm tra
trường hợp file trống.
public double sum() throws FileNotFoundException {
try (Scanner scanner = new Scanner(new File(this.fileName))) {
if (scanner.hasNext() == false)
throw new NoSuchElementException(this.fileName + " is empty");
double acc = 0.0;
while (scanner.hasNext())
acc += scanner.nextDouble();
return acc;
}
}
4.4. Trường hợp
file chứa data không phải là số
Để
kiểm tra việc thi hành chương trình đối với trường
hợp file chứa data không phải là số, ta tạo thêm text
file invalid.txt và
chèn vào đó chuỗi ký tự abc.
public class NumberFacadeTest {
@Test public void sum() throws FileNotFoundException {
assertEquals(41.4, new NumberFacade("manyNumbers.txt").sum(), 0.0);
assertEquals(8.7, new NumberFacade("oneNumber.txt").sum(), 0.0);
}
@Test(expected=NoSuchElementException.class)
public void sumEmptyFile() throws FileNotFoundException {
new NumberFacade("empty.txt").sum();
}
@Test(expected=FileNotFoundException.class)
public void sumNonxistentFile() throws FileNotFoundException {
new NumberFacade("nonexistence.txt").sum();
}
@Test public void sumInvalidFile() throws FileNotFoundException {
new NumberFacade("invalid.txt").sum();
}
}
Khi
thi hành test class, ta nhận được tín hiệu đỏ và
InputMismatchException (dữ
liệu nhập không khớp với yêu cầu). Lý do là ở vòng
lặp while trong
sum() thuộc
NumberFacade class, ta
đã kiểm tra xem file có chứa data không bằng cách gọi
hasNext(). Vì file
chứa chuỗi “abc”
nên dòng lệnh bên trong while
được thực hiện, tức đọc ra một giá trị qua lệnh
nextDouble() rồi
cộng dồn vào bộ tích lũy. Nhưng giá trị đọc ra không
phải là số nên nextDouble()
buộc phải tung exception.
Để
có được tín hiệu xanh khi thi hành test class, ta bổ sung
expected attribute vào
test method.
@Test(expected=InputMismatchException.class)
public void sumInvalidFile() throws FileNotFoundException {
new NumberFacade("invalid.txt").sum();
}
IV.
Tổng kết
Bài viết đã trình bày việc thiết kế data tier chịu trách nhiệm tương tác với dữ liệu lưu trữ, đồng thời sử dụng cấu trúc try-with-resources kết hợp với vòng lặp while để đọc data trong text file.
V.
Tài liệu tham khảo
- Tony Gaddis (2010) Starting Out with Java From Control Structures Through Objects (4th edition), Pearson Education, Boston. Chương 4.
Không có nhận xét nào:
Đăng nhận xét