Are script local functions (s:funcName()) unit testable?
up vote
5
down vote
favorite
Consider a script local function in a plugin/myplugin.vim
file. For example
myplugin.vim:
func! s:someFunction()
return 4
endfunc
I would like to unit test this function.
In the simplest example I would like to execute something like the following in ANOTHER script. Without modifying the original script.
test_myplugin.vim:
if s:someFunction() != 4
throw "Error"
endif
This obviously does not work because s:someFunction
is not visible from outside of the myplugin.vim
.
Is there a way to unit test these script local functions?
Some options that come to mind:
- Move the function to be unit tested to
autoload
directory and rename itmyplugin#someFunction()
. Inspiration is from :help write-library-script. I do not prefer this approach. The function(s) are not reused they are not supposed to be a library. I also do not own theplugin/myplugin.vim
. - Apparently vim prepends a
<SNR>123_
like string to script-local-functions. (123
can be any number). I could get the list of all functions with the :function command. Then find the full function name that matches<SNR>d+_someFunction
and call that function by name. Looking at this question it looks like calling functions is possible once you know their string name.
Update:
If anyone is interested in an implementation of the hackish approach in @IngoKarkat's answer, you can take a look at my take on it here
vimscript functions
New contributor
add a comment |
up vote
5
down vote
favorite
Consider a script local function in a plugin/myplugin.vim
file. For example
myplugin.vim:
func! s:someFunction()
return 4
endfunc
I would like to unit test this function.
In the simplest example I would like to execute something like the following in ANOTHER script. Without modifying the original script.
test_myplugin.vim:
if s:someFunction() != 4
throw "Error"
endif
This obviously does not work because s:someFunction
is not visible from outside of the myplugin.vim
.
Is there a way to unit test these script local functions?
Some options that come to mind:
- Move the function to be unit tested to
autoload
directory and rename itmyplugin#someFunction()
. Inspiration is from :help write-library-script. I do not prefer this approach. The function(s) are not reused they are not supposed to be a library. I also do not own theplugin/myplugin.vim
. - Apparently vim prepends a
<SNR>123_
like string to script-local-functions. (123
can be any number). I could get the list of all functions with the :function command. Then find the full function name that matches<SNR>d+_someFunction
and call that function by name. Looking at this question it looks like calling functions is possible once you know their string name.
Update:
If anyone is interested in an implementation of the hackish approach in @IngoKarkat's answer, you can take a look at my take on it here
vimscript functions
New contributor
add a comment |
up vote
5
down vote
favorite
up vote
5
down vote
favorite
Consider a script local function in a plugin/myplugin.vim
file. For example
myplugin.vim:
func! s:someFunction()
return 4
endfunc
I would like to unit test this function.
In the simplest example I would like to execute something like the following in ANOTHER script. Without modifying the original script.
test_myplugin.vim:
if s:someFunction() != 4
throw "Error"
endif
This obviously does not work because s:someFunction
is not visible from outside of the myplugin.vim
.
Is there a way to unit test these script local functions?
Some options that come to mind:
- Move the function to be unit tested to
autoload
directory and rename itmyplugin#someFunction()
. Inspiration is from :help write-library-script. I do not prefer this approach. The function(s) are not reused they are not supposed to be a library. I also do not own theplugin/myplugin.vim
. - Apparently vim prepends a
<SNR>123_
like string to script-local-functions. (123
can be any number). I could get the list of all functions with the :function command. Then find the full function name that matches<SNR>d+_someFunction
and call that function by name. Looking at this question it looks like calling functions is possible once you know their string name.
Update:
If anyone is interested in an implementation of the hackish approach in @IngoKarkat's answer, you can take a look at my take on it here
vimscript functions
New contributor
Consider a script local function in a plugin/myplugin.vim
file. For example
myplugin.vim:
func! s:someFunction()
return 4
endfunc
I would like to unit test this function.
In the simplest example I would like to execute something like the following in ANOTHER script. Without modifying the original script.
test_myplugin.vim:
if s:someFunction() != 4
throw "Error"
endif
This obviously does not work because s:someFunction
is not visible from outside of the myplugin.vim
.
Is there a way to unit test these script local functions?
Some options that come to mind:
- Move the function to be unit tested to
autoload
directory and rename itmyplugin#someFunction()
. Inspiration is from :help write-library-script. I do not prefer this approach. The function(s) are not reused they are not supposed to be a library. I also do not own theplugin/myplugin.vim
. - Apparently vim prepends a
<SNR>123_
like string to script-local-functions. (123
can be any number). I could get the list of all functions with the :function command. Then find the full function name that matches<SNR>d+_someFunction
and call that function by name. Looking at this question it looks like calling functions is possible once you know their string name.
Update:
If anyone is interested in an implementation of the hackish approach in @IngoKarkat's answer, you can take a look at my take on it here
vimscript functions
vimscript functions
New contributor
New contributor
edited 33 secs ago
New contributor
asked Nov 8 at 6:32
Hakan Baba
1283
1283
New contributor
New contributor
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
up vote
5
down vote
accepted
TL;DR: Yes, but you probably shouldn't (in general)
Other answers
Christian's answer offers two approaches that modify myplugin.vim
in order to expose the script-local function (either as a Funcref or just the SID that allows you to obtain a Funcref).
Having to extend a plugin just for testing purposes is not nice, and as I understand, you're reluctant to change myplugin.vim
, too.
The hacky way
What I have done occasionally for testing is parsing the output of :scriptnames
, matching plugin/myplugin.vim
in order to obtain the SID. With this, you can build a Funcref and invoke any script-local function:
:let sid = ... " Parse :scriptnames output
:let MyFuncref = function("<SNR>" . sid . "_someFunction")
:echo call(MyFuncref, )
The purist's answer
Script-local functions are equivalent to private methods in other languages (e.g. Java). There, there are similar discussions about lifting access restrictions (e.g. to protected
) for unit testing, and many people think that this is a bad idea, that unit tests should only rely on the object's public API, and that testing private implementation details leads to brittle tests and impedes refactoring.
Therefore, my first impulse would be to expose the function as an autoload function. (In fact, I almost never put functions in the plugin script; everything is (script-local or public) in an autoload script.)
I don't agree with your apprehension of turning these into "library functions". Most Vim plugins don't expose a Vimscript API at all (just mappings and custom commands). Even if you have a public API, you can still differentiate between official public functions and autoload functions exposed for unit testing via other means:
- Only have API documentation for the official functions, or mention the intended "visibility" (public / private) in the attached function documentation.
- Segregate into different autoload scripts, e.g.
autoload/myplugin.vim
for the public API andautoload/myplugin/impl.vim
for the functions to be unit-tested.
add a comment |
up vote
3
down vote
I think there are two different possibilities to achieve what you want.
You can create a global
funcref
to your script local function and then call that funcref. Something like this:
:let g:MyCustomFuncref=funcref("<sid>MyScriptLocalFunction")
(Note, a funcref variable must start with a capital letter).
Parse the script number inside your s:Function and make it available so other functions can call it, even when not defined inside your script-local file.
:fu! <sid>GetSID() "{{{1
return matchstr(expand('<sfile>'), '<SNR>zsd+ze_GetSID$')
endfu
let g:mysid = <sid>GetSID()
I have done this in my changes Plugin. You can then dynamically construct the function name and call it. That is because script-local functions are not really script-local, they are just namespaced.
In my csv plugin I used to set the foldexpr
to a script local function.
Now either possibility will possibly create a new global variable which you might want to avoid. What I have been doing in the past is to only allow access to those variable, if the plugin has been run in debug mode. (So set a configuration variable to enable debug mode, after which you have access to those special variable and can dynamically call all needed script-local functions).
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
5
down vote
accepted
TL;DR: Yes, but you probably shouldn't (in general)
Other answers
Christian's answer offers two approaches that modify myplugin.vim
in order to expose the script-local function (either as a Funcref or just the SID that allows you to obtain a Funcref).
Having to extend a plugin just for testing purposes is not nice, and as I understand, you're reluctant to change myplugin.vim
, too.
The hacky way
What I have done occasionally for testing is parsing the output of :scriptnames
, matching plugin/myplugin.vim
in order to obtain the SID. With this, you can build a Funcref and invoke any script-local function:
:let sid = ... " Parse :scriptnames output
:let MyFuncref = function("<SNR>" . sid . "_someFunction")
:echo call(MyFuncref, )
The purist's answer
Script-local functions are equivalent to private methods in other languages (e.g. Java). There, there are similar discussions about lifting access restrictions (e.g. to protected
) for unit testing, and many people think that this is a bad idea, that unit tests should only rely on the object's public API, and that testing private implementation details leads to brittle tests and impedes refactoring.
Therefore, my first impulse would be to expose the function as an autoload function. (In fact, I almost never put functions in the plugin script; everything is (script-local or public) in an autoload script.)
I don't agree with your apprehension of turning these into "library functions". Most Vim plugins don't expose a Vimscript API at all (just mappings and custom commands). Even if you have a public API, you can still differentiate between official public functions and autoload functions exposed for unit testing via other means:
- Only have API documentation for the official functions, or mention the intended "visibility" (public / private) in the attached function documentation.
- Segregate into different autoload scripts, e.g.
autoload/myplugin.vim
for the public API andautoload/myplugin/impl.vim
for the functions to be unit-tested.
add a comment |
up vote
5
down vote
accepted
TL;DR: Yes, but you probably shouldn't (in general)
Other answers
Christian's answer offers two approaches that modify myplugin.vim
in order to expose the script-local function (either as a Funcref or just the SID that allows you to obtain a Funcref).
Having to extend a plugin just for testing purposes is not nice, and as I understand, you're reluctant to change myplugin.vim
, too.
The hacky way
What I have done occasionally for testing is parsing the output of :scriptnames
, matching plugin/myplugin.vim
in order to obtain the SID. With this, you can build a Funcref and invoke any script-local function:
:let sid = ... " Parse :scriptnames output
:let MyFuncref = function("<SNR>" . sid . "_someFunction")
:echo call(MyFuncref, )
The purist's answer
Script-local functions are equivalent to private methods in other languages (e.g. Java). There, there are similar discussions about lifting access restrictions (e.g. to protected
) for unit testing, and many people think that this is a bad idea, that unit tests should only rely on the object's public API, and that testing private implementation details leads to brittle tests and impedes refactoring.
Therefore, my first impulse would be to expose the function as an autoload function. (In fact, I almost never put functions in the plugin script; everything is (script-local or public) in an autoload script.)
I don't agree with your apprehension of turning these into "library functions". Most Vim plugins don't expose a Vimscript API at all (just mappings and custom commands). Even if you have a public API, you can still differentiate between official public functions and autoload functions exposed for unit testing via other means:
- Only have API documentation for the official functions, or mention the intended "visibility" (public / private) in the attached function documentation.
- Segregate into different autoload scripts, e.g.
autoload/myplugin.vim
for the public API andautoload/myplugin/impl.vim
for the functions to be unit-tested.
add a comment |
up vote
5
down vote
accepted
up vote
5
down vote
accepted
TL;DR: Yes, but you probably shouldn't (in general)
Other answers
Christian's answer offers two approaches that modify myplugin.vim
in order to expose the script-local function (either as a Funcref or just the SID that allows you to obtain a Funcref).
Having to extend a plugin just for testing purposes is not nice, and as I understand, you're reluctant to change myplugin.vim
, too.
The hacky way
What I have done occasionally for testing is parsing the output of :scriptnames
, matching plugin/myplugin.vim
in order to obtain the SID. With this, you can build a Funcref and invoke any script-local function:
:let sid = ... " Parse :scriptnames output
:let MyFuncref = function("<SNR>" . sid . "_someFunction")
:echo call(MyFuncref, )
The purist's answer
Script-local functions are equivalent to private methods in other languages (e.g. Java). There, there are similar discussions about lifting access restrictions (e.g. to protected
) for unit testing, and many people think that this is a bad idea, that unit tests should only rely on the object's public API, and that testing private implementation details leads to brittle tests and impedes refactoring.
Therefore, my first impulse would be to expose the function as an autoload function. (In fact, I almost never put functions in the plugin script; everything is (script-local or public) in an autoload script.)
I don't agree with your apprehension of turning these into "library functions". Most Vim plugins don't expose a Vimscript API at all (just mappings and custom commands). Even if you have a public API, you can still differentiate between official public functions and autoload functions exposed for unit testing via other means:
- Only have API documentation for the official functions, or mention the intended "visibility" (public / private) in the attached function documentation.
- Segregate into different autoload scripts, e.g.
autoload/myplugin.vim
for the public API andautoload/myplugin/impl.vim
for the functions to be unit-tested.
TL;DR: Yes, but you probably shouldn't (in general)
Other answers
Christian's answer offers two approaches that modify myplugin.vim
in order to expose the script-local function (either as a Funcref or just the SID that allows you to obtain a Funcref).
Having to extend a plugin just for testing purposes is not nice, and as I understand, you're reluctant to change myplugin.vim
, too.
The hacky way
What I have done occasionally for testing is parsing the output of :scriptnames
, matching plugin/myplugin.vim
in order to obtain the SID. With this, you can build a Funcref and invoke any script-local function:
:let sid = ... " Parse :scriptnames output
:let MyFuncref = function("<SNR>" . sid . "_someFunction")
:echo call(MyFuncref, )
The purist's answer
Script-local functions are equivalent to private methods in other languages (e.g. Java). There, there are similar discussions about lifting access restrictions (e.g. to protected
) for unit testing, and many people think that this is a bad idea, that unit tests should only rely on the object's public API, and that testing private implementation details leads to brittle tests and impedes refactoring.
Therefore, my first impulse would be to expose the function as an autoload function. (In fact, I almost never put functions in the plugin script; everything is (script-local or public) in an autoload script.)
I don't agree with your apprehension of turning these into "library functions". Most Vim plugins don't expose a Vimscript API at all (just mappings and custom commands). Even if you have a public API, you can still differentiate between official public functions and autoload functions exposed for unit testing via other means:
- Only have API documentation for the official functions, or mention the intended "visibility" (public / private) in the attached function documentation.
- Segregate into different autoload scripts, e.g.
autoload/myplugin.vim
for the public API andautoload/myplugin/impl.vim
for the functions to be unit-tested.
edited 2 days ago
answered 2 days ago
Ingo Karkat
11.3k2538
11.3k2538
add a comment |
add a comment |
up vote
3
down vote
I think there are two different possibilities to achieve what you want.
You can create a global
funcref
to your script local function and then call that funcref. Something like this:
:let g:MyCustomFuncref=funcref("<sid>MyScriptLocalFunction")
(Note, a funcref variable must start with a capital letter).
Parse the script number inside your s:Function and make it available so other functions can call it, even when not defined inside your script-local file.
:fu! <sid>GetSID() "{{{1
return matchstr(expand('<sfile>'), '<SNR>zsd+ze_GetSID$')
endfu
let g:mysid = <sid>GetSID()
I have done this in my changes Plugin. You can then dynamically construct the function name and call it. That is because script-local functions are not really script-local, they are just namespaced.
In my csv plugin I used to set the foldexpr
to a script local function.
Now either possibility will possibly create a new global variable which you might want to avoid. What I have been doing in the past is to only allow access to those variable, if the plugin has been run in debug mode. (So set a configuration variable to enable debug mode, after which you have access to those special variable and can dynamically call all needed script-local functions).
add a comment |
up vote
3
down vote
I think there are two different possibilities to achieve what you want.
You can create a global
funcref
to your script local function and then call that funcref. Something like this:
:let g:MyCustomFuncref=funcref("<sid>MyScriptLocalFunction")
(Note, a funcref variable must start with a capital letter).
Parse the script number inside your s:Function and make it available so other functions can call it, even when not defined inside your script-local file.
:fu! <sid>GetSID() "{{{1
return matchstr(expand('<sfile>'), '<SNR>zsd+ze_GetSID$')
endfu
let g:mysid = <sid>GetSID()
I have done this in my changes Plugin. You can then dynamically construct the function name and call it. That is because script-local functions are not really script-local, they are just namespaced.
In my csv plugin I used to set the foldexpr
to a script local function.
Now either possibility will possibly create a new global variable which you might want to avoid. What I have been doing in the past is to only allow access to those variable, if the plugin has been run in debug mode. (So set a configuration variable to enable debug mode, after which you have access to those special variable and can dynamically call all needed script-local functions).
add a comment |
up vote
3
down vote
up vote
3
down vote
I think there are two different possibilities to achieve what you want.
You can create a global
funcref
to your script local function and then call that funcref. Something like this:
:let g:MyCustomFuncref=funcref("<sid>MyScriptLocalFunction")
(Note, a funcref variable must start with a capital letter).
Parse the script number inside your s:Function and make it available so other functions can call it, even when not defined inside your script-local file.
:fu! <sid>GetSID() "{{{1
return matchstr(expand('<sfile>'), '<SNR>zsd+ze_GetSID$')
endfu
let g:mysid = <sid>GetSID()
I have done this in my changes Plugin. You can then dynamically construct the function name and call it. That is because script-local functions are not really script-local, they are just namespaced.
In my csv plugin I used to set the foldexpr
to a script local function.
Now either possibility will possibly create a new global variable which you might want to avoid. What I have been doing in the past is to only allow access to those variable, if the plugin has been run in debug mode. (So set a configuration variable to enable debug mode, after which you have access to those special variable and can dynamically call all needed script-local functions).
I think there are two different possibilities to achieve what you want.
You can create a global
funcref
to your script local function and then call that funcref. Something like this:
:let g:MyCustomFuncref=funcref("<sid>MyScriptLocalFunction")
(Note, a funcref variable must start with a capital letter).
Parse the script number inside your s:Function and make it available so other functions can call it, even when not defined inside your script-local file.
:fu! <sid>GetSID() "{{{1
return matchstr(expand('<sfile>'), '<SNR>zsd+ze_GetSID$')
endfu
let g:mysid = <sid>GetSID()
I have done this in my changes Plugin. You can then dynamically construct the function name and call it. That is because script-local functions are not really script-local, they are just namespaced.
In my csv plugin I used to set the foldexpr
to a script local function.
Now either possibility will possibly create a new global variable which you might want to avoid. What I have been doing in the past is to only allow access to those variable, if the plugin has been run in debug mode. (So set a configuration variable to enable debug mode, after which you have access to those special variable and can dynamically call all needed script-local functions).
answered 2 days ago
Christian Brabandt
15k2445
15k2445
add a comment |
add a comment |
Hakan Baba is a new contributor. Be nice, and check out our Code of Conduct.
Hakan Baba is a new contributor. Be nice, and check out our Code of Conduct.
Hakan Baba is a new contributor. Be nice, and check out our Code of Conduct.
Hakan Baba is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fvi.stackexchange.com%2fquestions%2f17866%2fare-script-local-functions-sfuncname-unit-testable%23new-answer', 'question_page');
}
);
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password