About: Ph.D. Research Scientist working on on #NLProc #ComputerVision #MedicalImageProcessing #ComputationalAdvertising
Joined:
Oct 6, 2025
Testable Dotfiles Management: Building Development Environment with Chezmoi
Publish Date: Oct 6
0 0
This article explains an approach to dotfiles management that emphasizes testability, using the author's dotfiles repository shunk031/dotfiles as a case study.
💻 My dotfiles powered by chezmoi; Primarily built with Rust-based applications (sheldon/startship/mise etc.)
📂 dotfiles
🗿 Overview
This dotfiles repository is managed with chezmoi🏠, a great dotfiles manager
The setup scripts are aimed for MacOS, Ubuntu Desktop, and Ubuntu Server. The first two (MacOS/Ubuntu Desktop) include settings for client machines and the latter one (Ubuntu Server) for server machines.
The dotfiles refer to configuration files that start with a "." (dot) such as .bashrc, .vimrc, and .gitconfig. In recent years, dotfiles repositories that manage these files using Git repositories have become widely popular among developers.
A curated list of dotfiles resources. Inspired by the awesome list thing
Note that some articles or tools may look old or old-fashioned, but this usually means they're battle-tested and mature
(like dotfiles themselves). Feel free to propose new articles, projects or tools!
The dotfiles repositories often function not just as configuration file management tools, but as automated development environment setup tools that include configuration files, installation scripts, and setup scripts. This enables quick and consistent setup on new machines and environments.
The Problem of Untested Scripts
Most setup and installation scripts included in dotfiles repositories are not tested for proper functionality (which is painful). As a result, various problems can occur when setting up in new environments. Script errors, installation failures of some tools due to dependency issues, and script malfunctions due to OS updates may go unnoticed until actually executed. It's extremely stressful when you get a new computer or environment and excitedly start the setup process, only to have errors occur midway through, preventing the environment setup from completing.
This lack of quality assurance results in what should be automated environment construction consuming a lot of time on manual problem-solving and debugging.
My Repository's Approach: Testable Configuration
To solve the above problems, my dotfiles repository builds an architecture that emphasizes testability. Setup scripts are managed as independent files to enable individual testing, quality is ensured through automated testing with Bats, and continuous testing and code coverage measurement are performed in macOS and Ubuntu environments using GitHub Actions.
For managing dotfiles, I've adopted chezmoi. chezmoi is a modern dotfiles management tool with high popularity on GitHub (10,000+⭐️). Written in Go as a single, dependency-free binary, chezmoi is easy to install even on a brand-new, clean environment.
In this way, we aim to achieve reliable dotfiles management by ensuring script quality through testable configuration and flexibly managing environment-specific settings through chezmoi's template functionality.
Architecture Design: Testable Configuration
Repository Structure
My repository is broadly divided into three directories: home/, install/, and tests/, managing dotfiles, environment setup scripts, and automated tests independently.
The core of this architecture lies in "separation of concerns" and "maximizing testability". Traditional dotfiles repositories mix configuration files and setup scripts, making testing difficult, but this configuration clearly separates each element.
The install/ directory: Easy Unit Testing Through Script Separation
By making setup scripts independent from chezmoi, individual testing becomes possible.
install/common/rust.sh: Installation of tools used commonly across machines (e.g., Rust)
Platform-specific configuration separates OS-specific logic, allowing each to be tested independently. Each script follows the single responsibility principle, handling only the installation of specific tools or packages.
The home/ directory: chezmoi Templates and dotfiles
These are the actual dotfiles under chezmoi management. They follow chezmoi's unique file naming conventions (dot_ prefix etc.) and utilize template functionality. This repository specifies home as the source directory using the .chezmoiroot file2.
It's independent from the scripts in the install/ directory, separating configuration file placement and environment construction.
The tests/ directory: Automated Testing with Bats
I use Bash Automated Testing System (Bats) to test scripts in the install/ directory. The test directories and files are configured to be consistent with the script.
Each test file verifies the script's behavior and confirms that expected results (package installation, configuration file generation, etc.) are obtained.
Test & CI/CD Strategy
This repository adopts a test strategy based on the fundamental policy of "continuous verification". We can verify that scripts in the install/ directory work correctly in various environments and discover problems in advance to prevent failures during actual environment construction.
Unit Test Implementation with Bats
My repository adopts Bash Automated Testing System (Bats) to verify the behavior of each installation script. Bats is a testing framework specifically for shell scripts that allows writing tests with simple syntax.
#!/usr/bin/env bats
@test "brew installation script exists"{[-f"install/macos/common/brew.sh"]}
@test "brew installation script is executable"{[-x"install/macos/common/brew.sh"]}
@test "brew installation script runs without errors"{
run bash install/macos/common/brew.sh
["$status"-eq 0 ]}
@test "brew command is available after installation"{
run command-v brew
["$status"-eq 0 ]}
Each test progressively verifies script existence, executable permissions, actual execution, and expected results (command availability, etc.).
Comprehensive Verification with GitHub Actions
My repository uses GitHub Actions for multi-stage verification. In addition to unit tests, the workflow regularly executes actual end-to-end setup to achieve comprehensive quality assurance.
Unit Test Execution
My repository runs automated tests in macOS and Ubuntu environments to detect platform-specific issues early.
More importantly, we should verify in environments identical to the actual user experience. This repository's workflow automatically executes the setup process using setup.sh on macOS and Ubuntu runners every Friday. This script wraps the chezmoi environment construction one-liner mentioned earlier.
This regular execution continuously monitors the impact of external dependency changes, OS updates, and package manager changes on environment construction, ensuring reliability when actual users execute the setup.
Code Coverage Measurement and Codecov Integration
My repository measures shell script code coverage using kcov and visualizes it with Codecov. This helps identify untested code paths and improve testing. Actual measurement uses scripts/run_unit_test.sh.
Code coverage tool for compiled programs, Python and Bash which uses debugging information to collect and report data without special compilation options
kcov
Kcov is a FreeBSD/Linux/Mac OS code coverage tester for compiled languages, Python
and Bash. Kcov was originally a fork of Bcov, but has
since evolved to support a large feature set in addition to that of Bcov.
Kcov, like Bcov, uses DWARF debugging information for compiled programs to
make it possible to collect coverage information without special compiler
switches.
kcov /path/to/outdir executable [args for the executable]
/path/to/outdir will contain lcov-style HTML output generated
continuously while the application runs. Kcov will also write cobertura-
compatible XML output and generic JSON coverage information and can easily
be…
Easily upload coverage reports to Codecov from GitHub Actions
v5 Release
v5 of the Codecov GitHub Action will use the Codecov Wrapper to encapsulate the CLI. This will help ensure that the Action gets updates quicker.
Migration Guide
The v5 release also coincides with the opt-out feature for tokens for public repositories. In the Global Upload Token section of the settings page of an organization in codecov.io, you can set the ability for Codecov to receive a coverage reports from any source. This will allow contributors or other members of a repository to upload without needing access to the Codecov token. For more details see how to upload without a token.
Warning
The following arguments have been changed
file (this has been deprecated in favor of files)
plugin (this has been deprecated in favor of plugins)
To continuously monitor shell startup performance after dotfiles application and detect the impact of configuration changes early, I've automated benchmark measurement in the workflow of GitHub Actions.
This implementation references the following article using benchmark-action/github-action-benchmark. The GitHub Actions workflow measures both initial shell startup time and average startup time (measured 10 times) to quantify the impact of dotfiles configuration on shell startup.
GitHub Action for continuous benchmarking to keep performance
GitHub Action for Continuous Benchmarking
This repository provides a GitHub Action for continuous benchmarking
If your project has some benchmark suites, this action collects data from the benchmark outputs
and monitor the results on GitHub Actions workflow.
This action can store collected benchmark results in GitHub pages branch and provide
a chart view. Benchmark results are visualized on the GitHub pages of your project.
This action can detect possible performance regressions by comparing benchmark results. When
benchmark results get worse than previous exceeding the specified threshold, it can raise an alert
via commit comment or workflow failure.
This action currently supports the following tools:
Measurement results are published on GitHub Pages, achieving continuous performance monitoring. We can numerically confirm the impact of adding new plugins or configurations on shell startup time, preventing performance degradation before it occurs.
Structure and Implementation Examples of Setup Scripts
Scripts in the install/ directory are designed following the single responsibility principle. Each script handles only the installation and configuration of specific tools, creating an independently testable structure.
As a basic script structure, OS-specific processing is separated into different files and implemented as platform-specific scripts. All installation scripts follow the following common pattern. For shell script writing practices, Minimal safe Bash script template is helpful.
#!/usr/bin/env bashset-Eeuo pipefail
# Debug mode settingif["${DOTFILES_DEBUG:-}"];then
set-xfi# Tool-specific functionsfunction is_tool_exists(){command-v tool_name &>/dev/null
}function install_tool(){if! is_tool_exists;then# Platform-specific installation process# macOS: brew install tool_name# Ubuntu: sudo apt-get install -y tool_namefi}# Main processfunction main(){
install_tool
# Execute additional configuration process if needed}if[["${BASH_SOURCE[0]}"=="${0}"]];then
main
fi
The conditional statement if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then executes the main function only when the script is directly executed. When a script is loaded from another file using the source command (e.g., when calling functions from test files), only function definitions are loaded and main is not executed3. This allows the same script to be used both for "execution" and "library" purposes, greatly improving testability.
For example, Homebrew installation is separated into install/macos/common/brew.sh, and chezmoi Ubuntu installation is in install/ubuntu/common/chezmoi.sh. This structure achieves platform optimization and testability.
Development and Maintenance Flow
New Application Addition Procedure
(1). Create installation script
# Create install/macos/common/new_tool.sh# Implement following the basic structure above
(2). Create test file
# Create tests/install/macos/common/new_tool.bats
@test "new_tool installation script exists"{[-f"install/macos/common/new_tool.sh"]}
@test "new_tool can be installed"{
run bash install/macos/common/new_tool.sh
["$status"-eq 0 ]}
(3). Run local tests
bats tests/install/macos/common/new_tool.bats
Test-Driven Development Process
Development always proceeds test-first.
Create test cases: First write expected behavior as tests
Minimal implementation: Implement minimal script that passes tests
Refactoring: Improve code while maintaining behavior
Integration testing: Verify operation in CI environment
This operational flow allows continuous improvement of dotfiles while maintaining quality and maintainability. Each change is necessarily covered by tests and verified in the CI pipeline, minimizing the risk of problems occurring in actual environments.
Conclusion
This article explained the approach of "testable dotfiles management" combining chezmoi and test-driven development. We presented a comprehensive solution to the fundamental problem that traditional dotfiles repositories face: "not knowing if setup scripts work correctly until execution". Specifically, this was an approach combining unit testing with Bats, continuous verification with GitHub Actions, and regular execution of actual end-to-end setup. Please consider incorporating testability into your own dotfiles management to achieve reliable development environment construction.
💻 My dotfiles powered by chezmoi; Primarily built with Rust-based applications (sheldon/startship/mise etc.)
📂 dotfiles
🗿 Overview
This dotfiles repository is managed with chezmoi🏠, a great dotfiles manager
The setup scripts are aimed for MacOS, Ubuntu Desktop, and Ubuntu Server. The first two (MacOS/Ubuntu Desktop) include settings for client machines and the latter one (Ubuntu Server) for server machines.