agentsclimarketplace

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

Install
npx -y skills add Pavel-Kravchenko/Bioinformatics --skill bio-applied-testing-cicd

Assembled 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.py fixtures 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

CategoryWhat to test
Edge casesEmpty, single-base, very long sequences
Case handlingLowercase, uppercase, mixed
Ambiguous basesN, R, Y, other IUPAC codes
Coordinates0-based vs 1-based, inclusive vs exclusive
StrandForward, reverse, reverse complement
File formatsMalformed, empty, compressed
NumericFloat 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; use pytest.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.yml that you claim to support in pyproject.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.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.