Bio applied testing cicd
Skill Pavel-Kravchenko/Bioinformatics/Skills/bio-applied-testing-cicd
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.From its SKILL.md
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.
- 4 stars4 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.
SKILL.md
7.7 KB, ~2.0k tokens by cl100k_base, 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
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most docs writing skills give in ~2.0k tokens
Counted across 1,951 of the 3,904 authors here whose files we hold, read 2026-09-06
- Use third-person for skill descriptionsin 54 of 1951, across 35 files
- Start descriptions with Use whenin 43 of 1951, across 29 files
- Run baseline scenarios before writing any skillin 40 of 1951, across 26 files
- Use active voicein 40 of 1951, across 36 files
- Map file responsibilities before defining tasksin 36 of 1951, across 29 files
- Use checkbox syntax for tracking stepsin 35 of 1951, across 27 files
- Ask one question at a timein 35 of 1951
- Offer execution options after saving the planin 33 of 1951, across 24 files
- Include complete code in every stepin 33 of 1951, across 27 files
- Design units with clear boundaries and interfacesin 31 of 1951, across 23 files
- Announce the skill usage at the startin 30 of 1951
- Verify agent compliance after adding the skillin 29 of 1951, across 17 files
Said here and by no other author read
- write a test class for each function
- place shared fixtures in conftest.py
- use pytest.approx for float comparisons
- commit only small synthetic files to tests/data
- pin python versions in CI to match project support
- test edge cases like empty sequences and ambiguous bases
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.