RobotFramework thực hiện chuyển đổi từ tệp văn bản (.txt hoặc .robot) sang cấu trúc dữ liệu nội bộ thông qua một quy trình phân tích phân tầng. Dưới đây là phân tích chi tiết cơ chế này.
Điểm khởi đầu: Hàm phân tích
Quá trình bắt đầu từ hàm _parse, nơi gọi đến TestData - một factory function (không phải class như tên gợi ý):
def _analyze(self, file_path):
try:
return TestData(
source=abspath(file_path),
include_suites=self.suite_filter,
warn_on_skipped=self.skip_warning
)
except DataError, error:
raise DataError("Phân tích '%s' thất bại: %s" % (file_path, unicode(error)))
Hàm TestData phân nhánh dựa trên loại nguồn đầu vào:
def TestData(parent=None, source=None, include_suites=None, warn_on_skipped=False):
if os.path.isdir(source):
return TestDataDirectory(parent, source).populate(
include_suites, warn_on_skipped
)
return TestCaseFile(parent, source).populate()
Như vậy framework hỗ trợ cả tệp đơn lẻ (test suite) và thư mục chứa nhiều tệp.
Cấu trúc TestCaseFile
Khi nguồn là tệp, TestCaseFile được khởi tạo với bốn thành phần chính:
class TestCaseFile(_TestData):
def __init__(self, parent=None, source=None):
self.directory = os.path.dirname(source) if source else None
self.config_table = TestCaseFileSettingTable(self)
self.var_table = VariableTable(self)
self.test_table = TestCaseTable(self)
self.kw_table = KeywordTable(self)
_TestData.__init__(self, parent, source)
Bốn bảng này kế thừa từ _Table, với TestCaseFileSettingTable có thêm kế thừa từ _SettingTable và _WithSettings. Mỗi bảng chứa các thuộc tính như documentation, fixture_setup, fixture_teardown, tag_list, v.v.
Quá trình populate dữ liệu
Phương thức populate điều phối toàn bộ quá trình phân tích:
def populate(self):
FileContentLoader(self).load(self.source)
self._verify()
return self
Lớp FileContentLoader (tên gốc FromFilePopulator) chứa logic phân tích chính:
class FileContentLoader(object):
_handlers = {
'setting': ConfigTableHandler,
'variable': VarTableHandler,
'test case': TestTableHandler,
'keyword': KeywordTableHandler
}
def __init__(self, data_container):
self._container = data_container
self._handler = EmptyHandler()
self._current_dir = self._normalize_path(data_container.directory)
def _normalize_path(self, path):
return path.replace('\\','\\\\') if path else None
def load(self, file_path):
LOGGER.info("Đang phân tích tệp '%s'." % file_path)
stream = self._open(file_path)
try:
self._select_reader(file_path).read(stream, self)
except:
raise DataError(get_error_message())
finally:
stream.close()
Chọn bộ đọc theo định dạng
Framework hỗ trợ nhiều định dạng thông qua registry:
FORMAT_READERS = {
'html': HtmlReader, 'htm': HtmlReader, 'xhtml': HtmlReader,
'tsv': TsvReader, 'rst': RestReader, 'rest': RestReader,
'txt': TxtReader, 'robot': TxtReader
}
Phương thức chọn reader:
def _select_reader(self, path):
ext = os.path.splitext(path.lower())[-1][1:]
try:
return FORMAT_READERS[ext]()
except KeyError:
raise DataError("Định dạng tệp không được hỗ trợ '%s'." % ext)
Xử lý nội dung tệp
Với tệp .txt/.robot, TxtReader (kế thừa TsvReader) thực hiện đọc:
class TsvReader(object):
NBSP = u'\xA0'
def read(self, input_stream, loader):
active = False
for line in Utf8Reader(input_stream).readlines():
cleaned = self._clean_line(line)
cells = [self._process_cell(c) for c in self._split(cleaned)]
if cells and cells[0].strip().startswith('*') and \
loader.begin_section([c.replace('*', '') for c in cells]):
active = True
elif active:
loader.append(cells)
loader.finish()
Lớp Utf8Reader đảm bảo xử lý đúng encoding. Phương thức begin_section xác định bảng nào sẽ nhận dữ liệu tiếp theo.
Phân phối dữ liệu vào bảng
Phương thức begin_section của FileContentLoader:
def begin_section(self, header_cells):
self._handler.process()
table = self._container.init_table(RowData(header_cells).values)
self._handler = self._handlers[table.category](table) \
if table else EmptyHandler()
return bool(self._handler)
Lớp RowData chuẩn hóa dữ liệu dòng, loại bỏ comment (bắt đầu bằng #). Phương thức init_table được kế thừa từ _TestData:
def init_table(self, header_row):
try:
target = self._table_map[header_row[0]]
except (KeyError, IndexError):
return None
if not self._allow_table(target):
return None
target.set_header(header_row)
return target
Bản đồ bảng được xây dựng từ các tên hợp lệ:
class _TestData(object):
_config_names = 'Setting', 'Settings', 'Metadata'
_var_names = 'Variable', 'Variables'
_test_names = 'Test Case', 'Test Cases'
_kw_names = 'Keyword', 'Keywords', 'User Keyword', 'User Keywords'
def __init__(self, parent=None, source=None):
self.parent = parent
self.source = utils.abspath(source) if source else None
self.sub_items = []
self._table_map = utils.NormalizedDict(self._build_map())
def _build_map(self):
mappings = [
(self._config_names, self.config_table),
(self._var_names, self.var_table),
(self._test_names, self.test_table),
(self._kw_names, self.kw_table)
]
for names, table in mappings:
for name in names:
yield name, table
Luồng dữ liệu hoàn chỉnh
Tóm tắt quy trình:
- TestCaseFile được tạo với 4 bảng rỗng
- FileContentLoader chọn TxtReader dựa trên phần mở rộng
- TxtReader đọc từng dòng, phát hiện header bắt đầu bằng *
- begin_section xác định bảng đích qua _table_map
- Handler tương ứng (ConfigTableHandler, TestTableHandler,...) được gán
- Các dòng dữ liệu tiếp theo được append và xử lý bởi handler
- Quá trình lặp lại cho đến hết tệp
Cơ chế này cho phép RobotFramework linh hoạt hỗ trợ nhiều định dạng trong khi duy trì cấu trúc dữ liệu thống nhất nội bộ.