Hướng dẫn python mutation testing pytest - thử nghiệm đột biến của trăn pytest

Nội phân Chính showShow

  • Cài đặt và chạy
  • Danh sách trắng
  • Ví dụ đột biến
  • Cấu hình và danh sách trắng tiên tiến
  • Chọn các bài kiểm tra để chạy công
  • Lựa chọn dựa trên bố cục nguồn và thử nghiệm
  • Lựa chọn dựa trên nhập khẩu
  • Lựa chọn dựa trên bối cảnh bảo hiểm
  • Làm cho mọi thứ trở nên mạnh mẽ hơn
  • Hỗ trợ JUnit XML

Mutmut là một hệ thống thử nghiệm đột biến cho Python, tập trung mạnh vào việc dễ sử dụng. Nếu bạn không biết thử nghiệm đột biến nào là hãy thử bắt đầu với bài viết này.

Một số tính năng nổi bật:

  • Các đột biến được tìm thấy có thể được áp dụng trên đĩa với một lệnh đơn giản giúp nó rất dễ làm việc với kết quả
  • Nhớ công việc đã được thực hiện, vì vậy bạn có thể làm việc tăng dần
  • Hỗ trợ tất cả các vận động viên kiểm tra (vì Mutmut chỉ cần mã thoát khỏi lệnh thử nghiệm)
  • Nếu bạn sử dụng người chạy thử nghiệm Hammett, bạn có thể đi cực kỳ nhanh! Có xử lý đặc biệt cho người chạy này có một số kết quả khá ấn tượng.
  • Có thể sử dụng dữ liệu bảo hiểm chỉ để kiểm tra đột biến trên các dòng được bảo hiểm
  • Trận chiến được thử nghiệm trên các thư viện thực của nhiều công ty

