Bio applied testing cicd
Skill Pavel-Kravchenko/Bioinformatics/Skills/bio-applied-testing-cicd
208 bioinformatics skills for Claude Code — NGS, single-cell, metagenomics, structural biology, algorithms, AI for science
npx -y skills add Pavel-Kravchenko/Bioinformatics --skill bio-applied-testing-cicdAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Write pytest tests/fixtures for bio functions and GitHub Actions CI with pytest-cov, ruff, black, mypy. Use when adding tests to a bio tool, writing conftest.py fixtures, or building tests.yml/lint.yml CI workflows.
SKILL.md
7.7 KB, as published. Nobody here has run it
Testing and CI/CD for Bioinformatics
When to Use
- Adding unit tests to a bioinformatics module (sequence parsing, coordinate math, scoring functions)
- Writing
conftest.pyfixtures for temp FASTA/VCF files or shared test data - Setting up GitHub Actions to run
pytest+ coverage on every push/PR - Adding lint/type-check CI (ruff, black, mypy) alongside tests
- Deciding what edge cases to test for biological data (0-based vs 1-based coords, IUPAC ambiguity codes, strand)
Version Compatibility
pytest ≥7.4, pytest-cov ≥4.1, Python ≥3.10, GitHub Actions actions/checkout@v4, actions/setup-python@v5, codecov/codecov-action@v4.
Prerequisites
pip install pytest pytest-cov ruff black isort mypy
Assumes a package layout with src/<pkg>/ and tests/ (see Project Structure below), and basic familiarity with Python functions/classes.
Goal: Test bioinformatics functions correctly, including the biology-specific edge cases that plain "does it run" tests miss.
Approach: Write the module, then a TestClass per function plus @pytest.mark.parametrize for the same assertion across many sequences.
## bio_utils.py
def gc_content(sequence: str) -> float:
"""Calculate GC content as a percentage (0-100). Empty input -> 0.0."""
if not sequence:
return 0.0
seq = sequence.upper()
gc = seq.count('G') + seq.count('C')
return (gc / len(seq)) * 100
def reverse_complement(sequence: str) -> str:
"""Return the reverse complement of a DNA sequence; unknown bases -> 'N'."""
complement = {'A': 'T', 'T': 'A', 'G': 'C', 'C': 'G', 'N': 'N',
'a': 't', 't': 'a', 'g': 'c', 'c': 'g', 'n': 'n'}
return ''.join(complement.get(base, 'N') for base in reversed(sequence))
def find_motif(sequence: str, motif: str) -> list:
"""Find all 1-based, overlap-inclusive start positions of motif in sequence."""
positions, start = [], 0
while True:
pos = sequence.find(motif, start)
if pos == -1:
break
positions.append(pos + 1) # 1-based
start = pos + 1
return positions
## test_bio_utils.py
import pytest
from bio_utils import gc_content, reverse_complement, find_motif
class TestGCContent:
def test_balanced(self):
assert gc_content("ATGC") == 50.0
def test_empty_sequence(self):
"""Empty sequence must return 0.0, not raise."""
assert gc_content("") == 0.0
def test_lowercase(self):
assert gc_content("atgc") == 50.0
def test_with_n_bases(self):
"""N counts toward length but not toward GC."""
assert gc_content("GCNN") == 50.0
class TestReverseComplement:
def test_palindromic_site(self):
"""EcoRI site GAATTC is its own reverse complement."""
assert reverse_complement("GAATTC") == "GAATTC"
def test_handles_n(self):
assert reverse_complement("ATNG") == "CNAT"
class TestFindMotif:
def test_overlapping_occurrences(self):
"""Overlapping matches must all be reported."""
assert find_motif("AAAA", "AA") == [1, 2, 3]
def test_not_found(self):
assert find_motif("ATGC", "GGG") == []
@pytest.mark.parametrize("seq,expected", [
("GGGG", 100.0), ("AAAA", 0.0), ("ATGC", 50.0), ("GC", 100.0),
])
def test_gc_content_parametrized(seq, expected):
assert gc_content(seq) == expected
Goal: Reuse temp files and biological test data across many tests without duplicating setup.
Approach: Put shared fixtures in conftest.py; use the built-in tmp_path fixture for real files on disk.
## conftest.py
import pytest
@pytest.fixture
def sample_fasta_content():
"""Two-record FASTA string for parser tests."""
return (
">gene1 beta-globin\n"
"ATGGTGCACCTGACTCCTGAGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGGTGAAC\n"
">gene2 alpha-globin\n"
"ATGGTGCTGTCTCCTGCCGACAAGACCAACGTCAAGGCCGCCTGGGGTAAGGTCGGCGCG\n"
)
@pytest.fixture
def sample_fasta_file(tmp_path, sample_fasta_content):
"""Write sample_fasta_content to a real temp file and return its path."""
fasta_path = tmp_path / "test.fasta"
fasta_path.write_text(sample_fasta_content)
return fasta_path
def test_parse_fasta(sample_fasta_content):
from io import StringIO
from Bio import SeqIO
records = list(SeqIO.parse(StringIO(sample_fasta_content), "fasta"))
assert len(records) == 2
assert records[0].id == "gene1"
def test_fasta_file_exists(sample_fasta_file):
assert sample_fasta_file.exists()
assert sample_fasta_file.suffix == ".fasta"
pytest Commands
pytest -v # verbose, one line per test
pytest test_bio_utils.py::TestGCContent # run one class
pytest -x # stop at first failure
pytest -k "gc or reverse" # keyword filter
pytest --cov=bio_utils --cov-report=html # coverage report
GitHub Actions CI
## .github/workflows/tests.yml
name: Tests
on:
push: { branches: [main] }
pull_request: { branches: [main] }
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "${{ matrix.python-version }}" }
- run: pip install pytest pytest-cov && pip install -r requirements.txt
- run: pytest --cov=src --cov-report=xml --cov-report=term-missing
- uses: codecov/codecov-action@v4
with: { file: coverage.xml, fail_ci_if_error: false }
## .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install ruff black isort mypy
- run: black --check src/ tests/
- run: ruff check src/ tests/
- run: mypy src/ --ignore-missing-imports
Project Structure
my_tool/
├── .github/workflows/{tests.yml, lint.yml}
├── src/my_tool/{__init__.py, io.py, analysis.py, utils.py}
├── tests/{conftest.py, test_io.py, test_analysis.py, data/}
├── pyproject.toml
└── requirements.txt
Bioinformatics Testing Checklist
| Category | What to test |
|---|---|
| Edge cases | Empty, single-base, very long sequences |
| Case handling | Lowercase, uppercase, mixed |
| Ambiguous bases | N, R, Y, other IUPAC codes |
| Coordinates | 0-based vs 1-based, inclusive vs exclusive |
| Strand | Forward, reverse, reverse complement |
| File formats | Malformed, empty, compressed |
| Numeric | Float comparisons with tolerance (pytest.approx) |
Pitfalls
- Coordinate systems: BED is 0-based half-open; VCF/GFF is 1-based inclusive — mixing them causes off-by-one variant/interval errors
- Float comparison: never use
==on p-values or scores; usepytest.approx() - Test data size: commit small synthetic files to
tests/data/, never full genomes/BAMs - Fixture scope: default fixture scope is per-function; use
scope="module"only for expensive, read-only setup to avoid state leaking between tests - CI matrix drift: pin the same Python versions in
tests.ymlthat you claim to support inpyproject.toml
See Also
- bio-workflow-management-snakemake-workflows
- bio-workflow-management-nextflow-pipelines
- bio-reporting-automated-qc-reports
- bio-sequence-io-read-sequences