Vim script to run the tests for the current file/class/function.
Supports various Python test runners including
- pytest
- unittest autodiscovery
- nose
- django test
- zope.testrunner
Demo:
Contents
I recommend vim-plug:
Plug 'mgedmin/taghelper.vim' Plug 'mgedmin/py-test-runner.vim'
You'll also need taghelper.vim (or the older pythonhelper.vim), and I recommend asyncrun.vim too.
Needs Vim built with Python 3 support. (Python 2 might work too, but I make no promises about it staying working.)
The plugin defines the following commands:
:RunTestUnderCursor
Runs a single test function/method/class/module, depending on where your
cursor is. Can run the test asynchronously if you have asyncrun.vim
installed and have defined the :Make command as documented in the wiki.
:RunTest name-of-test
Runs a single test function/method/class/module.
:RunLastTestAgain
Repeats the last test run.
:CopyTestUnderCursor
Copies the command to run a single test function/method/class/module into the system clipboard, so you can paste it into a terminal window and not block your Vim instance.
If you want some, copy and paste this into your .vimrc:
map <C-F9> :RunTestUnderCursor<CR> map <C-S-F9> :RunLastTestAgain<CR>
If you're running the tests asynchronously it can be helpful to indicate the test status (running/pass/fail) by changing the status line color.
You can define the higlight groups like
hi StatusLineNeutral
\ term=bold,reverse cterm=bold,reverse gui=bold,reverse
hi StatusLineRunning ctermfg=53 guifg=#5f005f
\ term=bold,reverse cterm=bold,reverse gui=bold,reverse
hi StatusLineSuccess ctermfg=22 guifg=#005f00
\ term=bold,reverse cterm=bold,reverse gui=bold,reverse
hi StatusLineFailure ctermfg=52 guifg=#5f0000
\ term=bold,reverse cterm=bold,reverse gui=bold,reverse
hi! link StatusLine StatusLineNeutral
and then :RunTestUnderCursor will detect this and link StatusLine to
StatusLineRunning when the test process starts. It's up to you to define
an asyncrun exit callback to link it to success/failure as appropriate:
fun! OnAsyncRunExit()
if g:asyncrun_status == 'success'
hi! link StatusLine StatusLineSuccess
elseif g:asyncrun_status == 'failure'
hi! link StatusLine StatusLineFailure
endif
redrawstatus!
endf
let g:asyncrun_exit = "call OnAsyncRunExit()"
I'm considering removing the half-finished integration and suggesting you do the initial linking via
augroup AsyncRun au! au User AsyncRunStart hi! link StatusLine StatusLineRunning | redrawstatus! augroup END
This plugin reads ~/.vim/py-test-runner.cfg (default filename, can be
overridden by setting g:pyTestRunnerConfigFile) if it exists. It should be
an INI file like this:
[default]
runner = pytest
[runner:mytestrunner]
command = mytestrunner
filter_for_file = {filename}
filter_for_function = {filename}::{function}
filter_for_class = {filename}::{class}
filter_for_method = {filename}::{class}::{method}
filter_for_doctest = -k {function}
[path:~/src/myproject]
runner = mytestrunner
command = venv/bin/mytestrunner
The [default] section has the following settings:
runner
Specifies the default test runner. If omitted, the default is
pytest. You can use any of the predefined test runners (pytest,unittest,nose,zope, anddjango), or any custom test runner if you have a corresponding[runner:foo]section.This setting can be overridden by
[path:...]sections and manually, if you:call pytestrunner#use(runner)or setg:pyTestRunner.
ignore_functions_and_methods
Specifies a whitespace-separated list of function/method names that should not be considered to be tests.
For example, this is the default ignore list:
[default] ignore_functions_and_methods = __init__ setUp tearDown test_suiteWhen the cursor is inside a function/method with one of these names, it will be ignored (and the scope of the test will be the entire module/class).
This setting can be overridden by
[runner:...]sections, and by[path:...]sections.
The [runner:NAME] sections define/override test runners and have the
following settings:
command
Specifies the main test runner command. This can contain arguments. No shell escaping is done, so be careful!
Examples:
[runner:pytest] command = pytest -ra [runner:tox] command = tox -e py27,py37 --This setting can be overridden by
[path:...]sections and manually, by callingpytestrunner#use(runner, command)or by settingg:pyTestRunnerCommand.The full command is constructed from
commandand the multiplefilter_for_...settings in the following order:
- cd workdir, if workdir is not blank
- command
- filter_for_file, if not blank
- filter_for_directory, if not blank
- filter_for_package, if not blank
- filter_for_module, if not blank
- one of filter_for_function, filter_for_doctest, filer_for_doctest_file, filter_for_class, filter_for_method, whichever is applicable
As a special case, if filter_for_function (or filter_for_doctest, or filter_for_doctest_file, or filter_for_class, or filter_for_method, whichever was picked) mentions the
{filename}placeholder, filter_for_file, filter_for_directory, filter_for_package and filter_for_module will be skipped.
filter_for_file
Specifies how to tell the test runner which test file is interesting.
Example:
[runner:pytest] filter_for_file = {filename}Whether the
{filename}placeholder is replaced with a relative or absolute filename depends on theabsolute_filenamesandrelative_filenamessettings.You will want to specify either
filter_for_fileorfilter_for_module, but not both. (I don't know what will happen if you specify both.)This setting can be overridden by
[path:...]sections.
filter_for_directory
Specifies how to tell the test runner which test directory is interesting.
Example:
[runner:...] filter_for_directory = {directory}Whether the
{directory}placeholder is replaced with a relative or absolute filename depends on theabsolute_filenamesandrelative_filenamessettings.You will want to specify either
filter_for_directoryorfilter_for_filename, but not both. (I don't know what will happen if you specify both.)You will want to specify either
filter_for_directoryorfilter_for_package, but not both. (I don't know what will happen if you specify both.)This setting can be overridden by
[path:...]sections.
filter_for_package
Specifies how to tell the test runner which test package is interesting.
Example:
[runner:zope] filter_for_package = -s {package}The logic that computes Python package names from directory names relies on the presence/absence of
__init__.pyfiles and breaks if you use PEP-420 namespace packages.You will want to specify either
filter_for_packageorfilter_for_filename, but not both. (I don't know what will happen if you specify both.)You will want to specify either
filter_for_packageorfilter_for_directory, but not both. (I don't know what will happen if you specify both.)This setting can be overridden by
[path:...]sections.
filter_for_module
Specifies how to tell the test runner which test module is interesting.
Example:
[runner:zope] filter_for_module = -m {module}The module name is just the filename without the
.pyextension.You will want to specify either
filter_for_moduleorfilter_for_filename, but not both. (I don't know what will happen if you specify both.)This setting can be overridden by
[path:...]sections.
filter_for_function
Specifies how to tell the test runner which test function is interesting.
Filtering by test function requires taghelper.vim to be installed.
Examples:
[runner:zope] filter_for_function = -t {function} [runner:pytest] filter_for_function = {filename}::{function}This setting can be overridden by
[path:...]sections.
filter_for_doctest
Specifies how to tell the test runner which doctest function is interesting.
Filtering by test function requires taghelper.vim to be installed.
Regular functions from doctest functions are distinguished by name (functions starting with
testare assumed to be regular functions).Examples:
[runner:zope] filter_for_doctest = -t {function} [runner:pytest] filter_for_doctest = -k {function} [runner:pytest] filter_for_doctest = {filename}::{full_module}.{function}If this setting is not specified,
filter_for_functionis used instead for doctest functions as well.This setting can be overridden by
[path:...]sections.
filter_for_doctest_file
Specifies how to tell the test runner which doctest file is interesting.
Doctest files are recognized by their extension (.txt, .rst, .test).
Examples:
[runner:pytest] filter_for_doctest_file = -k {function}The filename (without directory) is used as the "function name" in the filter, hence the
{function}in the above example.If this setting is not specified,
filter_for_doctestorfilter_for_functionis used instead.This setting can be overridden by
[path:...]sections.
filter_for_class
Specifies how to tell the test runner which test class is interesting.
Filtering by test class requires pythonhelper.vim to be installed.
Examples:
[runner:zope] filter_for_class = -t {class} [runner:pytest] filter_for_class = {filename}::{class}If this setting is not specified,
filter_for_functionis used instead.This setting can be overridden by
[path:...]sections.
filter_for_method
Specifies how to tell the test runner which test method is interesting.
Filtering by test method requires taghelper.vim to be installed.
Examples:
[runner:zope] filter_for_method = -t '{method} \(.*\.{class}\)' [runner:pytest] filter_for_class = {filename}::{class}::{method} [runner:nose] filter_for_class = {filename}::{class}.{method}If this setting is not specified,
filter_for_functionis used instead and gets the method name (discarding the class name).This setting can be overridden by
[path:...]sections.
absolute_filenames
Set to a true value (
true,yes,1) if you want{filename}and{directory}placeholders to be absolute.This is helpful when the test runner script changes its working directory before it starts looking for files.
Set to a false value (
false,no,0) if you want{filename}and{directory}placeholders to be exactly as they appear in Vim (so they could be absolute or relative, depending on how you opened the file), as long as unlessrelative_filenamesis also set to a false value.Defauls to false. Can be overridden by
[path:...]sections.If both
absolute_filenamesandrelative_filenamesare enabled,absolute_filenamestake precedence.
relative_filenames
Set to a true value (
true,yes,1) if you want{filename}and{directory}placeholders to be made relative.The filenames are made relative to the directory named in the
relative_tosetting, if set, otherwise theworkdirsetting, if set, otherwise to the current working directory.This is helpful when the test runner script changes its working directory before it starts looking for files.
Set to a false value (
false,no,0) if you want{filename}and{directory}placeholders to be exactly as they appear in Vim (so they could be absolute or relative, depending on how you opened the file), as long as unlessrelative_filenamesis also set to a false value.Defauls to false. Can be overridden by
[path:...]sections.If both
absolute_filenamesandrelative_filenamesare enabled,absolute_filenamestake precedence.
relative_to
Set to a directory name when you want to use filenames relative to somewhere that is not the current working directory.
Example:
[path:~/src/project/alembic] runner = pytest command = tox -c alembic/ -- absolute_filenames = false relative_filenames = true relative_to = alembic/Defaults to empty string. Can be overridden by
[path:...]sections.
workdir
Set to a directory name when you want to change the working directory before running the test command.
Example:
[path:~/src/project/alembic] runner = pytest command = tox -- workdir = alembic/This will run
cd alembic/ && tox -- ...Defaults to empty string. Can be overridden by
[path:...]sections.
clipboard_extras
Extra command-line flags to be added when using :CopyTestUnderCursor.
Use this to add colors or progress bars that would otherwise confuse Vim's :make.
These flags are added to the beginning of the command line.
Example:
[runner:zope] clipboard_extras = -pvcThis setting can be overridden by
[path:...]sections.
clipboard_extras_suffix
Extra command-line flags to be added when using :CopyTestUnderCursor.
Use this to add colors or progress bars that would otherwise confuse Vim's :make.
These flags are added to the end of the command line.
No shell escaping is done so you can in fact do something like
[runner:zope] clipboard_extras = 2>&1 | less -Rto pipe the test runner's output to a pager.
This setting can be overridden by
[path:...]sections.
The [path:PATH] sections define overrides for your projects
identified by path names and have the following settings:
runner
Overrides the
runnerfrom the[defaults]section.Example:
[path:~/src/vim-plugins/py-test-checker.vim] command = coverage run -m pytestCan be overridden by setting
g:pyTestRunneror callingpytestrunner#use(runner).
command
Overrides the
commandfrom the[runner:...]section.Example:
[path:~/src/vim-plugins/py-test-checker.vim] command = coverage run -m pytestCan be overridden by setting
g:pyTestRunnerCommandor callingpytestrunner#use(runner, command).
filter_for_file, filter_for_directory, filter_for_package, filter_for_module, filter_for_function, filter_for_doctest, filter_for_doctest_file, filter_for_class, filter_for_method ignore_functions_and_methods
Override the corresponding setting from the
[runner:...]section.You're not expected to ever need this.
absolute_filenames, relative_filenames, relative_to, workdir, clipboard_extras, clipboard_extras_suffix
Override the corresponding setting from the
[runner:...]section.These look like settings it can make sense to override on a per-project basis.
The following global variables are used:
g:pyVimRunCommand (default: "")
Vim command to run an external process (after setting
&makeprg). If blank, the plugin will use:Makeif such a user-defined command exists, otherwise it will use:make.asyncrun.vim recommends defining
command! -bang -nargs=* -complete=file Make AsyncRun -program=make @ <args>so you can run commands in the background.
g:pyTestRunnerConfigFile (default: "")
Config file to read. If blank, the plugin reads ~/.vim/py-test-runner.cfg.
It makes sense to set it if you use NeoVim and want your py-test-runner.cfg in ~/.config/nvim/ instead of the default location.
g:pyTestRunner (default: "")
Test runner to use. If not blank, overrides the
runnersetting in the configuration file.The
:call pytestrunner#use(...)convenience command writes to this variable.
g:pyTestRunnerCommand (default: "")
Test runner command to use. If not blank, overrides the
commandsetting in the configuration file.The
:call pytestrunner#use(...)convenience command writes to this variable.
g:pyTestLastTest (default: "")
This is not a configuration setting, but instead the filter describing the last test executed via :RunTestUnderCursor. It is used by :RunLastTestAgain.
There are several functions that streamline the setup for the most common test runners, left for backwards compatibility:
:call UsePyTestTestRunner("pytest -ra")
Use pytest, which uses commands like
pytest -ra <filename>::<class>::<method>You can optionally specify the main executable, which is helpful if you use multiple virtualenvs, e.g.
:call UsePyTestTestRunner("tox -e py27,py37 --")assuming your tox.ini has
[testenv] commands = pytest {posargs}
:call UsePyTestTestRunner(...)is exactly equivalent to:call pytestrunner#use("pytest", ...)and is provided for backwards compatibility.
:call UseZopeTestRunner("bin/test")
Use the Zope test runner, which uses commands like
bin/test -s <package> -m <module> -t '<method> (class <Class>)'You can optionally specify the main executable or pass additional arguments, e.g.
:call UseZopeTestRunner("venv/bin/zope-testrunner -vv")
:call UseZopeTestRunner(...)is exactly equivalent to:call pytestrunner#use("zope", ...)and is provided for backwards compatibility.
:call UseNoseTestRunner("nosetests")
Use the nose test runner, which uses commands like
nosetests <filename>:<class>.<method>
:call UseNoseTestRunner(...)is exactly equivalent to:call pytestrunner#use("nose", ...)and is provided for backwards compatibility.
:call UseDjangoTestRunner("bin/django test")
Use the Django test runner. Assumes you're using django-nose, which uses commands like
bin/django test <filename>:<class>.<method>You can optionally specify the main executable or pass additional arguments, e.g.
:call UseDjangoTestRunner("python manage.py test") :call UseDjangoTestRunner("venv/bin/django-admin test")
:call UseDjangoTestRunner(...)is exactly equivalent to:call pytestrunner#use("django", ...)and is provided for backwards compatibility.
The following global variables are no longer used:
g:pyTestRunner
This used to define the test runner command, instead of selecting the test runner configuration section. If you keep defining it, you will get errors.
Use g:pyTestRunnerCommand instead.
g:pyTestRunnerTestFiltering
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_function = -t {function}instead.
g:pyTestRunnerDoctestFiltering
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_doctest = -t {function}instead.
g:pyTestRunnerTestFilteringClassAndMethodFormat
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_method = -t {class}.{method}instead.
g:pyTestRunnerTestFilteringBlacklist
This is now completely ignored.
Define a
[default]or[runner:...]or[path:...]section withignore_functions_and_methods = __init__ setUp tearDown test_suiteinstead.
g:pyTestRunnerDirectoryFiltering
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_directory = -s {directory}instead.
g:pyTestRunnerFilenameFiltering
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_file = {filename}instead.
g:pyTestRunnerUseAbsoluteFilenames
This is now completely ignored.
Define a
[runner:...]or[path:...]section withabsolute_filenames = yesinstead.
g:pyTestRunnerPackageFiltering
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_package = -s {package}instead.
g:pyTestRunnerModuleFiltering
This is now completely ignored.
Define a
[runner:...]or[path:...]section withfilter_for_module = -m {module}instead.
g:pyTestRunnerClipboardExtras
This is now completely ignored.
Define a
[runner:...]or[path:...]section withclipboard_extras = -pvcinstead.
g:pyTestRunnerClipboardExtrasSuffix
This is now completely ignored.
Define a
[runner:...]or[path:...]section withclipboard_extras_suffix = 2>&1 | less -Rinstead.
- various corner cases for lesser used test runners might not work
- [path:...] runner=... probably overrides g:pyTestRunner, contradicting the documentation
- there's no error if you specify an empty command in a config file (or set g:pyTestRunnerCommand to a bunch of spaces)
- there's no error if a [path:...] section specifies a bad runner
py-test-runner.vim was written by Marius Gedminas <[email protected]>.
Licence: MIT.