3.4K Views
November 25, 24
スライド概要
既存のコードに新機能を追加したり、バグを修正したりする際に、意図せず他の機能に影響を与えてしまうことは避けなければなりません。 この課題を克服するために、自動テストの導入が欠かせません。
本発表では、Vim プラグイン開発における自動テストの重要性を確認し、テスティングフレームワークの選定方法や具体的なテストケースの作成方法、 さらにはメンテナンスを容易にするための効率的なフローについて詳しく解説します。 既存のコードの品質を向上させ、新機能の追加やバグ修正をより安全に行える方法をお伝えします。
DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。
Future-Proof Your Vim Plugins: Strategies for Robust Testing VimConf2024 2024-11-23 © DeNA Co., Ltd. 1
:help Kazuma Inagaki 👾 a.k.a IK 💻 :DeNA SWET Group2 /Quality Assurance Dept. / IT Unit vim-airline organization member Collaborators @get-me-power @get_me_power
Do you write tests?
What happens if the test isn't written? ● Failing to notice regressions ○ New changes unexpectedly affecting existing features ● Difficulty in reviewing pull requests ○ Without tests, it's hard to verify the behavior during code review
What happens if the test isn't written? ● Failing to notice regressions ○ New changes unexpectedly affecting existing features The same tasks apply even when making a Vim plugin ● Difficulty in reviewing pull requests ○ Without tests, it's hard to verify the behavior during code review
Outline 1. Introduction to Simple Testing 2. Selection/Usage of a Testing Framework 3. Points to Consider When Writing Tests 4. Efficient Flow for Easing Maintenance Starting from Tests 5. Conclusion
Outline 1. Introduction to Simple Testing 2. Selection/Usage of aUsing Testing Explain the Framework vim-devicons API as an Example 3. Points to Consider When Writing Tests 4. Efficient Flow for Easing Maintenance Starting from Tests 5. Conclusion
" A function that returns a specific icon based on the arguments " a:1 (bufferName), a:2 (isDirectory) function! WebDevIconsGetFileTypeSymbol(...) abort endfunction " Return the buffer icon or a default one. call WebDevIconsGetFileTypeSymbol() " Return the Vim icon. call WebDevIconsGetFileTypeSymbol("hoge.vim") " Return the folder icon. call WebDevIconsGetFileTypeSymbol("hoge.vim", 1)
" A function that returns a specific icon based on the arguments " a:1 (bufferName), a:2 (isDirectory) function! WebDevIconsGetFileTypeSymbol(...) abort endfunction " Return the buffer icon or a default one. call WebDevIconsGetFileTypeSymbol() " Return the Vim icon. call WebDevIconsGetFileTypeSymbol("hoge.vim") " Return the folder icon. call WebDevIconsGetFileTypeSymbol("hoge.vim", 1)
" A function that returns a specific icon based on the arguments " a:1 (bufferName), a:2 (isDirectory) function! WebDevIconsGetFileTypeSymbol(...) abort endfunction " Return the buffer icon or a default one. call WebDevIconsGetFileTypeSymbol() " Return the Vim icon. call WebDevIconsGetFileTypeSymbol("hoge.vim") " Return the folder icon. call WebDevIconsGetFileTypeSymbol("hoge.vim", 1)
" A function that returns a specific icon based on the arguments " a:1 (bufferName), a:2 (isDirectory) function! WebDevIconsGetFileTypeSymbol(...) abort endfunction " Return the buffer icon or a default one. call WebDevIconsGetFileTypeSymbol() " Return the Vim icon. call WebDevIconsGetFileTypeSymbol("hoge.vim") " Return the folder icon. call WebDevIconsGetFileTypeSymbol("hoge.vim", 1)
" A function that returns a specific icon based on the arguments " a:1 (bufferName), a:2 (isDirectory) function! WebDevIconsGetFileTypeSymbol(...) abort endfunction " Return the buffer icon or a default one. call WebDevIconsGetFileTypeSymbol() Next: Letʼs write easy Test " Return the Vim icon. call WebDevIconsGetFileTypeSymbol("hoge.vim") " Return the folder icon. call WebDevIconsGetFileTypeSymbol("hoge.vim", 1)
function! TestWebDevIconsGetFileTypeSymbol()
let v:errors = []
call assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
call assert_equal('\ue62b', WebDevIconsGetFileTypeSymbol("hoge.vim"))
call assert_equal('\uf07b', WebDevIconsGetFileTypeSymbol("hoge.vim", 1))
if len(v:errors) >= 1
echo v:errors
return
endif
echo 'test success'
endfunction
function! TestWebDevIconsGetFileTypeSymbol()
let v:errors = []
call assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
call assert_equal('\ue62b', WebDevIconsGetFileTypeSymbol("hoge.vim"))
call assert_equal('\uf07b', WebDevIconsGetFileTypeSymbol("hoge.vim", 1))
if len(v:errors) >= 1
Whatʼs asset_equal ?
echo v:errors
return
endif
echo 'test success'
endfunction
assert_equal({expected}, {actual} [, {msg}])
function!
TestWebDevIconsGetFileTypeSymbol()
When {expected}
and {actual} are not equal an error message is
added
to v:errors and
1 is returned. Otherwise zero is
let
v:errors
= []
returned. assert-return
The error is in the form "Expected {expected} but got
call
assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
{actual}". When {msg} is present it is prefixed to that,
along with the location of the assert when run from a script.
call
WebDevIconsGetFileTypeSymbol("hoge.vim"))
Thereassert_equal('\ue62b',
is no automatic conversion, the String
"4" is different
from the Number 4. And the number 4 is different from the
Float 4.0. The value of 'ignorecase' is not used here, case
always
matters.
call
assert_equal('\uf07b',
WebDevIconsGetFileTypeSymbol("hoge.vim",
Example:
call assert_equal('foo', 'bar', 'baz')
Will add the following to v:errors:
if len(v:errors)
>= 1
test.vim line 12: baz: Expected 'foo' but got 'bar'
echo v:errors
return
Can also be used as a method, the base is passed as the
endif
second argument:
mylist->assert_equal([1, 2, 3])
echo 'test success'
Return type: Number
endfunction
1)
assert_equal({expected}, {actual} [, {msg}])
function!
TestWebDevIconsGetFileTypeSymbol()
When {expected}
and {actual} are not equal an error message is
added
to v:errors and
1 is returned. Otherwise zero is
let
v:errors
= []
returned. assert-return
The error is in the form "Expected {expected} but got
call
assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
{actual}". When {msg} is present it is prefixed to that,
along with the location of the assert when run from a script.
call
WebDevIconsGetFileTypeSymbol("hoge.vim"))
Thereassert_equal('\ue62b',
is no automatic conversion, the String
"4" is different
from the Number 4. And the number 4 is different from the
Float 4.0. The value of 'ignorecase' is not used here, case
always
matters.
call
assert_equal('\uf07b',
WebDevIconsGetFileTypeSymbol("hoge.vim",
Example:
call assert_equal('foo', 'bar', 'baz')
Will add the following to v:errors:
if len(v:errors)
>= 1
test.vim line 12: baz: Expected 'foo' but got 'bar'
Next: Explaining the test process
echo v:errors
return
Can also be used as a method, the base is passed as the
endif
second argument:
mylist->assert_equal([1, 2, 3])
echo 'test success'
Return type: Number
endfunction
1
function! TestWebDevIconsGetFileTypeSymbol()
let v:errors = []
Accumulate errors found by the test function.
call assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
call assert_equal('\ue62b', WebDevIconsGetFileTypeSymbol("hoge.vim"))
call assert_equal('\uf07b', WebDevIconsGetFileTypeSymbol("hoge.vim", 1))
if len(v:errors) >= 1
echo v:errors
return
endif
echo 'test success'
endfunction
function! TestWebDevIconsGetFileTypeSymbol()
let v:errors = []
call assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
call assert_equal('\ue62b', WebDevIconsGetFileTypeSymbol("hoge.vim"))
call assert_equal('\uf07b', WebDevIconsGetFileTypeSymbol("hoge.vim", 1))
if len(v:errors) >= 1
echo v:errors
return
endif
echo 'test success'
endfunction
Run the test.
function! TestWebDevIconsGetFileTypeSymbol()
let v:errors = []
call assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
call assert_equal('\ue62b', WebDevIconsGetFileTypeSymbol("hoge.vim"))
call assert_equal('\uf07b', WebDevIconsGetFileTypeSymbol("hoge.vim", 1))
if len(v:errors) >= 1
echo v:errors
return
endif
echo 'test success'
endfunction
Display the test results.
function! TestWebDevIconsGetFileTypeSymbol()
let v:errors = []
call assert_equal('\ue612', WebDevIconsGetFileTypeSymbol())
call assert_equal('\ue62b', WebDevIconsGetFileTypeSymbol("hoge.vim"))
:call TestWebDevIconsGetFileTypeSymbol()
call assert_equal('\uf07b', WebDevIconsGetFileTypeSymbol("hoge.vim", 1))
if len(v:errors) >= 1
echo v:errors
return
endif
echo 'test success'
endfunction
Successful test case. test success Failed test case. ['function TestWebDevIconsGetFileTypeSymbol line 3: Expected '''' but got ''<98><ab>''', 'function TestWebDevIconsGetFileTypeSymbol line 4: Expected '''' but got ''<98><ab>''', 'function TestWebDevIconsGetFileTypeSymbol line 5: Expected '''' but got ''<81><bb>''']
テストが成功した場合 test success テストが失敗した場合 ● Failure results are unclear. ● Rules or guidelines for writing tests are needed. ● Management of multiple test cases is required. ● Easy separation of test environments is desired. ['function TestWebDevIconsGetFileTypeSymbol line 3: Expected '''' but got ''<98><ab>''', 'function TestWebDevIconsGetFileTypeSymbol line 4: Expected '''' but got ''<98><ab>''', 'function TestWebDevIconsGetFileTypeSymbol line 5: Expected '''' but got ''<81><bb>''']
テストが成功した場合 test success ● Failure results are unclear. ● Rules or guidelines for writing tests are needed. ● Management of multiple test cases is required. ● Easy separation of test environments is desired. Let's use a testing framework. テストが失敗した場合 ['function TestWebDevIconsGetFileTypeSymbol line 3: Expected '''' but got ''<98><ab>''', 'function TestWebDevIconsGetFileTypeSymbol line 4: Expected '''' but got ''<98><ab>''', 'function TestWebDevIconsGetFileTypeSymbol line 5: Expected '''' but got ''<81><bb>''']
Outline 1. Introduction to Simple Testing 2. Selection/Usage of a Testing Framework 3. Points to Consider When Writing Tests 4. Efficient Flow for Easing Maintenance Starting from Tests 5. Conclusion
Benefits of Introducing a Testing Framework ● Test reports are easy to understand. ● Test case writing can be standardized, enhancing maintainability. ● Multiple test cases can be centrally managed by the testing framework. ● Test environments in Vim can be easily separated.
What testing frameworks are available for Vim script? ● thinca/vim-themis ● junegoon/vader.vim ● kana/vim-vspec ● google/vroom …
What testing frameworks are available for Vim script? ● thinca/vim-themis ● junegoon/vader.vim ● kana/vim-vspec ● google/vroom … It's so confusing to figure out which one is better!
Points to Consider When Making a Selection ● Can it be executed from the shell? ○ ● Want to execute tests in one line for potential CI integration. Does it require environments other than Vim? ○ If other environments are needed, it increases the setup effort both locally and in CI. ● Can it manage plugins that the test plugin depends on? ○ Being able to manage them lowers the difficulty of setting up the environment. ○ Test execution be standardized in the local environment
Selection of a Testing Framework Can be executed from the shell 「Works with only Vim Can incorporate dependent plugins during testing thinca/ vim-themis ◯ ◯ ◯ junegoon/ vader.vim ✗ ◯ ✗ kana/ vim-vspec △ ✗ ✗ google/ vroom ✗ ◯ ✗
Selection of a Testing Framework Can be executed from the shell 「Works with only Vim Can incorporate dependent plugins during testing Let's use vim-themis ◯ ◯ ◯ junegoon/ vader.vim ✗ ◯ ✗ kana/ vim-vspec △ ✗ ✗ google/ vroom ✗ ◯ ✗ thinca/ vim-themis
Outline 1. Introduction to Simple Testing 2. Selection/Usage of a Testing Framework 3. Points to Consider When Writing Tests 4. Efficient Flow for Easing Maintenance Starting from Tests 5. Conclusion
Points to consider when writing tests ● Ensure that the results do not change whether executed locally or on CI. ● Prevent assertion roulette.
Points to consider when writing tests ● Ensure that the results do not change whether executed locally or on CI. An explanation based on vim-airline's test cases. ● Prevent assertion roulette.
It should extract correct colors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(colors[0], 'NONE') Assert Equals(colors[1], 'NONE') Assert Equals(colors[2], '1') Assert Equals(colors[3], '2') End Execute locally ❌ Failed Execute on CI ✅ Success
It should extract correct colors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(colors[0], 'NONE') Assert Equals(colors[1], 'NONE') Assert Equals(colors[2], '1') Assert Equals(colors[3], '2') End Execute locally ❌ Failed 🤔 Execute on CI ✅ Success
It should extract correct colors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(colors[0], 'NONE') Assert Equals(colors[1], 'NONE') Assert Equals(colors[2], '1') Assert Equals(colors[3], '2') End set termguicolor ❌ Failed set notermguicolor ✅ Success
It should extract correct colors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(colors[0], 'NONE') Assert Equals(colors[1], 'NONE') Assert Equals(colors[2], '1') Assert Equals(colors[3], '2') End The test results change depending on Vim options! set termguicolor ❌ Failed set notermguicolor ✅ Success
It should extract correct colors with notermguicolors set notermguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End It should extract correct colors with termguicolors if !exists("+termguicolors") Assert Skip("termguicolors is disable build. Skip this test.") endif set termguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End
It should extract correct colors with notermguicolors set notermguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End It should extract correct colors with termguicolors if !exists("+termguicolors") Assert Skip("termguicolors is disable build. Skip this test.") endif set termguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End
It should extract correct colors with notermguicolors set notermguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End for It notermguicolor should extract method correct colors with termguicolors if !exists("+termguicolors") Assert Skip("termguicolors is disable build. Skip this test.") endif set termguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End
It should extract correct colors with notermguicolors set notermguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) for termguicolors method End It should extract correct colors with termguicolors if !exists("+termguicolors") Assert Skip("termguicolors is disable build. Skip this test.") endif set termguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End
It should extract correct colors with notermguicolors set notermguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End It should extract correct colors with termguicolors if !exists("+termguicolors") Assert Skip("termguicolors is disable build. Skip this test.") endif set termguicolors call airline#highlighter#reset_hlcache() highlight Foo ctermfg=1 ctermbg=2 let colors = airline#themes#get_highlight('Foo') Assert Equals(...) End ✅ Success ✅ Success
Points to consider when writing tests ● Ensure that the results do not change whether executed locally or on CI. ● Prevent assertion roulette. An explanation based on vim-devicons' test cases.
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction $themis test –reporter spec
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction WebDevIconsGetFileTypeSymbol [✓] WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon [✓] WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon [✓] WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon tests 3 passes 3
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction Simplify using a for loop let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() let targetfilenames = ['test.vim', '.vimrc', 'gvimrc'] for targetfilename in targetfilenames call s:assert.equals(WebDevIconsGetFileTypeSymbol(targetfilename), "\ue62b") endfor endfunction
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.WebDevIconsGetFileTypeSymbol_testdotvim_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_vimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") endfunction function! s:suite.WebDevIconsGetFileTypeSymbol_gvimrc_returnVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction Simplify using a for loop let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() let targetfilenames = ['test.vim', '.vimrc', 'gvimrc'] for targetfilename in targetfilenames call s:assert.equals(WebDevIconsGetFileTypeSymbol(targetfilename), "\ue62b") endfor endfunction
What is the issue?
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction Simplify using a for loop let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction Intentionally cause it to fail Simplify using a for loop let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "\ue62b") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "\ue62b") endfunction
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction Simplify using a for loop let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction $themis test –reporter spec Simplify using a for loop let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction WebDevIconsGetFileTypeSymbol [✖] OneArgumet_GetVimIcon Simplify using a for loop The equivalent values were expected, but it was not the case. let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') expected: "" got: "\ue612" function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call tests 1 s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") passes 0 endfunction fails 1
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction WebDevIconsGetFileTypeSymbol [✖] OneArgumet_GetVimIcon Simplify using a for loop The equivalent values were expected, but it was not the case. let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') expected: "" got: "\ue612" function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call "") !? tests 1 s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") passes 0 endfunction fails 1
Combine test methods into one let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), "") call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") endfunction WebDevIconsGetFileTypeSymbol Don't know where it failed! [✖] OneArgumet_GetVimIcon Simplify using a for loop The equivalent values were expected, but it was not the case. let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') expected: "" got: "\ue612" function! s:suite.OneArgumet_GetVimIcon() call s:assert.equals(WebDevIconsGetFileTypeSymbol('test.vim'), "") call "") !? tests 1 s:assert.equals(WebDevIconsGetFileTypeSymbol('vimrc'), call s:assert.equals(WebDevIconsGetFileTypeSymbol('gvimrc'), "") passes 0 endfunction fails 1
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() let targetfilenames = ['test.vim', '.vimrc', 'gvimrc'] for targetfilename in targetfilenames call s:assert.equals(WebDevIconsGetFileTypeSymbol(targetfilename), "\ue62b") endfor endfunction
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() let targetfilenames = ['test.vim', '.vimrc', 'gvimrc'] for targetfilename in targetfilenames call s:assert.equals(WebDevIconsGetFileTypeSymbol(targetfilename), "\ue62b") endfor endfunction let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:Assert(filename, icon) call s:assert.equals(WebDevIconsGetFileTypeSymbol(a:filename), a:icon) endfunction function! s:suite.__OneArgument_VimIcon__() let targetfilenames = ['test.vim', 'vimrc', 'gvimrc'] let expecticon = "\ue62b" let child = themis#suite('OneArgument_VimIcon') for targetfilename in targetfilenames let child[targetfilename] = funcref('s:Assert', [targetfilename, expecticon]) endfor endfunction
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() let targetfilenames = ['test.vim', '.vimrc', 'gvimrc'] for targetfilename in targetfilenames call s:assert.equals(WebDevIconsGetFileTypeSymbol(targetfilename), "\ue62b") endfor endfunction let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:Assert(filename, icon) call s:assert.equals(WebDevIconsGetFileTypeSymbol(a:filename), a:icon) endfunction WebDevIconsGetFileTypeSymbol OneArgument_VimIcon function! s:suite.__OneArgument_VimIcon__() [✓]targetfilenames test.vim let = ['test.vim', 'vimrc', 'gvimrc'] let = "\ue62b" [✓]expecticon vimrc let child = themis#suite('OneArgument_VimIcon') [✓] gvimrc for targetfilename in targetfilenames let child[targetfilename] = funcref('s:Assert', [targetfilename, expecticon]) tests 3 endfor passes 3 endfunction
let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:suite.OneArgumet_GetVimIcon() WebDevIconsGetFileTypeSymbol let targetfilenames = ['test.vim', '.vimrc', 'gvimrc'] [✓] OneArgumet_GetVimIcon for targetfilename in targetfilenames call s:assert.equals(WebDevIconsGetFileTypeSymbol(targetfilename), "\ue62b") tests 1 endfor passes 1 endfunction fails 0 let s:suite = themis#suite('WebDevIconsGetFileTypeSymbol') let s:assert = themis#helper('assert') function! s:Assert(filename, icon) call s:assert.equals(WebDevIconsGetFileTypeSymbol(a:filename), a:icon) endfunction WebDevIconsGetFileTypeSymbol OneArgument_VimIcon function! s:suite.__OneArgument_VimIcon__() [✓]targetfilenames test.vim let = ['test.vim', 'vimrc', 'gvimrc'] let = "\ue62b" [✓]expecticon vimrc let child = themis#suite('OneArgument_VimIcon') [✓] gvimrc for targetfilename in targetfilenames let child[targetfilename] = funcref('s:Assert', [targetfilename, expecticon]) tests 3 endfor passes 3 endfunction
Outline 1. Introduction to Simple Testing 2. Selection/Usage of a Testing Framework 3. Points to Consider When Writing Tests 4. Efficient Flow for Easing Maintenance Starting from Tests 5. Conclusion
make PR Pull Request merge Repo with patches applied
make PR Pull Request merge ✅ Does the plugin work in the first place? Repo with patches applied
make PR Pull Request merge ✅ Does the plugin work in the first place? ✅ Is there no regression? Repo with patches applied
make PR Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary?
make PR Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary? ✅ Is there no negative impact on performance?
make PR Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary? ✅ Is there no negative impact on performance? Do we have to do all of this manually!?!? maintainer
YOU DIED
This is not something a person should do
This is not something a person should do Let's automate it
make PR Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary? ✅ Is there no negative impact on performance?
make PR It seems automatable Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary? ✅ Is there no negative impact on performance?
make PR Static analysis is effective Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary? ✅ Is there no negative impact on performance?
make PR Executing automated tests is effective Pull Request merge Repo with patches applied ✅ Does the plugin work in the first place? ✅ Is there no regression? ✅ Does it meet the requirements outlined in the PR summary? ✅ Is there no negative impact on performance?
make PR Pull Request merge Execution of vint by CI Diff without syntax errors Execution of automated tests by CI Results with all tests passing Steps for automation Repo with patches applied
make PR Pull Request merge Execution of vint by CI Diff without syntax errors Repo with patches applied Do not merge unless it passes on CI! Execution of automated tests by CI Steps for automation Results with all tests passing
make PR Pull Request merge Execution of vint by CI Diff without syntax errors Repo with patches applied Do not merge unless it passes on CI! Maintenance becomes easier! Execution of automated tests by CI Steps for automation Results with all tests passing
Outline 1. Introduction to Simple Testing 2. Selection/Usage of a Testing Framework 3. Points to Consider When Writing Tests 4. Efficient Flow for Easing Maintenance Starting from Tests 5. Conclusion
Conclusion ● Nowadays, there are various ways to create Vim plugins ○ Deno, Lua, Vim script, Vim9 script, etc... ● Although Vim script is used as the example here, the basic concepts are the same for all. ● You can contribute to OSS through testing as well. 80
Conclusion ● Nowadays, there are various ways to create Vim plugins ○ Deno, Lua, Vim script, Vim9 script, etc... I encourage you to try writing test! ● Although Vim script is used as the example here, the basic concepts are the same for all. ● You can contribute to OSS through testing as well. 81
DeNA × AI Day || DeNA TechCon 2025 2025年2月5日 オンラインにて開催します!! イベントサイトは12⽉上旬公開予定!! X 公式アカウントにて告知しますのでフォローお願いします。 © DeNA Co., Ltd. @DeNAxTech 82