Tôi không thể chỉ ra một nguồn tài nguyên trực tuyến tốt (các bài viết Wikipedia tiếng Anh về các chủ đề này có xu hướng có thể ứng biến được), nhưng tôi có thể tóm tắt một bài giảng mà tôi đã nghe cũng bao gồm lý thuyết kiểm tra cơ bản.
Chế độ kiểm tra
Có các lớp kiểm tra khác nhau, như kiểm tra đơn vị hoặc kiểm tra tích hợp . Một thử nghiệm đơn vị khẳng định rằng một đoạn mã kết hợp (hàm, lớp, mô-đun) được thực hiện theo cách riêng của nó như mong đợi, trong khi thử nghiệm tích hợp khẳng định rằng nhiều đoạn như vậy hoạt động chính xác với nhau.
Một trường hợp thử nghiệm là một môi trường đã biết trong đó một đoạn mã được thực thi, ví dụ bằng cách sử dụng đầu vào thử nghiệm cụ thể hoặc bằng cách chế nhạo các lớp khác. Hành vi của mã sau đó được so sánh với hành vi dự kiến, ví dụ một giá trị trả về cụ thể.
Một thử nghiệm chỉ có thể chứng minh sự hiện diện của một lỗi, không bao giờ thiếu tất cả các lỗi. Các xét nghiệm đặt một giới hạn trên về tính chính xác của chương trình.
Mã số bảo hiểm
Để xác định số liệu bao phủ mã, mã nguồn có thể được dịch sang biểu đồ luồng điều khiển trong đó mỗi nút chứa một đoạn tuyến tính của mã. Điều khiển luồng giữa các nút này chỉ ở cuối mỗi khối và luôn luôn có điều kiện (nếu có điều kiện, thì nút goto A, nút goto B khác). Biểu đồ có một nút bắt đầu và một nút kết thúc.
- Với biểu đồ này, phạm vi câu lệnh là tỷ lệ của tất cả các nút được truy cập cho tất cả các nút. Bảo hiểm tuyên bố đầy đủ không đủ để thử nghiệm kỹ lưỡng.
- Phạm vi chi nhánh là tỷ lệ của tất cả các cạnh được truy cập giữa các nút trong CFG so với tất cả các cạnh. Điều này không đủ kiểm tra các vòng lặp.
- Độ bao phủ của đường dẫn là tỷ lệ của tất cả các đường dẫn được truy cập cho tất cả các đường dẫn, trong đó một đường dẫn là bất kỳ chuỗi các cạnh nào từ đầu đến cuối nút. Vấn đề là với các vòng lặp, có thể có vô số đường dẫn, do đó, phạm vi bảo hiểm đường dẫn đầy đủ không thể được kiểm tra trên thực tế.
Do đó thường hữu ích để kiểm tra phạm vi điều kiện .
- Trong phạm vi điều kiện đơn giản , mỗi điều kiện nguyên tử là một lần đúng và một lần sai - nhưng điều này không đảm bảo phạm vi bảo hiểm tuyên bố đầy đủ.
- Trong phạm vi bao phủ nhiều điều kiện , các điều kiện nguyên tử đã thực hiện trên tất cả các kết hợp
true
và false
. Điều này ngụ ý bảo hiểm chi nhánh đầy đủ, nhưng khá tốn kém. Chương trình có thể có các ràng buộc bổ sung loại trừ các kết hợp nhất định. Kỹ thuật này là tốt để có được phạm vi bảo hiểm chi nhánh, có thể tìm thấy mã chết, nhưng không thể tìm thấy lỗi xuất phát từ điều kiện sai .
- Trong phạm vi bảo hiểm nhiều điều kiện tối thiểu , mỗi điều kiện nguyên tử và hỗn hợp là một lần đúng và sai. Nó vẫn ngụ ý bảo hiểm chi nhánh đầy đủ. Nó là một tập hợp con của bảo hiểm nhiều điều kiện, nhưng yêu cầu ít trường hợp thử nghiệm hơn.
Khi xây dựng đầu vào kiểm tra bằng cách sử dụng phạm vi điều kiện, thì cần tính đến đoản mạch. Ví dụ,
function foo(A, B) {
if (A && B) x()
else y()
}
cần phải được thử nghiệm với foo(false, whatever)
, foo(true, false)
và foo(true, true)
cho đầy đủ bảo hiểm tình trạng nhiều tối thiểu.
Nếu bạn có các đối tượng có thể ở nhiều trạng thái, thì việc kiểm tra tất cả các chuyển trạng thái tương tự với các luồng điều khiển có vẻ hợp lý.
Có một số số liệu bảo hiểm phức tạp hơn, nhưng chúng thường tương tự như các số liệu được trình bày ở đây.
Đây là các phương pháp thử nghiệm hộp trắng , và có thể được tự động hóa một phần. Lưu ý rằng một bộ kiểm tra đơn vị nên đặt mục tiêu có độ bao phủ mã cao theo bất kỳ số liệu được chọn nào, nhưng 100% không phải lúc nào cũng có thể. Đặc biệt khó kiểm tra xử lý ngoại lệ, trong đó các lỗi phải được đưa vào các vị trí cụ thể.
Kiểm tra chức năng
Sau đó, có các kiểm tra chức năng xác nhận rằng mã tuân thủ thông số kỹ thuật bằng cách xem việc thực hiện dưới dạng hộp đen. Các bài kiểm tra như vậy rất hữu ích cho các bài kiểm tra đơn vị và kiểm tra tích hợp như nhau. Vì không thể kiểm tra tất cả dữ liệu đầu vào có thể (ví dụ: kiểm tra độ dài chuỗi với tất cả các chuỗi có thể), nên rất hữu ích khi nhóm đầu vào (và đầu ra) thành các lớp tương đương - nếu length("foo")
đúng, foo("bar")
cũng có khả năng hoạt động tốt. Đối với mỗi sự kết hợp có thể có giữa các lớp tương đương đầu vào và đầu ra, ít nhất một đầu vào đại diện được chọn và kiểm tra.
Một bài kiểm tra bổ sung
- trường hợp cạnh
length("")
, foo("x")
, length(longer_than_INT_MAX)
,
- các giá trị được cho phép bởi ngôn ngữ, nhưng không phải bởi hợp đồng của chức năng
length(null)
và
- các dữ liệu rác càng tốt
length("null byte in \x00 the middle")
...
Với số, điều này có nghĩa là thử nghiệm 0, ±1, ±x, MAX, MIN, ±∞, NaN
và với phép so sánh dấu phẩy động kiểm tra hai phao lân cận. Ngoài ra, các giá trị thử nghiệm ngẫu nhiên có thể được chọn từ các lớp tương đương. Để dễ dàng gỡ lỗi, cần ghi lại hạt giống được sử dụng
Kiểm tra phi chức năng: Kiểm tra tải trọng, kiểm tra căng thẳng
Một phần mềm có các yêu cầu phi chức năng, cũng phải được kiểm tra. Chúng bao gồm kiểm tra tại các ranh giới xác định (kiểm tra tải) và vượt ra ngoài chúng (kiểm tra căng thẳng). Đối với một trò chơi trên máy tính, điều này có thể khẳng định số lượng khung hình tối thiểu mỗi giây trong một thử nghiệm tải. Một trang web có thể được kiểm tra căng thẳng để quan sát thời gian phản hồi khi số lượng khách truy cập dự kiến gấp đôi so với các máy chủ. Các thử nghiệm như vậy không chỉ phù hợp với toàn bộ hệ thống mà còn cho các thực thể đơn lẻ - làm thế nào để một bảng băm xuống cấp với một triệu mục?
Các loại thử nghiệm khác là thử nghiệm toàn hệ thống trong đó các kịch bản được mô phỏng hoặc thử nghiệm chấp nhận để chứng minh rằng hợp đồng phát triển đã được thực hiện.
Phương pháp không thử nghiệm
Nhận xét
Có những kỹ thuật không thử nghiệm có thể được sử dụng để đảm bảo chất lượng. Ví dụ là các hướng dẫn, đánh giá mã chính thức hoặc lập trình cặp. Mặc dù một số bộ phận có thể được tự động hóa (ví dụ: bằng cách sử dụng linters), nhưng những thứ này thường tốn nhiều thời gian. Tuy nhiên, đánh giá mã bởi các lập trình viên có kinh nghiệm có tỷ lệ phát hiện lỗi cao và đặc biệt có giá trị trong quá trình thiết kế, nơi không thể kiểm tra tự động.
Khi đánh giá mã rất tuyệt vời, tại sao chúng ta vẫn viết bài kiểm tra? Ưu điểm lớn của các bộ kiểm thử là chúng có thể chạy (phần lớn) tự động và rất hữu ích cho các bài kiểm tra hồi quy .
Xác minh chính thức
Xác minh chính thức đi và chứng minh các thuộc tính nhất định của mã. Xác minh thủ công chủ yếu là khả thi cho các phần quan trọng, ít hơn cho toàn bộ chương trình. Bằng chứng đặt một giới hạn thấp hơn về tính đúng đắn của chương trình. Bằng chứng có thể được tự động hóa ở một mức độ nhất định, ví dụ thông qua trình kiểm tra kiểu tĩnh.
Một số bất biến có thể được kiểm tra rõ ràng bằng cách sử dụng các assert
câu lệnh.
Tất cả các kỹ thuật này có vị trí của chúng, và là bổ sung. TDD viết các bài kiểm tra chức năng lên phía trước, nhưng các bài kiểm tra có thể được đánh giá bằng các số liệu bảo hiểm của chúng sau khi mã được thực thi.
Viết mã kiểm tra có nghĩa là viết các đơn vị mã nhỏ có thể được kiểm tra riêng (các hàm trợ giúp với độ chi tiết phù hợp, nguyên tắc trách nhiệm duy nhất). Càng ít đối số, mỗi hàm càng tốt. Mã như vậy cũng cho vay để chèn các đối tượng giả, ví dụ như thông qua tiêm phụ thuộc.
double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }
mà tôi đã học được từ giáo viên toán của mình . Mã này có chính xác một lỗ , không thể tự động phát hiện được từ kiểm tra hộp đen. Trong môn Toán không có lỗ như vậy. Trong tính toán, bạn được phép đóng lỗ nếu giới hạn một phía bằng nhau.