Nếu bạn cần chạy Mutmut trên cơ sở mã Python 2, hãy sử dụng Mutmut

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
4. Mutmut
[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
5 là phiên bản cuối cùng hỗ trợ Python
[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
6,
[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
7 và
[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
8.

Cài đặt và chạy

Bạn có thể bắt đầu với một đơn giản:

pip install mutmut
mutmut run

Điều này theo mặc định sẽ chạy pytest (hoặc không nhất định nếu pytest không có sẵn) trong các bài kiểm tra trong thư mục thử nghiệm của các thử nghiệm hoặc hoặc thử nghiệm và nó sẽ cố gắng tìm ra mã để biến đổi ở đâu. Chạy

Đối với các cờ có sẵn, để sử dụng các vận động viên khác, v.v ... Cách được đề xuất để sử dụng Mutmut nếu mặc định không có hoạt động cho bạn là thêm một khối trong

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
9. Sau đó, khi bạn quay lại Mutmut vài tuần sau đó, bạn không phải tìm ra cờ một lần nữa, chỉ cần chạy
[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
0 và nó hoạt động. Như thế này:

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct

Để sử dụng nhiều đường dẫn trong tùy chọn

[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
1 hoặc
[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
2, hãy sử dụng danh sách phân tách dấu phẩy hoặc đại tràng. Ví dụ:

[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/

Bạn có thể dừng đột biến chạy bất cứ lúc nào và Mutmut sẽ khởi động lại nơi bạn rời đi. Nó cũng đủ thông minh để chỉ kiểm tra lại các đột biến còn sống khi bộ thử nghiệm thay đổi.

Để in kết quả chạy

[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
3. Nó sẽ cung cấp cho bạn một danh sách các đột biến được nhóm theo tệp. Bây giờ bạn có thể xem xét một đột biến cụ thể khác với
[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
4, tất cả các đột biến cho một tệp cụ thể với
[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
5 hoặc tất cả các đột biến với
[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
6.

Bạn cũng có thể viết một đột biến cho đĩa với

[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
7. Bạn thực sự nên có tập tin bạn đột biến trong kiểm soát mã nguồn và cam kết trước khi bạn áp dụng một đột biến!REALLY have the file you mutate under source code control and committed before you apply a mutant!

Danh sách trắng

Bạn có thể đánh dấu các dòng như thế này:

some_code_here()  # pragma: no mutate

để dừng đột biến trên những dòng đó. Một số trường hợp chúng tôi đã tìm thấy nơi bạn cần để danh sách trắng là:

  • Chuỗi phiên bản trên thư viện của bạn. Bạn thực sự không nên có một bài kiểm tra cho điều này: P
  • Tối ưu hóa phá vỡ thay vì tiếp tục. Mã này chạy tốt khi đột phá đột phá để tiếp tục, nhưng nó chậm hơn.

Xem thêm Cấu hình và danh sách trắng nâng cao

Ví dụ đột biến

  • Các chữ số nguyên được thay đổi bằng cách thêm 1. Vì vậy, 0 trở thành 1, 5 trở thành 6, v.v.
  • [mutmut]
    paths_to_mutate=src/,src2/
    tests_dir=tests/:tests2/
    
    8 được thay đổi thành
    [mutmut]
    paths_to_mutate=src/,src2/
    tests_dir=tests/:tests2/
    
    9
  • Phá vỡ được thay đổi để tiếp tục và ngược lại

Nói chung, ý tưởng là các đột biến nên tinh tế nhất có thể. Xem

some_code_here()  # pragma: no mutate
0 để biết danh sách đầy đủ.

Quy trình làm việc

Phần này mô tả cách làm việc với Mutmut để tăng cường bộ thử nghiệm của bạn.

  1. Chạy Mutmut với
    [mutmut]
    paths_to_mutate=src/,src2/
    tests_dir=tests/:tests2/
    
    0. Một cuộc chạy đầy đủ được ưa thích nhưng nếu bạn chỉ bắt đầu, bạn có thể thoát ở giữa và bắt đầu làm việc với những gì bạn đã tìm thấy cho đến nay.
  2. Hiển thị các đột biến với
    some_code_here()  # pragma: no mutate
    
    2
  3. Áp dụng một đột biến còn sống cho đĩa chạy
    [mutmut]
    paths_to_mutate=src/,src2/
    tests_dir=tests/:tests2/
    
    7 (thay thế 3 bằng ID đột biến có liên quan từ
    some_code_here()  # pragma: no mutate
    
    2)
  4. Viết một bài kiểm tra mới mà thất bại
  5. Hoàn nguyên đột biến trên đĩa
  6. Chạy lại bài kiểm tra mới để thấy rằng nó hiện đang vượt qua
  7. Quay trở lại điểm 2.

Mutmut giữ một bộ đệm kết quả trong

some_code_here()  # pragma: no mutate
5, vì vậy nếu bạn muốn đảm bảo rằng bạn chạy một cuộc chạy Mutmut đầy đủ, chỉ cần xóa tệp này.

Nếu bạn muốn chạy lại tất cả những người sống sót sau khi thay đổi nhiều mã hoặc thậm chí cấu hình, bạn có thể sử dụng cho ID trong $ (ID kết quả Mutmut còn tồn tại); làm Mutmut chạy $ id; thực hiện (cho bash).

Bạn cũng có thể nói với Mutmut chỉ cần kiểm tra một đột biến duy nhất:

Cấu hình và danh sách trắng tiên tiến

Mutmut có một hệ thống cấu hình tiên tiến. Bạn tạo một tệp gọi là

some_code_here()  # pragma: no mutate
6. Bạn có thể xác định hai chức năng ở đó:
some_code_here()  # pragma: no mutate
7 và
some_code_here()  # pragma: no mutate
8.
some_code_here()  # pragma: no mutate
9 được gọi khi Mutmut bắt đầu và
def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
0 được gọi trước khi mỗi đột biến được áp dụng và thử nghiệm. Bạn có thể biến đổi đối tượng
def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
1 khi bạn cần. Bạn có thể sửa đổi lệnh kiểm tra như thế này:

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else

hoặc bỏ qua một đột biến:

def pre_mutation(context):
    if context.filename == 'foo.py':
        context.skip = True

hoặc bỏ qua ghi nhật ký:

def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True

Nhìn vào mã cho lớp

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
2 để biết những gì bạn có thể sửa đổi. Vui lòng mở một vấn đề GitHub nếu bạn cần giúp đỡ.

Cũng có thể vô hiệu hóa đột biến của các loại nút cụ thể bằng cách chuyển tùy chọn

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
3. Nhiều loại có thể được chỉ định bằng cách tách chúng bằng dấu phẩy:

mutmut run --disable-mutation-types=string,decorator

Ngược lại, bạn cũng chỉ có thể chỉ định chỉ chạy các đột biến cụ thể với

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
4. Lưu ý rằng
def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
3 và
def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
4 là độc quyền và không thể kết hợp.

Chọn các bài kiểm tra để chạy công

Nếu bạn có một bộ thử nghiệm lớn hoặc các bài kiểm tra dài, có thể có lợi khi thu hẹp tập hợp các bài kiểm tra để chạy cho mỗi đột biến xuống các bài kiểm tra có cơ hội giết nó. Xác định tập hợp con có liên quan của các bài kiểm tra phụ thuộc vào dự án của bạn, cấu trúc của nó và siêu dữ liệu mà bạn biết về các bài kiểm tra của bạn.

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
7 cung cấp thông tin như tệp cho bối cảnh đột biến và bảo hiểm (nếu được sử dụng với công tắc
def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
8). Bạn có thể đặt
def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
9 trong móc
some_code_here()  # pragma: no mutate
8 của
some_code_here()  # pragma: no mutate
6.
def pre_mutation(context):
    if context.filename == 'foo.py':
        context.skip = True
2 được thiết lập lại sau mỗi đột biến, vì vậy bạn không phải (tái) đặt nó cho mỗi đột biến.

Phần này đưa ra các ví dụ để cho thấy làm thế nào điều này có thể được thực hiện cho một số trường hợp sử dụng cụ thể. Tất cả các ví dụ sử dụng trình chạy thử nghiệm mặc định (

def pre_mutation(context):
    if context.filename == 'foo.py':
        context.skip = True
3).

Lựa chọn dựa trên bố cục nguồn và thử nghiệm

Nếu vị trí của mô -đun thử nghiệm có mối tương quan nghiêm ngặt với bố cục mã nguồn của bạn, bạn chỉ cần xây dựng đường dẫn đến tệp thử nghiệm tương ứng từ

def pre_mutation(context):
    if context.filename == 'foo.py':
        context.skip = True
4. Giả sử bố cục của bạn theo cấu trúc sau đây trong đó tệp thử nghiệm luôn được đặt ngay bên cạnh mã sản xuất:

mypackage
├── production_module.py
├── test_production_module.py
└── subpackage
    ├── submodule.py
    └── test_submodule.py

some_code_here()  # pragma: no mutate
6 của bạn trong trường hợp này sẽ như thế này:

import os.path

def pre_mutation(context):
    dirname, filename = os.path.split(context.filename)
    testfile = "test_" + filename
    context.config.test_command += ' ' + os.path.join(dirname, testfile)

Lựa chọn dựa trên nhập khẩu

Nếu bạn có thể dựa vào cấu trúc thư mục hoặc đặt tên cho các tệp thử nghiệm, bạn có thể cho rằng các thử nghiệm có khả năng tiêu diệt đột biến được đặt trong các tệp thử nghiệm trực tiếp nhập mô -đun bị ảnh hưởng bởi người đột biến. Sử dụng mô -đun

def pre_mutation(context):
    if context.filename == 'foo.py':
        context.skip = True
6 của thư viện tiêu chuẩn Python, bạn có thể sử dụng móc
some_code_here()  # pragma: no mutate
7 để xây dựng bản đồ kiểm tra tệp nhập mô -đun nào, sau đó tra cứu tất cả các tệp kiểm tra nhập mô -đun đột biến và chỉ chạy chúng:

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
0

Lựa chọn dựa trên bối cảnh bảo hiểm

Nếu bạn ghi lại bối cảnh bảo hiểm và sử dụng công tắc

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
8, bạn có thể truy cập dữ liệu bảo hiểm này bên trong móc
some_code_here()  # pragma: no mutate
8 thông qua thuộc tính
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
0. Thuộc tính này là một từ điển trong mẫu
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
1.

Hãy nói rằng bạn đã sử dụng tùy chọn bối cảnh động tích hợp của

def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
2 bằng cách thêm phần sau vào tệp
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
3 của bạn:

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
1

def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
4 sẽ tạo bối cảnh mới cho mỗi chức năng kiểm tra mà bạn chạy ở dạng
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
5. Với
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
6, chúng ta có thể sử dụng công tắc
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
7 để kiểm tra bộ lọc phù hợp với biểu thức đã cho.

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
2

Hãy chú ý rằng định dạng của tên ngữ cảnh khác nhau tùy thuộc vào công cụ bạn sử dụng để tạo bối cảnh. Ví dụ: plugin

def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
8 sử dụng
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
9 làm dấu phân cách giữa mô -đun và chức năng kiểm tra. Hơn nữa, không phải tất cả các công cụ đều có thể chọn chính xác các bối cảnh chính xác.
mutmut run --disable-mutation-types=string,decorator
0 Ví dụ là (tại thời điểm viết) không thể nhận các bài kiểm tra bên trong một lớp khi sử dụng
def pre_mutation(context):
    line = context.current_source_line.strip()
    if line.startswith('log.'):
        context.skip = True
6. Bạn sẽ phải kiểm tra cơ sở dữ liệu
mutmut run --disable-mutation-types=string,decorator
2 của mình bằng cách sử dụng API FAVEAGE.PY để xác định cách bạn có thể trích xuất thông tin chính xác để sử dụng với người chạy thử nghiệm.

Làm cho mọi thứ trở nên mạnh mẽ hơn

Mặc dù có những nỗ lực tốt nhất của bạn trong việc chọn tập hợp con bài kiểm tra phù hợp, nhưng có thể xảy ra là người đột biến tồn tại vì bài kiểm tra có thể giết nó không được đưa vào bộ thử nghiệm. Bạn có thể nói với

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
7 để chạy lại bộ thử nghiệm đầy đủ trong trường hợp đó, để xác minh rằng đột biến này thực sự tồn tại. Bạn có thể làm như vậy bằng cách chuyển tùy chọn
mutmut run --disable-mutation-types=string,decorator
4 cho
[mutmut]
paths_to_mutate=src/,src2/
tests_dir=tests/:tests2/
0. Tùy chọn này được tắt theo mặc định.

Hỗ trợ JUnit XML

Để tích hợp tốt hơn với các hệ thống CI/CD,

def pre_mutation(context):
    context.config.test_command = 'python -m pytest -x ' + something_else
7 hỗ trợ việc tạo báo cáo XML JUNIT (sử dụng https://pypi.org/project/junit-xml/). Tùy chọn này có sẵn bằng cách gọi
mutmut run --disable-mutation-types=string,decorator
7. Để xác định cách đối phó với các đột biến đáng ngờ và chưa được kiểm tra, bạn có thể sử dụng

[mutmut]
paths_to_mutate=src/
backup=False
runner=python -m hammett -x
tests_dir=tests/
dict_synonyms=Struct, NamedStruct
3

Các giá trị có thể cho các chính sách này là:

  • mutmut run --disable-mutation-types=string,decorator
    
    8: Không bao gồm các kết quả trên báo cáo
  • mutmut run --disable-mutation-types=string,decorator
    
    9: Bao gồm người đột biến trên báo cáo khi đã bỏ qua
  • mypackage
    ├── production_module.py
    ├── test_production_module.py
    └── subpackage
        ├── submodule.py
        └── test_submodule.py
    
    0: Bao gồm người đột biến trên báo cáo là lỗi lỗi
  • mypackage
    ├── production_module.py
    ├── test_production_module.py
    └── subpackage
        ├── submodule.py
        └── test_submodule.py
    
    1: Bao gồm người đột biến vào báo cáo là lỗi thất bại

Nếu một đột biến thất bại được đưa vào báo cáo, thì sự khác biệt thống nhất của đột biến cũng sẽ được đưa vào cho mục đích gỡ lỗi.