洞察纵观鸿蒙next版本,如何凭借FinClip加强小程序的跨平台管理,确保企业在数字化转型中的高效运营和数据安全?
672
2022-10-29
JCUnit - 基于JUnit的组合+基于模型的测试框架
Current version of JCUnit (0.8.x) is not compatible with older versions anymore. Users who need to use older ones, please refer to JCUnit-0.7.x.
In this document, the new version is called "JCUnit8" to tell it from older ones when necessary.
About 0.8.x
0.8.x and later is actively being developed right now and called JCUnit8. It requires Java SE8.
Major changes have been introduced in 0.8.x including move to Java 8, redesigning pipeline mechanism, renewed annotations, etc., most of which are incompatible with older versions.
Installation and how to use it.
Please use following dependencies to use JCUnit8.
NOTE: Please use JCUnit with JUnit 4.12 (or later). Otherwise JCUnit will not be able to execute tests for failing to instantiate parameterized runner.NOTE: Source code is found here:0.8.x-develop branch
New features
Followings are major new features that have been introduced to JCUnit in 0.8.x.
Annotation RenewalPipeline Renewal New pipeline designAPI New covering array engine "IPO-G+"
It will no longer be working with old Java SE6 and require Java 8. Neither it will not be compatible with older versions of JCUnit. The new version is called "JCUnit 8" named after both Java 8 and the version of JCUnit, 0.8.x. Not only that, some functionalities of older versions are removed from JCUnit8.
Annotation Renewal
In JCUnit8, its annotation system was completely renewed. You can find an example of the new annotation style here.
These days, people more and more tend to prefer '@Theories' style, where each test method takes parameters to each of whose actual value is assigned by a test runner, over conventional JUnit style. Newly introduced annotation system takes the approach.
Followings are the annotations used in the new style.
Defining a test class @Runwith(JCUnit8.class)@ConfigureWith Defining a test method @Test@Given@From Defining a test space @ParameterSource@Condition
These will be explained in this section.
Defining a test class
The new test runner is called JCUnit8 (com.github.dakusui.jcunit8.runners.junit4.JCUnit8X). When you use the runner, a configuration factory for the test class can be specified it with another annotation @ConfigureWith. With it, you can control various features of JCUnit such as negative test generation, test suite strength, etc.
@RunWith(JCUnit8.class) @ConfigureWith(BankAccountExample.BankAccountConfigFactory.class) public class BankAccountExample { ...
You can also configure a class from which parameters, constraints, and non-constraint conditions are generated using parameterSpace attribute.
Defining a test method
Methods annotated with @Test are considered to be test methods, but unlike conventional JUnit test methods, they can take parameters. But each of those parameters must be annotated with @From, which specifies how actual argument values of the parameter should be supplied.
@Test @Given("overdraftNotHappens") public void whenPerformScenario$thenBalanceIsCorrect( @From("scenario") List
@From annotations specify a name of method defined from which actual parameter values should be generated. Those methods must be defined in a class specified by @ConfigureWith#parameterSpace.
NOTE: Unlike previous versions, you will not need to use @Uses annotations anymore because the factors used in your test method are already declared by parameter definitions.
Associating test cases and test oracles
Expected behaviours of test cases can be different depending on inputs of them. E.g., when a sequence of bank account operations is executed, the expected outcome will be different depending on whether an overdraft happens or not.
In JCUnit8, this can be expressed by using @Given annotation.
A @Given annotation specifies a condition on which this test method should be executed. In the example above, the test method whenPerformScenario$thenBalanceIsCorrect will be invoked when (and only when) a method overdraftNotHappens, defined in the class specified by @ConfigureWith#parameterSpace attribute, returns true.
Currently JCUnit allows you to create composite conditions using three operators, AND, OR, and NOT from simple ones. With them, you should be able to express any boolean forms in theory (because you can transform any boolean form into DNF)
But parentheses cannot be used.
If you want to express ANDed conditions, you can do it by following.
@Test @Given("condition1&&condition2") public void aTestMethod(...) { ...
To express ORed conditions,
@Test @Given({"condition1", "condition2"}) public void aTestMethod(...) { ...
And to negate a condition, you can do
@Test @Given({"!condition1", "condition2&&!condition3"}) public void aTestMethod(...) { ...
As mentioned already, parentheses are not supported and you cannot write a condition like this.
@Given("(condition1||!condition2)&&condition3")
You will need to rewrite this to following.
@Given({"condition1&&condition3", "!condition2&&condition3"})
Defining a test space
The model from which JCUnit generates test cases consists of three elements which are
ParametersConstraintsTest oracles
Test oracles are defined as methods in a test class and associated with test cases using @Given annotations. In this section it will be discussed how to define parameters and constraints.
Defining a parameter
When you can list actual values of a parameter and it's sufficient, you can (and should) use "Simple" parameter model. Following is an example.
@ParameterSource public Simple.Factory
The annotation @ParameterSource tells JCUnit that it is a method that supplies actual values of parameter depositAmount (method name). @From annotations reference methods defined in a class that implements Config.Factory and is referred to by @ConfiguredWith annotation.
A method annotated with @ParameterSource must return a factory of a parameter class. And the parameter object created by the returned factory should hold actual values to be used in the generated test suite.
When you want to use a simple parameter, it can be done just by doing
return Simple.Factory.of(asList(100, 200, 300, 400, 500, 600, -1));
The values 100, 200, 300, ..., passed to (Arrays.)asList are values that you can use the parameter depositAmount.
Defining a constraint and a condition
To define a constraint, you can do following.
@Condition(constraint = true) public boolean overdraftNotHappens( @From("scenario") List
When you want to make it a non-constraint-condition, you can omit the attribute constraint or explicitly set the value to false.
@Condition(constraint = false) public boolean overdraftNotHappens(
NOTE: Unlike previous versions, you will not need to use @Uses annotations anymore because the factors used in your test method are already declared by parameter definitions.
Pipeline Renewal
The test suite generation pipeline of JCUnit was completely redesigned.
In the new JCUnit style, users will define "parameters" using models, such as FSM or Regex, and constraints over them first, and then they will be converted into factors and constraints.
One parameter can be turned into multiple factors. For instance, from a regex A(B|C){0,3} parameter can be turned into 10 factors and 9 constraints that look like following.
REGEX:regex1:alt-5:[(VOID), [B], [C]] REGEX:regex1:alt-6:[(VOID), [B], [C]] ... REGEX:regex1:cat-14:[[[A], Reference:
After all the parameters and constraints are converted to factors and constraints, factors and constraints are grouped based on how constraints are referring to factors so that each group can be processed independently.
Overview
Users sometimes want to define constraints that involve non-simple parameters. For instance, if you define a parameter to describe a sequence of operations using a regular expression model, maybe you want to exclude test cases where overdrafts happen. To make it possible to generate tests from such a model, JCUnit first converts eah non-simple parameter which involves a constraint into a simple parameter. This conversion is done by a sub-pipeline called "Engine".
The box "Engine" will be discussed later in more detail.
"Engine"
The "Engine" stage is a main component of the entire test suite generation pipeline. It takes 'preprocessed' parameters and constraints and generates a covering array of required strength any of whose element does not violate any of given constraints.
This stage is not designed to handle parameters that are involved in any constraints. It is because that we speculated that, if such a parameter is included, the factors and constraints will not be partitioned efficiently and covering array generation will take impractically long time.
To address this concerning, the pipeline designed to 'preprocess' the non-simple parameters referenced by constraints to convert into simple ones by applying "Engine" process for each parameter as if it is an independent parameter space beforehand. (see "Overview")
After parameters and constraints are encoded into factors and constraints that can be handled by implementations of com.github.dakusui.jcunit8.pipeline.stages.Generator, they will then be 'partitioned'.
In 'partition' step, factors will be grouped by constraints that involve them, so that each group can be processed independently.
After all the groups are processed and therefore a small covering array is generated for each group, those groups will be joined. After all joining processes have finishes, you will have final covering array whose each tuple's values can be decoded to actual values of parameters.
New covering array engine: "IPO-G+"
In JCUnit8, a new covering array generator that can handle constraints, IPO-G+, was introduced.
Idea of the algorithm is simple. In conventional IPO-G algorithm, which is shown below, new values are assigned at some points annotated with (*1) - (*4). The IPO-G+ algorithm only searches for assignments which can become valid at these points.
Algorithm: IPOG-Test (int t , ParameterSet ps ) { 1. initialize test set ts to be an empty set 2. denote the parameters in ps , in an arbitrary order, as P1 , P2, ..., and Pn 3. add into test set ts a test for each combination of values of the first t parameters (*1) 4. for (int i = t + 1 ; i ≤ n ; i ++ ){ 5. let π be the set of t -way combinations of values involving parameter Pi and t -1 parameters among the first i – 1 parameters (*2) 6. // horizontal extension for parameter Pi 7. for (each test τ = (v 1 , v 2 , ..., v i-1 ) in test set ts ) { 8. choose a value vi of Pi and replace τ with τ’ = (v 1 , v 2 , ..., vi-1 , vi ) so that τ’ covers the most number of combinations of values in π (*3) 9. remove from π the combinations of values covered by τ’ 10. } 11. // vertical extension for parameter P i 12. for (each combination σ in set π ) { 13. if (there exists a test that already covers σ ) { 14. remove σ from π 15. } else { 16. change an existing test, if possible, or otherwise add a new test to cover σ and remove it from π (*4) 17. } 18. } 19. } 20. return ts; } See http://barbie.uta.edu/~fduan/ACTS/IPOG_%20A%20General%20Strategy%20for%20T-Way%20Software%20Testing.pdf
Checking if given assignments can become valid and iterating valid assignments are done by following method.
public static Stream
This method streams assignments of factors, involved and allowed by given constraints, not given by tuple.
NOTE: Right now performance of this algorithm is being optimized and validated. And it might be re-desinged based on findings from the optimization and validation.
Limitations
If you have multiple test methods in your test class from an IDE such as IntelliJ, you cannot run only one of them. JCUnit has a helper class to work around the situation. Please refer to Issue-125.
TODOs
ValidationsDefault value of '@ConfigureWith' annotation: If the test class is implementing Config.Factory interface, it might be good idea to use it as a value for '@ConfigureWith' annotation when it is absent.IPO-G+ performance improvements.FSM feature has already been implemented, but not yet tested in 0.8.x line.Make the pipeline execute its internal processes where possible.
References
JCUnit wikiJCUnit blog; Japanese; 日本語Test Design as Code: JCUnit; A paper on this product; Presented at IEEE International Conference on Software Testing 2017
Copyright and license
Copyright 2013 Hiroshi Ukai.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
http://apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~