It is quite hard to test couchdb currently. The main difficulties are related
to complex setup functions and weak isolation of test cases. There are some
functions in test_util
module which simplify setup a little bit but not by much.
The purpose of this proposal is to define some requirements for testing
infrastructure and proposing a solution which satisfy most of these requirements.
couch_test
app which would contain helper functions to make eunit
easier to use for couchdb testing. It would include solutions for some of
the identified earlier problems.cdt:setup
cdt:make_casses
src/couch_test/bin/cdt ci
couch_test/
+-- include/
+-- intercept.hrl
+-- cdt.hrl
+-- intercepts/
+-- setups/ - Would contain setup/teardown code for reuse
+-- src
+-- intercept.erl
+-- cdt.erl
+-- combinatorics.erl
+-- bin/
+-- cdt
+-- rebar.config
+-- etc/
+-- cdt.conf.example
+-- local.conf.example
+-- test_cluster.conf.example
Just to illustrate the idea here is the list of commands for cdt
(eventually).
cdt --help
-module(cdt_chttpd_setup).
-export([chttpd/4]).
chttpd(A, B, C, D) ->
{setup(A, B, C, D), teardown()}.
setup(A, B, C, D) ->
fun(Ctx) ->
%% use A and B to setup chttpd
%% store C and D in context for latter use in tests
NewCtx = update_ctx(Ctx, C, D),
{?MODULE, NewCtx}
end.
teardown() ->
fun(Ctx) ->
%% Clean up the things using Ctx to find out what to do
{?MODULE, Ctx}
end.
Setups could be composed into a chain
-module(my_unit_test).
-import_lib("cdt.hrl").
%% Imports are optional
-import([cdt_chttpd_setup, [chttpd/4]]).
-import([cdt_couch_setup, [couch/3]]).
-import([cdt_cluster_setup, [cluster/1]]).
-import([cdt_fault_setup, [disconnect/2, drop_packets/3]]).
setup(Type) ->
Chain = [
couch(Type, 1, 2),
chttpd(backdoor, foo, bar, baz),
cluster(3),
disconnect(1, 2),
drop_packets(2, 3, 30) %% 30% of packets to drop between db2 and db3 nodes
],
Args = [],
Opts = [],
cdt:setup(Chain, Args, Opts).
teardown(_Type, Ctx) ->
cdt:teardown(Ctx).
Since a crossplatform solution is required it is better to use something erlang based for fault injection. We could extend epmdpxy to simulate latency or connectivity problems. It should be extended in such a way to be able to selectively induce problems between specified nodes (without affecting the communication between test master and test slaves). In this case nodes should be started using:
ERL_EPMD_PORT=43690 erl
Don't log or produce noisy output by default. However should be able to control verbosity. It is also possible to split the output.
In some cases should be able to:
The easiest way to achieve both goals is using permanent intercept for couch_log.erl
.
Another approach could be a special couch_log
backend.
Store fixtures in tests/fixtures
of the applications we are testing.
We also might have some common fixtures in couch_test/fixtures
.
All fixtures should be templates.
couch_test
app would have some helpers to find and include the fixtures.
It would be helpful to support following types of fixtures:
<name>.script
- similar to file:script
but with template rendering<name>
- similar to file:consult
but with template renderingUse list of lists as a test name to determine if grouping is needed. For example:
apply_options_test_() ->
Funs = [fun ensure_apply_is_called/2],
Cases = combinatorics:powerset([pipe, concurrent]),
cdt:make_cases(
["apply options tests", "Apply with options: ~p"],
fun setup/1, fun teardown/2,
Cases, Funs).
This would generate following test cases
{
"apply options tests",
[
{
"Apply with options: []",
[
{
foreachx, fun setup/1, fun teardown/2,
[
{[], fun ensure_apply_is_called/2}
]
}
]
},
{
"Apply with options: [pipe]",
[
{
foreachx, fun setup/1, fun teardown/2,
[
{[pipe], fun ensure_apply_is_called/2}
]
}
]
},
{
"Apply with options: [concurent]",
[
{
foreachx, fun setup/1, fun teardown/2,
[
{[concurent], fun ensure_apply_is_called/2}
]
}
]
},
{
"Apply with options: [pipe, concurrent]",
[
{
foreachx, fun setup/1, fun teardown/2,
[
{[pipe, concurrent], fun ensure_apply_is_called/2}
]
}
]
},
]
In order to distinguish kinds of tests we would need to annotate test cases. We could use one of the following in order of my personal preference (any other ideas?):
Implement parse transform using merl
to support annotations:
-scope([integration, unit, cluster]).
my_tests_() - >
ok.
Split different kinds of tests into different modules and maybe keep them in different directories
cdt:make_cases
.Introduce naming convention for a test name
i_my_integration_tests_() -> ok
.u_my_unit_tests_() -> ok
.Have a module where we add every test case into approporiate scope.