I’m setting up unit tests for my code that creates various custom indexes. I have content on the file system that serves as my known input. With that data I then need to create the content database and run the processes that create the various indexes over the content database.
Thus I need to create the databases, populate them, then verify the populated result.
As you can’t create a database and query it in the same XQuery, I don’t see a way to use %unit:before-module to initialize my databases before running unit tests in the same module.
The solution seems to be to use a BaseX script to do the database initialization, which seems easy enough:
# Run unit tests with fresh database # Make sure the databases exist so we can then drop them check pce-test-01 check _lrc_pce-test-01_link_records # Drop them drop db pce-test-01 drop db _lrc_pce-test-01_link_records # Now create them fresh check pce-test-01 check _lrc_pce-test-01_link_records # # Now run the tests that use these databases in the # required order # test ./test-db-from-git.xqy test ./test-link-record-keeping.xqy
However, in running this from the BaseX GUI, it appears that the test commands are trying to find files relative to the location of the basexgui command rather than relative to the script being run:
Resource "/Users/eliot.kimber/apps/basex/bin/test-db-from-git.xqy" not found.
I don’t see anything in the commands documentation that suggests a way to parameterize the values passed to commands.
Am I missing a way to have this kind of setup script be portable from within the GUI or is there a better/different way to initialize the databases for unit tests?
Thanks,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
I don’t see a way to use %unit:before-module to initialize my databases before running unit tests in the same module.
I've successfully used %unit:before-module and %unit:after-module to set up (db:create) and tear down (db:drop) databases respectively, and then %unit:before to update them with test case documents (db:add/db:replace) in the individual unit tests. It's a bit fiddly at first, but it works.
Andrew
Interesting—I did some tests with just having mixupdates on and then doing updates and queries in the same XQuery run and the updates where not seen in the same query.
But maybe the test runner runs unit:before-module() as a separate transaction?
Cheers,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Andrew Sales andrew@andrewsales.com Date: Sunday, January 30, 2022 at 7:32 AM To: Eliot Kimber eliot.kimber@servicenow.com Cc: basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations [External Email]
I don’t see a way to use %unit:before-module to initialize my databases before running unit tests in the same module.
I've successfully used %unit:before-module and %unit:after-module to set up (db:create) and tear down (db:drop) databases respectively, and then %unit:before to update them with test case documents (db:add/db:replace) in the individual unit tests. It's a bit fiddly at first, but it works.
Andrew
But maybe the test runner runs unit:before-module() as a separate transaction?
That does look to be the behaviour -- the phrase "This extension is e. g. helpful if the results of updates need to be tested." and the accompanying code snippet [1] imply as much to me, but it would be good if the BaseX dev team could confirm, and the documentation make this explicit.
Andrew
That’s precisely what happens. I have added this piece of information to our documentation. – Thanks, Christian
On Mon, Jan 31, 2022 at 5:54 PM Andrew Sales andrew@andrewsales.com wrote:
But maybe the test runner runs unit:before-module() as a separate transaction?
That does look to be the behaviour -- the phrase "This extension is e. g. helpful if the results of updates need to be tested." and the accompanying code snippet [1] imply as much to me, but it would be good if the BaseX dev team could confirm, and the documentation make this explicit.
Andrew
I’m unable to verify the expected transaction behavior.
I made this unit test:
module namespace test = 'http://basex.org/modules/xqunit-tests';
declare variable $test:dbName := string('test_' || replace(string(current-dateTime()),'[:.]', '_'));
declare %unit:before-module updating function test:setUp() { update:output(prof:dump('test:setUp(): Creating database ' || $test:dbName)), db:create($test:dbName) };
declare %unit:after-module updating function test:tearDown() { update:output(prof:dump('test:tearDown(): Dropping database ' || $test:dbName)), if (db:exists($test:dbName)) then try { db:drop($test:dbName) } catch * { update:output(prof:dump('test:tearDown(): Error ' || $err:code || ' from db:drop(): ' || $err:description)) } else update:output("test:tearDown(): Database " || $test:dbName || " does not exist.") };
declare %unit:test updating function test:addToDb() { update:output(prof:dump('test:addToDb(): Adding doc to database ' || $test:dbName)), try { db:add($test:dbName, <doc time="{current-dateTime()}">doc 1</doc>, 'doc-01.xml') } catch * { update:output(prof:dump('test:addToDb(): Error ' || $err:code || ' from db:add(): ' || $err:description)) } }; (: ==== End of m odule ==== :)
And it produces this result:
Evaluating: "test:setUp(): Creating database test_2022-02-01T11_59_34_703-06_00" "test:addToDb(): Adding doc to database test_2022-02-01T11_59_34_718-06_00" "test:addToDb(): Error db:open from db:add(): Database 'test_2022-02-01T11_59_34_718-06_00' was not found." "test:tearDown(): Dropping database test_2022-02-01T11_59_34_727-06_00"
The database is created but is not dropped and obviously not updated.
If my test is legit then this suggests that before-module and after-module are not separate transactions.
Cheers,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Christian Grün christian.gruen@gmail.com Date: Monday, January 31, 2022 at 11:28 AM To: Andrew Sales andrew@andrewsales.com Cc: Eliot Kimber eliot.kimber@servicenow.com, basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations [External Email]
That’s precisely what happens. I have added this piece of information to our documentation. – Thanks, Christian
On Mon, Jan 31, 2022 at 5:54 PM Andrew Sales andrew@andrewsales.com wrote:
But maybe the test runner runs unit:before-module() as a separate transaction?
That does look to be the behaviour -- the phrase "This extension is e. g. helpful if the results of updates need to be tested." and the accompanying code snippet [1] imply as much to me, but it would be good if the BaseX dev team could confirm, and the documentation make this explicit.
Andrew
[1] https://urldefense.com/v3/__https://docs.basex.org/wiki/Unit_Module*unit:bef...https://urldefense.com/v3/__https:/docs.basex.org/wiki/Unit_Module*unit:before__;Iw!!N4vogdjhuJM!QaGgfyLTQYT7a_H1uFaAoJJOpRxHYVQRJ1n_ZoFbEsnfmodXuczcubZkHyMDVWbI-ZMIIQ$
Hi Eliot,
The database is created but is not dropped and obviously not updated.
If my test is legit then this suggests that before-module and after-module are not separate transactions.
As each function will be evaluated separately, the current dateTime value will be different for each function call. Your code should work if you replace current-dateTime() by current-date() or any other static value.
Best, Christian
I see. I guess I assumed that a module-level variable would be invariant across the transactions, but clearly not (and I didn’t notice the milliseconds difference).
With a static database value then my tests do pass.
Cheers,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Christian Grün christian.gruen@gmail.com Date: Tuesday, February 1, 2022 at 12:35 PM To: Eliot Kimber eliot.kimber@servicenow.com Cc: basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations [External Email]
Hi Eliot,
The database is created but is not dropped and obviously not updated.
If my test is legit then this suggests that before-module and after-module are not separate transactions.
As each function will be evaluated separately, the current dateTime value will be different for each function call. Your code should work if you replace current-dateTime() by current-date() or any other static value.
Best, Christian
Just confirm: while before-module and after-module are separate transactions, within a single unit:test function that itself performs an updating function there’s no way to then evaluate the result of the update as any asserts will be in the same transaction.
Cheers,
E. _____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Eliot Kimber eliot.kimber@servicenow.com Date: Tuesday, February 1, 2022 at 12:41 PM To: Christian Grün christian.gruen@gmail.com Cc: basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations I see. I guess I assumed that a module-level variable would be invariant across the transactions, but clearly not (and I didn’t notice the milliseconds difference).
With a static database value then my tests do pass.
Cheers,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Christian Grün christian.gruen@gmail.com Date: Tuesday, February 1, 2022 at 12:35 PM To: Eliot Kimber eliot.kimber@servicenow.com Cc: basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations [External Email]
Hi Eliot,
The database is created but is not dropped and obviously not updated.
If my test is legit then this suggests that before-module and after-module are not separate transactions.
As each function will be evaluated separately, the current dateTime value will be different for each function call. Your code should work if you replace current-dateTime() by current-date() or any other static value.
Best, Christian
within a single unit:test function that itself performs an updating function there’s no way to then evaluate the result of the update as any asserts will be in the same transaction.
That's where %unit:before and (more likely) %unit:after come in. You can't make the update and assertion within a single function, as you say, but the effective workaround is to do it after the fact, in a separate transaction.
Andrew
I can see how that would work but it would only make sense when you have exactly one unit:test in a module, as unit:before/after is run for each test in the module.
Seems like it would be useful to have a way to perform a transaction within a unit test and then do assertions as a follow-on transaction. But of course that ends up just being a convenience as opposed to having single-test modules, which is not the end of the world.
But if you could do that in a single unit test function context it starts to suggest that you could do it in a general XQuery processing context (similar to MarkLogic’s multi-transaction module extension to XQuery), but I have to assume that if that was a good idea the BaseX team would have already done it…
Cheers,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Andrew Sales andrew@andrewsales.com Date: Tuesday, February 1, 2022 at 1:35 PM To: Eliot Kimber eliot.kimber@servicenow.com Cc: basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations [External Email]
within a single unit:test function that itself performs an updating function there’s no way to then evaluate the result of the update as any asserts will be in the same transaction.
That's where %unit:before and (more likely) %unit:after come in. You can't make the update and assertion within a single function, as you say, but the effective workaround is to do it after the fact, in a separate transaction.
Andrew
Just confirm: while before-module and after-module are separate transactions, within a single unit:test function that itself performs an updating function there’s no way to then evaluate the result of the update as any asserts will be in the same transaction.
Exactly.
[…] it would only make sense when you have exactly one unit:test in a module, as unit:before/after is run for each test in the module.
You can also specify the name of a function along with %unit:before and %unit:after [1]:
module namespace _ = '_';
declare %unit:before('b') function _:before-b() { prof:dump('before b') }; declare %unit:test function _:a() { prof:dump('a') }; declare %unit:test function _:b() { prof:dump('b') };
Evaluating: "a" "before b" "b"
If you want to simulate updates, another solution is to use the copy/modify/return or update clauses [2]:
let $x := <x/> update { insert node 'x' into . } return unit:assert-equals(string($x), 'x')
[1] https://docs.basex.org/wiki/Unit_Module#unit:before [2] https://docs.basex.org/wiki/XQuery_Update#Non-Updating_Expressions
Thanks—that’s very helpful.
My unit tests are doing what they need to for now but will definitely want to refine them with this new knowledge.
Cheers,
E.
_____________________________________________ Eliot Kimber Sr Staff Content Engineer O: 512 554 9368 M: 512 554 9368 servicenow.comhttps://www.servicenow.com LinkedInhttps://www.linkedin.com/company/servicenow | Twitterhttps://twitter.com/servicenow | YouTubehttps://www.youtube.com/user/servicenowinc | Facebookhttps://www.facebook.com/servicenow
From: Christian Grün christian.gruen@gmail.com Date: Wednesday, February 2, 2022 at 9:24 AM To: Eliot Kimber eliot.kimber@servicenow.com Cc: basex-talk@mailman.uni-konstanz.de basex-talk@mailman.uni-konstanz.de Subject: Re: [basex-talk] Techniques for Unit Testing Updating Operations [External Email]
Just confirm: while before-module and after-module are separate transactions, within a single unit:test function that itself performs an updating function there’s no way to then evaluate the result of the update as any asserts will be in the same transaction.
Exactly.
[…] it would only make sense when you have exactly one unit:test in a module, as unit:before/after is run for each test in the module.
You can also specify the name of a function along with %unit:before and %unit:after [1]:
module namespace _ = '_';
declare %unit:before('b') function _:before-b() { prof:dump('before b') }; declare %unit:test function _:a() { prof:dump('a') }; declare %unit:test function _:b() { prof:dump('b') };
Evaluating: "a" "before b" "b"
If you want to simulate updates, another solution is to use the copy/modify/return or update clauses [2]:
let $x := <x/> update { insert node 'x' into . } return unit:assert-equals(string($x), 'x')
[1] https://urldefense.com/v3/__https://docs.basex.org/wiki/Unit_Module*unit:bef...https://urldefense.com/v3/__https:/docs.basex.org/wiki/Unit_Module*unit:before__;Iw!!N4vogdjhuJM!SU4jXyuFok_Q006ccRre4Z1FgRQUxTg3o7EtB9sQO6XQHOr3EfrV7tn9osDaLac4rJ3Rag$ [2] https://urldefense.com/v3/__https://docs.basex.org/wiki/XQuery_Update*Non-Up...https://urldefense.com/v3/__https:/docs.basex.org/wiki/XQuery_Update*Non-Updating_Expressions__;Iw!!N4vogdjhuJM!SU4jXyuFok_Q006ccRre4Z1FgRQUxTg3o7EtB9sQO6XQHOr3EfrV7tn9osDaLadhvMzKWA$
basex-talk@mailman.uni-konstanz.de