Quá trình phân tích dữ liệu kiểm thử trong RobotFramework

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_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:

  1. TestCaseFile được tạo với 4 bảng rỗng
  2. FileContentLoader chọn TxtReader dựa trên phần mở rộng
  3. TxtReader đọc từng dòng, phát hiện header bắt đầu bằng *
  4. begin_section xác định bảng đích qua _table_map
  5. Handler tương ứng (ConfigTableHandler, TestTableHandler,...) được gán
  6. Các dòng dữ liệu tiếp theo được append và xử lý bởi handler
  7. 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ộ.

Thẻ: robotframework test-automation python-parsing AST interpreter-design

Đăng vào ngày 4 tháng 8 lúc 12:06