Techniques for Unit Testing Updating Operations
Saturday, 29 January 2022 · 13 messages · last reply 2 Feb 2022
EK
Eliot Kimber29 Jan 2022 17:36 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>
AS
Andrew Sales30 Jan 2022 14:32 #
Quoted text · Eliot Kimber · 4 lines
> > 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.
AndrewQuoted text · Eliot Kimber · 1 line
>
EK
Eliot Kimber31 Jan 2022 05:25 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>
Quoted text · Andrew Sales · 10 lines
From: Andrew Sales <andrew at andrewsales.com> Date: Sunday, January 30, 2022 at 7:32 AM To: Eliot Kimber <eliot.kimber at servicenow.com> Cc: basex-talk at mailman.uni-konstanz.de <basex-talk at 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
AS
Andrew Sales31 Jan 2022 17:54 #
Quoted text · Eliot Kimber · 4 lines
> > 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://docs.basex.org/wiki/Unit_Module#unit:before
CG
Christian Grün31 Jan 2022 18:28 #
That’s precisely what happens. I have added this piece of information to our documentation. – Thanks, Christian
Quoted text · Andrew Sales · 10 lines
On Mon, Jan 31, 2022 at 5:54 PM Andrew Sales <andrew at 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://docs.basex.org/wiki/Unit_Module#unit:before
EK
Eliot Kimber1 Feb 2022 19:08 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>Quoted text · Christian Grün · 18 lines
From: Christian Grün <christian.gruen at gmail.com> Date: Monday, January 31, 2022 at 11:28 AM To: Andrew Sales <andrew at andrewsales.com> Cc: Eliot Kimber <eliot.kimber at servicenow.com>, basex-talk at mailman.uni-konstanz.de <basex-talk at 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 at 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:before__;Iw!!N4vogdjhuJM!QaGgfyLTQYT7a_H1uFaAoJJOpRxHYVQRJ1n_ZoFbEsnfmodXuczcubZkHyMDVWbI-ZMIIQ$<https://urldefense.com/v3/__https:/docs.basex.org/wiki/Unit_Module*unit:before__;Iw!!N4vogdjhuJM!QaGgfyLTQYT7a_H1uFaAoJJOpRxHYVQRJ1n_ZoFbEsnfmodXuczcubZkHyMDVWbI-ZMIIQ$>
CG
Christian Grün1 Feb 2022 19:34 #
Hi Eliot,
Quoted text · Eliot Kimber · 3 lines
> 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
EK
Eliot Kimber1 Feb 2022 19:41 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>
Quoted text · Christian Grün · 16 lines
From: Christian Grün <christian.gruen at gmail.com> Date: Tuesday, February 1, 2022 at 12:35 PM To: Eliot Kimber <eliot.kimber at servicenow.com> Cc: basex-talk at mailman.uni-konstanz.de <basex-talk at 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
EK
Eliot Kimber1 Feb 2022 19:48 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>
Quoted text · Eliot Kimber · 31 lines
From: Eliot Kimber <eliot.kimber at servicenow.com> Date: Tuesday, February 1, 2022 at 12:41 PM To: Christian Grün <christian.gruen at gmail.com> Cc: basex-talk at mailman.uni-konstanz.de <basex-talk at 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.com<https://www.servicenow.com> LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow> From: Christian Grün <christian.gruen at gmail.com> Date: Tuesday, February 1, 2022 at 12:35 PM To: Eliot Kimber <eliot.kimber at servicenow.com> Cc: basex-talk at mailman.uni-konstanz.de <basex-talk at 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
AS
Andrew Sales1 Feb 2022 20:35 #
Quoted text · Eliot Kimber · 5 lines
> > 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
EK
Eliot Kimber1 Feb 2022 21:22 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>
Quoted text · Andrew Sales · 9 lines
From: Andrew Sales <andrew at andrewsales.com> Date: Tuesday, February 1, 2022 at 1:35 PM To: Eliot Kimber <eliot.kimber at servicenow.com> Cc: basex-talk at mailman.uni-konstanz.de <basex-talk at 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
CG
Christian Grün2 Feb 2022 16:24 #
Quoted text · Eliot Kimber · 1 line
> 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.
Quoted text · Eliot Kimber · 1 line
> […] 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')EK
Eliot Kimber2 Feb 2022 16:37 #
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.com<https://www.servicenow.com>
LinkedIn<https://www.linkedin.com/company/servicenow> | Twitter<https://twitter.com/servicenow> | YouTube<https://www.youtube.com/user/servicenowinc> | Facebook<https://www.facebook.com/servicenow>
Quoted text · Christian Grün · 33 lines
From: Christian Grün <christian.gruen at gmail.com> Date: Wednesday, February 2, 2022 at 9:24 AM To: Eliot Kimber <eliot.kimber at servicenow.com> Cc: basex-talk at mailman.uni-konstanz.de <basex-talk at 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:before__;Iw!!N4vogdjhuJM!SU4jXyuFok_Q006ccRre4Z1FgRQUxTg3o7EtB9sQO6XQHOr3EfrV7tn9osDaLac4rJ3Rag$<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-Updating_Expressions__;Iw!!N4vogdjhuJM!SU4jXyuFok_Q006ccRre4Z1FgRQUxTg3o7EtB9sQO6XQHOr3EfrV7tn9osDaLadhvMzKWA$<https://urldefense.com/v3/__https:/docs.basex.org/wiki/XQuery_Update*Non-Updating_Expressions__;Iw!!N4vogdjhuJM!SU4jXyuFok_Q006ccRre4Z1FgRQUxTg3o7EtB9sQO6XQHOr3EfrV7tn9osDaLadhvMzKWA$>
Written for BaseX 9.6. BaseX may have changed since: please check the current documentation.
⚡Generated with XQuery