IDEMPIERE-4138 Move fitnesse integration from core

This commit is contained in:
hengsin 2019-12-25 21:46:01 +08:00
parent a8ad176e47
commit c6e1ea7748
5123 changed files with 2 additions and 111955 deletions

18
.gitignore vendored
View File

@ -8,7 +8,6 @@ org.adempiere.webstore/bin
org.idempiere.hazelcast.service.config/bin org.idempiere.hazelcast.service.config/bin
*/target/ */target/
runtime-server.product runtime-server.product
org.idempiere.fitnesse.server/FitNesseRoot
syntax: regexp syntax: regexp
adempiere adempiere
org.adempiere.install/lib org.adempiere.install/lib
@ -18,7 +17,6 @@ org.adempiere.ui.zk/bin/**
**/lib/*.jar **/lib/*.jar
**/seed/*.jar **/seed/*.jar
db/ddlutils/lib/*.jar db/ddlutils/lib/*.jar
fitnesse/fitnesse.jar
org.adempiere.ui.zk/labelapplet.jar org.adempiere.ui.zk/labelapplet.jar
lib/plugins lib/plugins
@ -36,7 +34,6 @@ org.compiere.db.postgresql.provider/bin/*.class
org.adempiere.ui.swing/bin/** org.adempiere.ui.swing/bin/**
org.adempiere.report.jasper.fonts/bin/** org.adempiere.report.jasper.fonts/bin/**
org.adempiere.tomcat.config/META-INF/tomcat/server.xml
org.apache.ecs/bin/*.class org.apache.ecs/bin/*.class
org.adempiere.replication/bin/*.class org.adempiere.replication/bin/*.class
org.adempiere.replication.server/bin/** org.adempiere.replication.server/bin/**
@ -50,9 +47,6 @@ org.adempiere.ui.swing.pluginlist/bin/*.class
org.adempiere.plugin.utils/bin/*.class org.adempiere.plugin.utils/bin/*.class
org.adempiere.webstore.servlet/bin/*.class org.adempiere.webstore.servlet/bin/*.class
org.adempiere.webstore.servlet/bin/*.tld org.adempiere.webstore.servlet/bin/*.tld
org.adempiere.eclipse.equinox.servletbridge/bin/*.class
org.adempiere.eclipse.equinox.http.servlet/bin/*.class
org.adempiere.eclipse.equinox.http.servletbridge/bin/*.class
org.adempiere.ui.zk.example/bin/* org.adempiere.ui.zk.example/bin/*
org.adempiere.webstore.resource/bin/* org.adempiere.webstore.resource/bin/*
org.adempiere.webstore.resource/bin/* org.adempiere.webstore.resource/bin/*
@ -75,24 +69,12 @@ hazelcast.xml
swingclient.product*.launch swingclient.product*.launch
server.product*.launch server.product*.launch
org.zkoss.zk.library/bin
.class .class
org.zkoss.zk.library/*/calendar*.jar
org.zkoss.zk.library/*/ckez*.jar
org.zkoss.zk.library/*/gmapsz*.jar
org.zkoss.zk.library/*/jruby*.jar
org.zkoss.zk.library/*/jython*.jar
org.zkoss.zk.library/*/timelinez*.jar
org.zkoss.zk.library/*/timeplotz*.jar
org.zkoss.zk.library/*/z*.jar
.buckminster/
jettyhome/ jettyhome/
.recommenders/ .recommenders/
packin packin
packout packout
RemoteSystemsTempFiles RemoteSystemsTempFiles
fitnesse/FitNesseRoot/FitLibraryWeb/*.zip
org.idempiere.javadoc/API org.idempiere.javadoc/API

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="fitnesse.jar" sourcepath="lib/fitnesse-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>fitnesse</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -1,36 +0,0 @@
This test is intended to create repeatable test cases for Average Invoice Costing, in order to make a refactor we need to:
* stabilize current code
* guarantee that refactoring don't break actual results
Brief description of the tests:
* login into !-GardenAdmin-!
* ensure that tenant is configured with avg inv costing
* create a product (random name/value)
* create a PO to purchase 50 products with value 10
* create a material receipt
* create a matchPO
* post the matchPO
* create an invoice
* post the invoice
* verify that avg cost=10 and qty=50
* create a POS sales order for 3 products
* post the shipment
* verify that shipment was posted with cost=10
* verify that avg cost=10 and qty=47
* create a PO to purchase 10 products with value 11
* create a material receipt based on the PO
* check the matchPO created
* post the matchPO
* verify that avg cost=10.175438596 and qty=57
* create an internal use inventory for 2 products
* post internal use
* verify that internal use used avg cost=10.175438596
* verify that avg cost=10.175438596 and qty=55
!include -c .CommonTests.LoginGardenAdmin
!include -c .CommonTests.ValidateClientSetOnAvgInv
!include -c .CommonTests.SetRandomName
!include -c .CommonTests.CreateProduct
!include -c .CommonTests.CreateProductPrice
!include -c .CommonTests.CreatePurchaseOrder
!include -c .CommonTests.CreateMaterialReceipt

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Test/>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1 +0,0 @@
!contents -R2 -g -p -f -h

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Suite>true</Suite>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,32 +0,0 @@
Create material receipt
!|Create Record|
|*Table* |M_InOut |
|ad_org_id |@AD_Org_ID@ |
|c_doctype_id |@Ref=C_DocType[Name='MM Receipt'].C_DocType_ID|
|c_bpartner_id |@c_bpartner.c_bpartner_id@ |
|c_bpartner_location_id|@Ref=C_BPartner_Location[C_BPartner_ID=@c_bpartner.c_bpartner_id@].C_BPartner_Location_ID |
|m_warehouse_id |@M_Warehouse_ID@ |
|salesrep_id |@Ref=AD_User[Name='GardenAdmin'].AD_User_ID |
|movementtype |V+ |
|*Save* | |
Create material receipt line
!|Create Record|
|*Table* |M_InOutLine |
|m_inout_id |@m_inout.m_inout_id@ |
|ad_org_id |@m_inout.AD_Org_ID@ |
|m_product_id|@m_product.M_Product_ID@|
|qtyentered |50 |
|c_uom_id |@m_product.c_uom_id@|
|m_locator_id|@Ref=M_Locator[M_Warehouse_ID=@M_Warehouse_ID@ AND IsDefault='Y'].M_Locator_ID|
|*Save* | |
Complete the material receipt
!|Run Process|
|*ProcessValue*|M_InOut Process |
|*RecordID* |@M_InOut.M_InOut_ID@|
|*DocAction* |CO |
|*Run* | |

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,8 +0,0 @@
!|Create Record|
|*Table* |M_Product|
|Name |@RandomName@|
|Value |@RandomName@|
|C_UOM_ID |@Ref=C_UOM[Name='Each'].C_UOM_ID|
|M_Product_Category_ID |@Ref=M_Product_Category[Name='Standard' and AD_Client_ID=@AD_Client_ID@].M_Product_Category_ID|
|C_TaxCategory_ID |@Ref=C_TaxCategory[Name='Standard' and AD_Client_ID=@AD_Client_ID@].C_TaxCategory_ID|
|*Save* | |

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,8 +0,0 @@
!|Create Record|
|*Table* |M_ProductPrice|
|M_PriceList_Version_ID |@Ref=M_PriceList_Version[IsActive='Y' AND M_PriceList_ID=(select m_pricelist_id from m_pricelist where name='Purchase')].M_PriceList_Version_ID|
|M_Product_ID |@m_product.m_product_id@|
|PriceLimit | 0 |
|PriceList | 0 |
|PriceStd | 0 |
|*Save* | |

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,41 +0,0 @@
Create purchase order
!|Read Record|
|*Table* |C_BPartner |
|c_bpartner_id |@Ref=C_BPartner[Name='Patio Fun, Inc.'].c_bpartner_id|
|*Read* | |
!|Create Record|
|*Table* |C_Order |
|ad_org_id |@AD_Org_ID@ |
|c_doctypetarget_id |@Ref=C_DocType[Name='Purchase Order'].C_DocType_ID|
|issotrx |N |
|salesrep_id |@Ref=AD_User[Name='GardenAdmin'].AD_User_ID|
|c_bpartner_id |@c_bpartner.c_bpartner_id@ |
|c_bpartner_location_id|@Ref=C_BPartner_Location[C_BPartner_ID=@c_bpartner.c_bpartner_id@].C_BPartner_Location_ID |
|paymentrule |B |
|m_warehouse_id |@M_Warehouse_ID@ |
|m_pricelist_id |@Ref=M_PriceList[Name='Purchase'].M_PriceList_ID |
|ad_user_id |@Ref=AD_User[Name='GardenAdmin'].AD_User_ID |
|*Save* | |
Create purchase order line
!|Create Record|
|*Table* |C_OrderLine |
|c_order_id |@C_order.c_Order_id@ |
|ad_org_id |@C_Order.AD_Org_ID@ |
|m_product_id|@M_Product.M_Product_ID@|
|qtyentered |50 |
|qtyordered |50 |
|priceactual |10 |
|c_uom_id |@m_product.c_uom_id@|
|*Save* | |
Complete the purchase order
!|Run Process|
|*ProcessValue*|C_Order Process |
|*RecordID* |@C_Order.C_Order_ID@|
|*DocAction* |CO |
|*Run* | |

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Test/>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,8 +0,0 @@
!|Login|
|User |GardenAdmin|
|Password |GardenAdmin|
|AD_Client_ID|@Ref=AD_Client[Value='GardenWorld'].AD_Client_ID|
|AD_Role_id |@Ref=AD_Role[Name='GardenWorld Admin'].AD_Role_ID|
|AD_Org_ID |@Ref=AD_Org[Name='HQ'].AD_Org_ID|
|M_Warehouse_ID|@Ref=M_Warehouse[Name='HQ Warehouse'].M_Warehouse_ID|
|*Login* | |

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,2 +0,0 @@
!|Set Variable|
|@RandomName@ |@random_string(TestAvg-,,4)|

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,2 +0,0 @@
!|Assert Variable|
|I|@SQL=select costingmethod from c_acctschema where c_acctschema_id = (select c_acctschema1_id from ad_clientinfo where ad_client_id=@AD_Client_ID@)|

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1 +0,0 @@
!contents -R2 -g -p -f -h

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,68 +0,0 @@
!1 Parsing
#
''!-FitLibrary-!'' turns the text in a table cell into a value, such as an ''int'', ''String'' or ''Date''.
However, sometimes:
* You want to handle text in a special way. Eg, you want to treat "" or "null" as a null value rather than as a normal ''String''.
* You want to use simple text to represent objects of your own types
''!-FitLibrary-!'' provides several mechanisms to cover these.
!3 Handling Special values with a ''Finder Method''
For example, if we want to treat the text "null" in a table cell as representing '''null''', rather than the String "null", we can use a finder method.
In your fixturing code, include the following method:
{{{ public String findString(String s) {
if ("null".equals(s))
return null;
return s;
}
}}} * ''!-FitLibrary-!'' finds a method corresponding to an action.
* It automatically parses the arguments, based on their types, and calls the method.
* It also uses this approach for the returned value, based on the return type.
By default, it uses built-in Parsers for the standard types. However:
* It first checks if there is a ''finder method'' for each type in the fixturing code (actually, generally in scope, as discussed below).
* For type ''T'', the ''finder method'' is ''findT(String s)'' with a return type.
* If a ''finder method'' exists, ''!-FitLibrary-!'' instead uses that to turn the text from the table cell into an object.
* This works for any table in ''!-FitLibrary-!''
Consider another example, where we want to handle ''!-TimeStamp-!'' values in a special way. We would include a ''finder method'' for it:
{{{ public TimeStamp findTimeStamp(String s) {
...
}
}}}This can then incorporate specialised code for handling odd ''!-TimeStamp-!''s, such as unknown ones.
There is a corresponding method for displaying an object, a ''show method''.
For example, if we wanted to show a '''null''' String as a empty String in any displays in a report, we could include the following method:
{{{ public String showString(String s) {
if (s == null)
return "";
return s;
}
}}}
#
Why is it called a ''finder method''.
I originally added this capability to allow for a domain-driven-design approach to storytests.
Sometimes you need to refer to an existing entity in a storytest. But it may not be possible to refer to it by a visible key, or the key may be too long.
So the idea is that you can invent names to refer to entities, such as "the customer", or "the third transaction". The ''finder method'' interprets these names for you, returning a reference to the appropriate object.
After I added this, I realised that it could also be used for parsing arbitrary text for types with concrete values, such as dates and user-defined types. So I now use finders for those cases as well.
What if a ''finder method'' needs to apply across lots of fixtures? Rather than repeating the finder code, you can include it in a custom global actions object. See .FitLibrary.SpecifiCations.AddingGlobalActionsObject for further details.
!3 Parsing with user-defined classes
#
Instead of using ''finder methods'', it's possible to define a static method in your class C of the form:
{{{ public static C parser(String s) {
...
}
}}}This is called by ''!-FitLibrary-!'' to turn text into an object of type C.

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,49 +0,0 @@
Dynamic variables allow for storytests to take different values, depending on the values that have been defined.
As a general rule, it's better to set a single value to a ''dynamic variable''.
* Rather than using an !-!include-! and revising the value of variables and dynamic variables to alter the contents of the !-!include-!, make use of ''defined actions'' with appropriate parameters.
Dynamic variables can be loaded from a properties file, as well as being set within storytests.
!2 Using Dynamic variables
* The value of a dynamic variable is accessed using the @{} form. Eg:
|''with''|//input|''set text''|@{simone.name}|
* They may be nested, such as within ''defined actions''. Eg, where ''person'' is a parameter, the value of the variable is resolved before resolving the dynamic variable value:
|''with''|//input|''set text''|@{@{person}.name}|
* To show the value of one or more dynamic variables:
|'''show'''|''get''|@{simone.name} with card @{simone.credit card.number}|
* If a dynamic variable doesn't have a value, the @{} form remains
!2 Changing Dynamic variables
* Load dynamic variables from a property file:
|''add dynamic variables from file''|c:/props.txt|
* Load dynamic variables from a unicode-based property file:
|''add dynamic variables from unicode file''|c:/props.uni|
* Set a dynamic variable to a string in a storytest:
|'''set'''|simone.name|''to''|Simone|
* Set a dynamic variable to the result of an action:
|'''set'''|simone.id|''add''|Simone|''to''|Persons|
* Set several dynamic variables at once in a storytest:
|''set variables''|
|simone.name|Simone|
|simone.credit card.number|41111111|
* Clear all dynamic properties:
|''clear dynamic variables''|
!2 Specification
|.FitLibrary.SpecifiCations.DynamicVariables|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,86 +0,0 @@
!1 2. In Fixturing/Code
#
Several techniques can be used at the fixturing/code level. The first, ''Print'', is quick, but can often be unsatisfactory.
#
----!2 2.1 Print
#
Use {{{ System.out.println();}}} to print information. For example, consider the following tables:
!|fitlibrary.specify.log.LogExampleFromFixture|
|''action that prints''|log4j|
|''action that prints''|<u>text</u>|
The underlying method as as follows:{{{ public boolean actionThatPrints(String s) {
System.out.println("Output "+s);
return true;
}
}}}The output from this is available after running a '''Test''' or '''Suite'''.
* The output is accessed from the ''Output Captured'' button in the top-right-hand corner of the report.
* You can see that if you run this as a '''Test'''.
* The ''Output Captured'' button is only shown in a report if there is some output.
The output is "escaped" so that any HTML is shown as the literal text rather than being displayed by the browser (eg, you see "<u>text</u>" rather than "''text''").
* But sometimes it can be more convenient to be able to structure displayed information, such as in a list or table.
The major disadvantage of the ''Print'' approach is that you need to flip back and forth between the storytest and the output in order to understand what's going on.
* This can be avoided by printing extra information so that you can see what-happens-when in relation to the tables.
* But that's extra work, repeating information and losing the advantage of the report information being displayed directly in the tables.
#
----!2 2.2 show()
#
Fixturing code can call the ''show()'' method to have information shown in the current row:
|''action that shows''|AFTER <u>all</u>|
The underlying method is in a subclass of ''!-DoFixture-!'':{{{ public void actionThatShows(String s) {
show(s);
}
}}}If your class is not a subclass of ''!-DoFixture-!'' (or equivalent) but is being used as a fixturing class (ie, so that ''!-FitLibrary-!'' auto-wraps it in a ''!-DoFixture-!''), you need to do a little more.
* You can access the ''show()'' method by having your class ''implement !-RuntimeContextual-!'' so that it has a runtime injected into it.
* The runtime has a ''show()'' method.
If your class is neither of those, then it's not possible to use this method. However, the some of the later techniques do apply.
#
----!2 2.3 showAfterTable()
#
Fixturing code can call the ''showAsAfterTable()'' method to have information shown after the table:
|''action that shows after table''|AFTER <u>all</u>|
The underlying method is in a subclass of ''!-DoFixture-!'':{{{ public void actionThatShowsAfterTable(String s) {
showAsAfterTable("My Log", s);
}
}}}If your class is not a subclass of ''!-DoFixture-!'' (or equivalent) but is being used as a fixturing class (ie, so that ''!-FitLibrary-!'' auto-wraps it in a ''!-DoFixture-!''), you need to do a little more.
* You can access the ''showAsAfterTable()'' method by having your class ''implement !-RuntimeContextual-!'' so that it has a runtime injected into it.
* The runtime has a ''showAsAfterTable()'' method.
If your class is neither of those, then it's not possible to use this method. However, the next two techniques do apply.
#
----!2 2.4 Request '''show''' when things go wrong
#
If an error is discovered by your fixture code, it can throw a ''!-FitLibraryShowException-!''. This will be caught by ''!-FitLibrary-!'' and the information will be shown after the current table.
This is useful when the information displayed is best structured with HTML.
For example, consider the following action:
|''action that shows on error''|bad data|
|''action that shows on error''|<ul><li>bad<li>data</ul>|
The underlying method is:{{{ public void actionThatShows(String s) {
throw new FitLibraryShowException(new Show(s));
}
}}}The class concerned can be any class at all. It does not need to be a subclass of ''!-DoFixture-!'', for example.
#
----!2 Next
#
On the [[next page of this tutorial][Log4jLogging]] we show how to handle logging with ''log4j'' from within code, and how to configure logging through storytest actions.

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,41 +0,0 @@
This is very similar to the last example, but a different logger is used: ''!-FixturingLogger-!''.
!|fitlibrary.specify.log.AppWithFixturingLogger|
The above fixturing code is as follows:
{{{ public class AppWithFixturingLogger {
private static Logger logger = FixturingLogger.getLogger(AppWithLog4j.class);
public boolean call() {
logger.trace("App called");
return true;
}
}
}}}
|''with fixturing logger''|
|''level''|TRACE|
|''show after''|true|
* On ''Test'', the following has text added after the table, because we've enabled ''show after'' and the level is TRACE:
|''call''|
|''with fixturing logger''|
|''level''|DEBUG|
* The following does not add text because the level is DEBUG, so trace() calls are not shown:
|''call''|
|''with fixturing logger''|
|''level''|TRACE|
|''show after''|false|
* The following does not add text because we've disabled ''show after'':
|''call''|
#
----!1 Next
#
Continue with the [[rest of the tutorial here][<LoggingTechniques.Log4jLogging]]

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,56 +0,0 @@
!|fitlibrary.specify.log.AppWithLog4j|
The above fixture refers to an object of class ''!-AppWithLog4j-!'', as follows:
{{{ public class AppWithLog4j {
private static Logger logger = Logger.getLogger(AppWithLog4j.class);
public boolean call() {
logger.trace("App called");
return true;
}
public void alsoShowFixturingInNormalLog(boolean delegate) {
FixturingLogger.setDelegatingToNormalLogger(delegate);
}
public void alsoShowFitLibraryInNormalLog(boolean delegate) {
FitLibraryLogger.setDelegatingToNormalLogger(delegate);
}
}
}}}The action ''call into application'' calls the method ''call()'' above.
The last 2 methods above illustrate how to redirect these other loggers to the normal log4j logger. This may be useful if you want to interweave normal logging with the other specialised loggers.
|''with log4j''|
|''show after''|true|
|''level''|TRACE|
* On ''Test'', the following has text added after the table, because we've enabled ''show after'' and the level is TRACE:
|''call''|
|''!-with FitLibrary logger-!''|
|''level''|TRACE|
|''call''|
|''!-with FitLibrary logger-!''|
|''level''|OFF|
|''with log4j''|
|''level''|DEBUG|
* The following does not add text because the level is DEBUG, so trace() calls are not shown:
|''call''|
|''with log4j''|
|''level''|TRACE|
|''show after''|false|
* The following does not add text because we've disabled ''show after'':
|''call''|
#
----!1 Next
#
Continue with the [[rest of the tutorial here][<LoggingTechniques.Log4jLogging]]

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Test>true</Test>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,54 +0,0 @@
!|fitlibrary.specify.log.AppWithLog4j|
We need to add the following table afer our main fixture, otherwise the ''!-configure FitLibrary logger-!'' will take control of the storytest.
|''!-with FitLibrary logger-!''|
|''show after''|true|
|''level''|TRACE|
The above fixture is as follows:
{{{ public class AppWithLog4j {
private static Logger logger = Logger.getLogger(AppWithLog4j.class);
public boolean call() {
logger.trace("App called");
return true;
}
}
}}}The action ''call into application'' calls the method ''call()'' above.
|''with log4j''|
|''show after''|true|
|''level''|TRACE|
* On ''Test'', the following has text added after the table, because we've enabled ''show after'' and the level is TRACE:
|''call''|
|''with log4j''|
|''level''|DEBUG|
* The following only adds logging from ''!-FitLibrary-!'' because the level of log4j is DEBUG, so trace() calls are not shown:
|''call''|
* Note that the following only turns off the logging into ''show after'' (by removing the appender); it does not have any other impact on logging with log4j.
|''with log4j''|
|''show after''|false|
|''!-with FitLibrary logger-!''|
|''show after''|false|
* The following does not add text because we've disabled ''show after'' for both loggers:
|''call''|
!2 Notice:
#
It pays to turn off all logging into ''show after'' at the end of the storytest, so that it doesn't affect other storytests.
#
----!1 Next
#
Return to the [[last page of the tutorial here][<LoggingTechniques.Log4jLogging]]

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,54 +0,0 @@
This is very similar to the last example, but we ensure that all logs are '''also''' routed to the normal log4j.
!|fitlibrary.specify.log.AppWithFixturingLogger|
We use this action to do it:
|''route logging''|
The above fixturing code is as follows:
{{{ public class AppWithFixturingLogger {
private static Logger logger = FixturingLogger.getLogger(AppWithLog4j.class);
public boolean call() {
logger.trace("App called");
return true;
}
public void routeLogging() {
Logger.getRootLogger().setLevel(Level.ALL);
Logger.getRootLogger().addAppender(new ConsoleAppender(new SimpleLayout()));
FixturingLogger.setDelegatingToNormalLogger(true);
}
}
}}}
|''with fixturing logger''|
|''level''|TRACE|
* On ''Test'', the following has text added after the table, because we've enabled ''show after'' and the level is TRACE:
|''call''|
|''with fixturing logger''|
|''level''|DEBUG|
* The following does not add text after the table because the level is DEBUG, so trace() calls are not shown there:
|''call''|
|''with fixturing logger''|
|''level''|TRACE|
|''show after''|false|
* The following does not add text after the table because we've disabled ''show after'':
|''call''|
#
!2 Routed logs
#
Notice that while we have reconfigured the ''fixturing logger'' at several points above, the normal ''log4j logger'' is unaltered and so it continues to log to the console.
See ''Output Captured'' after running this storytest to see the logs that were directed to the console.
#
----!1 Next
#
Continue with the [[rest of the tutorial here][<LoggingTechniques.Log4jLogging]]

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,127 +0,0 @@
!1 3. log4j logging
#
There are three aspects to logging with log4j when using ''!-FitLibrary-!''.
* Sections 3.3, 3.4 and 3.5 apply whether you're currently using log4j or not.
* Sections 3.1, 3.2 and 3.6 are only relevant to you if you're currently using log4j for logging in your application.
We assume familiarity with log4j. See the manual that's part of the log4j download for a good introduction.
If you use another logging system, and would like to be able to hook it into ''show after'' as in 3.1 below:
* Contact me and I can add a plugin mechanism to allow it. Rick Mugridge (rick at rimuresearch.com).
#
----!2 3.1 Appending log4j output after tables
#
If you're having trouble working out what's going on (or wrong) in a storytest and/or your code, you may find it convenient to have your log4j logs appended after !-FitLibrary-! tables.
To start this, add the following action to the top of your storytest, just after the table that specifies the main fixture (''false'' stops it):
|''with log4j''|
|''show after''|true|
This adds a special log4j ''Appender'' that will display log information in the report. (See the log4j docs for details of ''Appender''s if you're interested.)
* This will not affect other appenders that you use in your application, such as writing to a log file.
* You configure logging to be enabled in log4j in the usual manner in order for logs to appear.
When ''!-FitLibrary-!'' runs a storytest with ''show after'' turned on for log4j.
* All the logging information that arrives while a table is executing will be appended to that table.
* The logging is thread-safe, so it will handle logging from several threads at once.
* Any logging that occurs after the storytest finished will not be shown in the report.
Log4j levels can also be controller with the following actions, for example:
|''with log4j''|
|''level''|DEBUG|''for''|''com.corp.us.app.pck''|
or, for all:
|''with log4j''|
|''level''|TRACE|
Note that this is a global setting; it's not specific to a storytest and so will affect subsequent storytests in a suite. It's assumed that it will normally be used with a single storytest while investigating its behaviour.
With careful use of packages (or other log4j ''names''), you can easily tailor the logs that are collected and thus shown after tables.
See [[this page for an example][^ExampleLog4j]]
#
----!2 3.2 Logging to log4j from fixture code
#
Fixturing code can call the ''logText()'' method to have information logged with log4j (it will only show up if logging in normal log4j is enabled):
|''action that logs''|logging to go to log4j|
The underlying method is in a subclass of ''!-DoFixture-!'':{{{ public void actionThatLogs(String s) {
logText(s);
}
}}}#
----!2 3.3 Using !-FixturingLogger-! in your fixture
#
Here's how to use it in your fixture code if:
* You don't use log4j in your application code, or
* You prefer to keep your fixture and application logging completely separate
Similar actions to those above apply. However, it uses a separate "name space" so that its logging does not affect normal uses of log4j.
See [[this page for an example][>ExampleFixturingLogger]]
!-FixturingLogger-! is used by some of the fixtures in ''!-FitLibraryWeb-!'', such as for ''web services''.
#
----!2 3.4 Logging of ''!-FitLibrary-!'' itself
#
''!-FitLibrary-!'' uses log4j for logging as well. However, it uses another, separate "name space" so that its logging does not affect normal uses of log4j.
You may like to turn on the logging in ''!-FitLibrary-!'' so you can see what it's doing. For example:
|''!-with FitLibrary logger-!''|
|''level''|TRACE|
|''show after''|true|
See [[this page for an example][^FitLibraryLog4j]]
#
----!2 3.5 Configuring ''!-FitLibraryLogger-!'' and ''!-FixturingLogger-!'' from property files
#
Two property files in the ''fitnesse'' directory (at the same level as ''!-FitNesseRoot-!'') are used to configure ''!-FitLibraryLogger-!'' and ''!-FixturingLogger-!'':
* !-FitLibraryLogger.properties-!
* !-FixturingLogger.properties-!
These use the standard log4j property configuration format, but are instead applied to the specialised loggers ''!-FitLibraryLogger-!'' and ''!-FixturingLogger-!''.
These two files are both the same (as provided), as follows:
{{{ log4j.rootLogger=OFF,consoleAppender
log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=%r [%t] %-5p %c %x - %m%n
log4j.appender.fileAppender=org.apache.log4j.RollingFileAppender
log4j.appender.fileAppender.File=logForFitLibrary.txt
log4j.appender.fileAppender.MaxFileSize=10MB
log4j.appender.fileAppender.MaxBackupIndex=1
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%r [%t] %-5p %c %x - %m%n
}}}To turn on logging to the console, change the first line to:
{{{ log4j.rootLogger=ALL,consoleAppender
}}}To just turn on logging after tables, change the first line to:
{{{ log4j.rootLogger=ALL
}}}To just turn on file logging, change the first line to:
{{{ log4j.rootLogger=ALL,fileAppender
}}}This may be helpful if a '''Test''' or '''Suite''' doesn't complete, as it will log all the communications that ''!-FitLibraryServer-!'' has with ''!-FitNesse-!''.
You may want to also change the name of the logging file, and various other attributes. See the log4j documentation for details.
To turn on after-table, console and file logging, change the first line to:
{{{ log4j.rootLogger=ALL,consoleAppender,fileAppender
}}}#
----!2 3.6 Routing to Standard log4j
#
The logging from ''!-FitLibraryLogger-!'' and/or ''!-FixturingLogger-!'' can be routed to the standard log4j:
* So that you can include it in your usual log files, etc.
* This is done at the code level. See [[this page for an example][>RoutingLogger]].
#
!1 The End
#
That's the end of this tutorial on logging.

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Normal/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,90 +0,0 @@
It can be helpful to add logging to understand what's going on with storytests, and especially when something does not seem right.
In this tutorial we show how to add logging at the storytest and at the fixturing/code level. We start with the storytest level.
#
!1 1. In Storytests
#
Several techniques can be used at the storytest level without further support at the fixturing/code level.
#
----!2 1.1 Show with actions
#
Use the '''show''' special action to display the value of an action directly in the report.
For example, the action in the second table below results in "My result". This is displayed at the end of the row:
!|fitlibrary.specify.log.LogExample|
|'''show'''|''some action''|
Here the output of the action contains HTML ("<u>text</u>"), so it is rendered by the browser:
|'''show'''|''some action with html''|
This can be convenient if you want to organise the data or highlight it in some way. For example, tabular data may be displayed in an HTML table.
However, if you want to see the literal form, use '''show escaped''':
|'''show escaped'''|''some action with html''|
#
----!2 1.2 Show with dynamic variables
#
We can show the value of a dynamic variable as well. Eg:
|'''set'''|colour|''to''|red|
|'''show'''|''get''|roses are @{colour}|
|''get''|roses are @{colour}|'''is'''|roses are red|
#
----!2 1.3 Show After
#
This approach is convenient when you don't expect to look at the logged result very often, or it's long, so you don't want it visually cluttering up the report.
'''show after''' includes the result of the action in a folding area after the table.
|'''show after'''|some action|
|'''show after'''|''some action with html''|
To simply include some text, use the ''get'' action, which just returns that text supplied to it:
|'''show after'''|''get''|Some text|
#
----!2 1.4 Show After As
#
If there's lot of information, or different categories of information, it may be worth segmenting the '''show after''' logs into different folding areas.
'''show after as''' includes name of the folding area as the first argument and provided the result of the action (such as ''some action'' below) in a named folding area after the table.
|'''show after as'''|Other Log|some action|
|'''show after as'''|Further Log|''some action with html''|
|'''show after as'''|Other Log|''some action with html''|
To simply include some text, use:
|'''show after as'''|Other Log|''get''|Some text|
#
----!2 1.5 Log Text
#
If you want timing information included, or want to also log to a file (or elsewhere), this is a useful approach.
'''logged''' logs the result of the action (such as ''some action'' below) in a folding area after the table. See later in this tutorial (2 pages on) for how to have this also logged to a file (or elsewhere).
|'''logged'''|some action|
|'''logged'''|''some action with html''|
|'''logged'''|''some action with html''|
You can also put '''logged''' at the end of the row:
|some action|'''logged'''|
To simply include some text, use:
|'''logged'''|''get''|Some text|
or
|''log text''|Some text|
#
----!2 Next
#
On the [[next page of this tutorial][^FixtureLogging]] we show how to handle logging from the fixture/code level.

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,49 +0,0 @@
!2 When is it easy to migrate decision tables?
#
Slim storytests that only use decision tables can be easily migrated to ''!-FitLibrary-!'' when:
* The fixtures are written in Java
* Use is '''not''' made of the following in cells in the tables
* 3 < _ < 6
* ~=3.14
* < 5
* != 4
* $p=
* $p
* No use is made of the ''table()'' method in the fixture code
#
!2 Why bother?
#
Scenarios in Slim are limited to containing ''Script Tables'', so it doesn't help with repetition in decision tables.
''!-FitLibrary-!'' has something similar to scenarios, called [[''defined actions''][.FitLibrary.UserGuide.FitLibraryByExample.DefinedActions]], but they're superior as they can contain any tables.
''!-FitLibrary-!'' has other capability that you may like to use, such as ''!-FitLibraryWeb-!'' fixtures for testing web systems (''!-SpiderFixture-!''), email, PDFs, databases, etc.
#
!2 How to change
#
There are four simple step:
1. In the top-level page of your projects, add the following:
#
{{{!define TEST_RUNNER {fitlibrary.suite.FitLibraryServer}
}}}2. Remove the following:
#
{{{!define TEST_SYSTEM(slim}
}}}#
#
3. Add the classpath for fitlibrary.jar:
#
{{{!path fitlibrary.jar
}}}4. Finally, with your fixture classes, have them implement the Java interface ''!-RuleTable-!''.
* This is an empty ''interface'' that's used a marker so that ''!-FitLibrary-!'' can tell that the table should be treated as a decision table.
#
!2 For example
#
!|fitlibrary.specify.calculate.RuleTableExample|
|in|in2|out?|
|1|1|2|
|2|2|4|
|3|4|7|

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,35 +0,0 @@
A storytest may need to check/update different sub-systems or systems in order to carry out a test.
For example:
* It may be interacting through a web browser but also also checking a database.
* It may be calling a web service, checking email has been sent, and verifying that the PDF attached to the email is correct.
* It may be calling several different web services
* It may be interacting with several distinct subsystems, through their APIs
Let's continue with the browser/database example, for now:
... TO BE COMPLETED
But what happens if the actions of two flow objects are the same? This would happen if we were using two web services, for example.
As we see in the next example, it's necessary to explicitly select between them:
|''add''|!-fitlibrary.specify.select.FirstSelect-!|''as''|first|
|''add''|!-fitlibrary.specify.select.SecondSelect-!|''as''|second|
|''select''|first|
|''count''|'''is'''|1|
|''select''|second|
|''count''|'''is'''|2|
|''select''|first|
|''count''|'''is'''|1|
|''select''|second|
|''count''|'''is'''|2|

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,20 +0,0 @@
It's possible, of course, to use a fixture directly, by calling its methods in code.
However, it's assumed in ''!-FitLibrary-!'' that the underlying framework will inject a runtime into all fixture objects. The runtime contains such information as:
* Values of dynamic variables
* Values of time-outs
* Defined actions
* References to the global action object, which provides methods for dealing with dynamic variables and etc
If you try to use a fixture programmatically you may get an error "Runtime has not been injected into this". Here's the fix:
After you create your fixture object, explicitly inject a runtime into it. Eg:
----{{{MyDoFixture doF = new MyDoFixture();
doF.setRuntime(new RuntimeContextContainer());
}}}----
Now it's possible for this to be done automatically, and this will be added later. I don't want to do it automatically at this stage because it will hide any other potential problems with runtime.
!**> Note for Rick
!2 Don't rename this page as it's referenced from within the code at Traverse.getRuntimeContext()
**!

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,41 +0,0 @@
Sometimes it's handy to be able to run some code when a fixture (or ''!-DomainAdapter-!'') starts and/or stops running.
* For example, resources can be allocated at the start and released at the end.
It's also handy to be able to take special action if a storytest fails.
* For example, doing a screen dump of the browser interface when something has gone wrong.
These are handled with three methods that may be optionally included in a fixture (or ''!-DomainAdapter-!''):
* setUp()
* onFailure()
* tearDown()
All of these methods are called, if they exist, even if stop-on-error is set.
For the main fixture (or ''!-DomainAdapter-!'') that is used for the whole storytest:
* setUp() -- called at the beginning, before any table is interpreted.
* onFailure() -- called at the end of the storytest, after all tables have been interpreted, but only if an error/fail has occurred. Called just before tearDown().
* tearDown() -- called at the end of the storytest, after all tables have been interpreted.
Other fixtures may be created to interpret a single table (or part of a table). In that case:
* setUp() -- called at the beginning, before the table (or part of the table) is interpreted.
* onFailure() -- called at the end of the table, but only if an error/fail has occurred. Called just before tearDown().
* tearDown() -- called at the end of the table.
If the onFailure() method is called, as there has been a fail/error, and it returns a value:
* That returned value is added as text to the end of the first row of the last table that's been interpreted, and marked as shown.
* The text can be HTML, which will be rendered by the browser. This allows for screen dumps and etc to be created.
For suite fixtures, there are corresponding methods:
* suiteSetUp()
* suiteTearDown()
#
!3 See
#
.FitLibrary.SpecifiCations.DoTableFixturing.OnFailure for specifications of ''onFailure()''

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,27 +0,0 @@
|[[Multiple Flow Objects][>MultipleFlowObjects]]|''For when a storytest has to affect different parts of a system, or several systems. Eg, running through a web browser and updating/checking a database''|
|[[Defined Actions][.FitLibrary.UserGuide.FitLibraryByExample.DefinedActions]]|''For when we find we're repeating the same sequence of actions, and want to name them as a single action''|
|[[Dynamic Variables][>DynamicVariables]]|''Dynamic variables allow for storytests to take different values, depending on the values that have been defined.''|
Specific Sorts of Collections:
|[[An ordered list][.FitLibrary.UserGuide.FitLibraryByExample.OrderedList]]|''Where the order of the elements in a collection is important''|
|[[An unordered list][.FitLibrary.UserGuide.FitLibraryByExample.UnorderedList]]|''Where the order of the elements is irrelevant''|
|[[Handling Subsets][.FitLibrary.UserGuide.FitLibraryByExample.SubSet]]|''Checking a subset of a collection''|
|[[Handling Maps][.FitLibrary.UserGuide.FitLibraryByExample.MapHandling]]|''Checking when the underlying data is stored as a Map''|
|[[Handling Arrays][.FitLibrary.UserGuide.FitLibraryByExample.SimpleArray]]|''Checking when the underlying (simple) data is stored as an array''|
Implementation details:
|[[set up, tear down, on failure methods][>SetUpTearDownOnFailure]]|''How to have code run when a fixture or storytest starts or finishes, and when it finishes with errors''|
|[[Runtime injection][>RuntimeInjection]]|''Technical details for advanced use of fixtures''|
|[[Specialised handling of table cell text][>CustomParsing]]|''How to handle null strings, special values, etc with custom parsing''|
Migrating from Slim to ''!-FitLibrary-!''
|[[Migrating Decision Tables][^MigratingSlimDecisionTables]]|''How Slim storytests that only use decision tables can be very easily moved to ''!-FitLibrary-!'' ''|
Logging Techniques
|[[Logging Techniques][^LoggingTechniques]]|''How to log information to help understand what is happening''|
This will be expanded further.

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suite/>
<Suites/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,3 +0,0 @@
!|fitlibrary.eg.ChatSuite|
|''select or''|skipped|

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<LastModified>20070107135019</LastModified>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Versions/>
<WhereUsed/>
<saveId>1168131019531</saveId>
<ticketId>-7209608950152216224</ticketId>
</properties>

View File

@ -1,7 +0,0 @@
This defines another suite
* This page includes a symbolic link to .FitLibrary.SuiteFixtureExample so that we can access the same storytests as the other suite
* This page includes a different ..FitLibrary.AnotherSuiteFixtureExample.SuiteSetUp from the other suite
* In this case, it uses the same suite fixture but chooses a different keyword
* So a different subset of storytests are run
|!contents|

View File

@ -1,18 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<LastModified>20070107154306</LastModified>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suite/>
<SymbolicLinks>
<TheTests>.FitLibrary.SuiteFixtureExample</TheTests>
</SymbolicLinks>
<Versions/>
<WhereUsed/>
<saveId>1158223109396</saveId>
<ticketId>1881132187854299693</ticketId>
</properties>

View File

@ -1,25 +0,0 @@
A business process concerns the order that things happen, and their consequences.
Here we show how adding and multiplying on a calculator affect the total.
|''with a calculator''|
|''given the total is''|10|
|''+''|5|
|''*''|10|
|''the total now is''|150|
But we don't get any useful feedback (by default) if the expected value is wrong:
|''the total now is''|140|
So we can use the special '''is''' instead, as follows:
|''the total now''|'''is'''|140|
as it shows what the actual result was. After all, it's just as easy to make mistakes in tests.
Next: ChatBusinessProcessExample

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Test>true</Test>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,8 +0,0 @@
When a user enters a chat room, that doesn't exist, that action can't succeed (it's rejected):
|''with chat''|
|''given''|anna|''is a connected user''|
|'''reject'''|''when''|anna|''enters''|lotr|''room''|

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,14 +0,0 @@
When a user enters a chat room, they are then an occupant of that room.
|''with chat''|
|''given''|anna|''is a connected user''|
|''given''|lotr|''is a chat room''|
|''when''|anna|''enters''|lotr|''room''|
|''then occupants of''|lotr|''are''|
|''name''|
|anna|

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Test>true</Test>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,59 +0,0 @@
We repeat the table for convenience:
|''A 5% discount is provided whenever the total purchase is greater than $1,000''|
|''given amount''|''expected discount?''|
|0.00|0.00|
|999.95|0.00|
|1000.00|0.00|
|1010.00|50.50|
The class ''Global'' is loaded due to the first (infrastructure) table, that's included in .FitLibrary.BeginningTutorial.SuiteSetUp. The ''Global'' is used so that technical details such as class and package names are not visible in storytests.
Ths ''Global'' class includes the following method:
----{{{ public Rule a5PercentDiscountIsProvidedWheneverTheTotalPurchaseIsGreaterThanDollar1Comma000() {
return new DiscountRule();
}
}}}----This method is called when the first (visible) table of the storytest is executed. The first row of that table is turned into a method name (using extended camel casing).
And here's the code for ''!-DiscountRule-!'':
----{{{public class DiscountRule implements Rule {
private DiscountingApplication sut = new DiscountingApplication();
private double givenAmount;
public void setGivenAmount(double givenAmount) {
this.givenAmount = givenAmount;
}
public double getExpectedDiscount() {
return sut.expectedDiscount(givenAmount);
}
}
}}}----Because the ''!-DiscountRule-!'' class implements ''Rule'' (a marker interface), the rest of the table is treated as a rule table:
* The second row of the table names the input and expected columns. These are mapped directly to the setter and getter methods in ''!-DiscountRule-!''
* The third row leads to:
* The method ''setGivenAmount()'' being called with the value 0.00
* The method ''getExpectedDiscount()'' being called and it's result compared against the expected value of 0.00.
* As the expected value matches, it is coloured green
* The 4th, 5th and 6th rows are treated similarly to the third row, as decribed above
So the ''!-DiscountRule-!'' acts as a "fixture" to manage the test, calling into the appropriate method in the application under test.
The relevant code for the ''!-DiscountingApplication -!'' is as follows:
----{{{public class DiscountingApplication {
public double expectedDiscount(double amount) {
if (amount > 1000.00)
return amount * 0.05;
return 0.00;
}
}
}}}----In this case:
* ''!-DiscountRule-!'' creates the application object so that it can call into it.
* No further setup of the application is needed, because the discount only depends on the amount; it does not depend on the state of the application.
* Money is (badly) represented as a ''double'', rather than as a ''Money'' type.
We'll see variations on these later.
!3 Next
#
[[Second Rule Table Example][.FitLibrary.BeginningTutorial.SecondRuleTableExample]]

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,30 +0,0 @@
!3 Rule Table for Discounts
#
A rule table is a way of defining, and testing, a business rule by providing several examples.
In our first example, the discount is determined from a price (From ${fitBook}, p13):
|''A 5% discount is provided whenever the total purchase is greater than $1,000''|
|''given amount''|''expected discount?''|
|0.00|0.00|
|999.95|0.00|
|1000.00|0.00|
|1010.00|50.50|
The table format:
* The first row of the table above describes the business rule. This row is called the "header".
* The second row names the input and result columns. Here there is one input ("''given amount''") and one result ("''expected discount?''").
* The subsequent rows are examples. For example, when the ''given amount'' is 1010.00, the ''expected discount'' is 50.50.
* The input column is to the left of the results column. Results are distinguished with a ''?'' at the end of the name.
!3 Code
#
Here's the [[code for this example][^CodeForDiscount]]
!3 Next
#
SecondRuleTableExample

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,76 +0,0 @@
!3 Tables
#
Here's some of the tables again, for convenience:
|''Credit is allowed for worthy customers''|
|''months as customer''|''has paid reliably''|''balance owing''|''credit is allowed?''|
|14|yes|5000.00|yes|
|12|yes|5000.00|no|
|14|no|5000.00|no|
|14|yes|6000.00|no|
|''Credit limit depends on whether credit is allowed''|
|''credit is allowed''|''credit limit?''|
|yes|1000.00|
|no|0.00|
|''Credit is allowed for worthy customers who have been trading with us for''|14|''months''|
|''has paid reliably''|''balance owing''|''credit is allowed?''|
|yes|5000.00|yes|
|no|5000.00|no|
|yes|6000.00|no|
----As before, we start with the relevant methods in ''Global'':
{{{public class Global {
public Rule creditIsAllowedForWorthyCustomers() {
return new CreditRule();
}
public Rule creditLimitDependsOnWhetherCreditIsAllowed() {
return new CreditLimitRule();
}
public Rule creditIsAllowedForWorthyCustomersWhoHaveBeenTradingWithUsForMonths(int months) {
CreditRule creditLimitRule = new CreditRule();
creditLimitRule.setMonthsAsCustomer(months);
return creditLimitRule;
}
}
}}}----And here's the ''!-CreditRule-!'' class:
{{{public class CreditRule implements Rule {
private CreditApplication sut = new CreditApplication();
private int monthsAsCustomer;
private boolean hasPaidReliably;
private double balanceOwing;
public void setMonthsAsCustomer(int monthsAsCustomer) {
this.monthsAsCustomer = monthsAsCustomer;
}
public void setHasPaidReliably(boolean hasPaidReliably) {
this.hasPaidReliably = hasPaidReliably;
}
public void setBalanceOwing(double balanceOwing) {
this.balanceOwing = balanceOwing;
}
public boolean getCreditIsAllowed() {
return sut.creditPermitted(monthsAsCustomer, hasPaidReliably, balanceOwing);
}
public double getCreditLimit() {
return sut.creditLimit(monthsAsCustomer, hasPaidReliably, balanceOwing);
}
}
}}}----Again, this implements ''Rule'', the marker interface. It has setter methods for each of the given column values, and getter methods for the results columns.
It calls into the application to test it:
----{{{public class CreditApplication {
public boolean creditPermitted(int monthsAsCustomer, boolean hasPaidReliably,
double balanceOwing) {
return monthsAsCustomer > 12 && hasPaidReliably && balanceOwing < 6000.0;
}
public double creditLimit(int monthsAsCustomer, boolean hasPaidReliably,
double balanceOwing) {
if (creditPermitted(monthsAsCustomer, hasPaidReliably, balanceOwing))
return 1000.0;
return 0.00;
}
}
}}}----Note that, in practice, the application code could well be structured around Customer objects.

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,62 +0,0 @@
!3 A Rule Table for Credit Limits
#
In our next rule table example, the credit limit is determined from several inputs (From ${fitBook}, p17):
* Credit is allowed, up to an amount of $1000, for a customer who has been trading with us for more than 12 months, has paid reliably over that period, and has a current balance owing of less than $6,000.
|''Credit is allowed for worthy customers''|
|''months as customer''|''has paid reliably''|''balance owing''|''credit is allowed?''|''credit limit?''|
|14|yes|5000.00|yes|1000.00|
|12|yes|5000.00|no|0.00|
|14|no|5000.00|no|0.00|
|14|yes|6000.00|no|0.00|
As the business rule is so long, we've summarised it in the first row of the table.
Here we have three inputs and two results. We name the columns to be clear about their role.
#
!3 Splitting the business rule into two
#
Now, you may have noticed that whenever credit is allowed, the credit limit is fixed. So we can split the business rule into two rules:
* Credit is allowed for a customer who has been trading with us for more than 12 months, has paid reliably over that period, and has a current balance owing of less than $6,000.
* When credit is allowed, up to an amount of $1000 is permitted.
|''Credit is allowed for worthy customers''|
|''months as customer''|''has paid reliably''|''balance owing''|''credit is allowed?''|
|14|yes|5000.00|yes|
|12|yes|5000.00|no|
|14|no|5000.00|no|
|14|yes|6000.00|no|
|''Credit limit depends on whether credit is allowed''|
|''credit is allowed''|''credit limit?''|
|yes|1000.00|
|no|0.00|
#
!3 Splitting into separate tables for each major case
#
Sometimes it's convenient to have a table for each of the major cases of a business rule. For example, we could split into tables based on whether the customer has been trading with us for long enough, or not.
|''Credit is allowed for worthy customers''|
|''months as customer''|''has paid reliably''|''balance owing''|''credit is allowed?''|
|14|yes|5000.00|yes|
|14|no|5000.00|no|
|14|yes|6000.00|no|
We can now include the fixed value in the header of the table.
|''Credit is allowed for worthy customers who have been trading with us for''|14|''months''|
|''has paid reliably''|''balance owing''|''credit is allowed?''|
|yes|5000.00|yes|
|no|5000.00|no|
|yes|6000.00|no|
The first row now also provides an input that applies to all the data rows in that table.
The ''months'' input is in the second cell of that header row. As we'll see soon, such inputs are in evenly-numbered cells, with the odd cells containing explanatory text.
#
!3 Code
#
Here's the [[code for this example][^CodeForCreditLimits]]

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Test>true</Test>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,7 +0,0 @@
!**> technical infrastructure
The following is needed to run this as a test. It's discussed in the code details. It can be set up by someone who has some technical knowledge.
|''add global''|!-fitlibrary.tutorial.Global-!|
**!

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,49 +0,0 @@
!2 Introduction
#
In ''!-FitLibrary-!'', one or more tables are used to specify business rules and processes.
Tables are used for two important reasons:
* They help to clarify the language of the domain
* They provide a structure in which feedback is provided, such as whether a test passed or not.
As we'll see in the following, tables are used in several different ways.
#
!2 Business Rules
#
A rule table is a way of defining, and testing, a business rule by providing several examples.
|>FirstRuleTableExample|''Discount business rules''|
|>SecondRuleTableExample|''Credit limit rules''|
Another approach to rules tables is here: .FitLibrary.UserGuide.FitLibraryByExample.CalculationRule
#
!2 Business Processes
#
A business process concerns the order that things happen, and their consequences.
A workflow storytest shows what happens when an action is carried out on the system. The action could be carried out by:
* A user through a user interface
* Another system that sends or requests data and gives some signal
* An automatic background process that happens at certain times, such as every 10 minutes or at the end of the day
|^CalculatorBusinessProcessExample|''Steps in using a calculator''|
|>ChatBusinessProcessExample|''Steps in using a simple chat system''|
|^ChatBadPath|''When an action is expected to fail''|
Sometimes, some of the tables used in defining a business process will specify the details of a single business object, such as a Customer.
--- examples of setting up and checking individual business objects...
Or we may want to deal with collections of things, such as a list of customers who owe us more than $10,000.
... examples of setting up and checking collections...
For further details, see .FitLibrary.UserGuide and .FitLibrary.ReferenCe.DoTables
!2 Under Development
#
This tutorial is still under development. You'll find further information at .FitLibrary.UserGuide
^SuiteSetUp

View File

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suite/>
<Suites/>
<Versions/>
<WhereUsed/>
</properties>

View File

@ -1,44 +0,0 @@
!3 Build process
#
The Ant script ''build.xml'' provided in the top-level directory of the distributed release defines the build process.
This is organised for a Hudson (http://www.hudson-ci.org) pipeline of ''projects'' for continuous integration. These are defined in Hudson as follows:
* ''fitlibrary.build'' - polls git for a change and runs one Ant target:
* ''jar'' - compiles and creates ''fitlibrary.jar''. It copies this jar into the ''!-FitLibraryWeb-!'' directory. See below for directory-structure assumptions.
* ''fitlibrary.specifications'' - follows from the previous stage (if successful). Runs two Ant targets:
* ''delete-batch-runner-results-dir''
* ''batch-run-specifications'' -- runs the ''!-FitLibrary-!'' specifications in batch using ''!-FitLibraryRunner-!''
* ''fitlibrary.create.release.zip'' - follows from previous stage. Runs Ant target:
* ''create-release-zip'' - Copies all files for release into a separate folder and zips them up
* ''fitlibrary.check.release'' - follows from previous stage. Runs Ant target:
* ''release-check'' - runs the ''!-FitLibrary-!'' specifications in batch again from an unzipped copy of the release, integrated with a fresh copy of ''!-FitNesse-!''
* ''fitlibrary.final.release'' - follows from previous stage. Runs Ant target:
* ''final-release'' - Renames the zip created from the ''fitlibrary.create.release.zip'' stage to include the date. Within Hudson, ''final.release.dir'' is changed so that it appears in win7 space.
All of these Hudson projects share the workspace of ''fitlibrary.build''.
#
!3 Directory Structure Assumptions
#
The build process for ''!-FitLibrary-!'' assumes two git projects that are held in the same directory, as follows:
* ''fitlibrary''
* ''fitlibraryweb''
This allow the build process to copy any ''fitlibrary.jar'' update into ''../fitlibraryweb/fitnesse''.
Note that with the Hudson pipeline, this happens within Hudson's copy of the files when it clones them from git (which, currently, is unnecessary).
It is necessary to run the first stage, at least, of this process directly within the git folder so that:
* ''!-FitNesse-!'' and ''!-FitLibraryRunner-!'' can be used to test changes to ''!-FitLibrary-!''.
* ''!-FitNesse-!'' and ''!-FitLibraryRunner-!'' can be used to test ''!-FitLibraryWeb-!'' with the latest ''fitlibrary.jar''.
It is assumed that after the build succeeds, after any change to ''!-FitLibrary-!'':
* The updated ''fitlibrary.jar'' that was auto-copied into ''fitlibraryweb'' will be committed
* Thus kicking off the ''fitlibraryweb'' build.

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit>true</Edit>
<Files>true</Files>
<Properties>true</Properties>
<RecentChanges>true</RecentChanges>
<Refactor>true</Refactor>
<Search>true</Search>
<Versions>true</Versions>
<WhereUsed>true</WhereUsed>
</properties>

View File

@ -1,18 +0,0 @@
!2 Cross-reference of the use of fixtures and ${doFixture} actions in storytests in a suite
* This can be included in any page. The argument to ''xref'' is the full name of a suite.
* ''xref'' runs through all storytests in the suite and determines which actions are used in which storytest.
* It also walks over ''defined actions'' that are referenced in the suite and builds a cross-reference for those.
* It produces a table, with links to the relevant pages
* Because ''xref'' can't easily tell what rows are ${doFixture} actions, it marks those that may not be with a "~" and these appear at the end of the cross-reference table.
Run this test to get a cross-reference of the use of ${doFixture} actions in storytests in the suite mentioned
!|fitlibrary.DoFixture|
|''xref''|.FitLibrary.SpecifiCations.DefinedActions|
!3 Note that this doesn't work with all ''!-FitNesse-!'' versions, as it depends on trinidad code within ''!-FitNesse-!''.
It is known to work with fitnesse20090818

View File

@ -1,16 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Files/>
<Help/>
<Properties/>
<RecentChanges/>
<Refactor/>
<Search/>
<Suites/>
<Test/>
<Versions/>
<WhereUsed/>
<saveId>1255037127406</saveId>
<ticketId>8035047237467041367</ticketId>
</properties>

View File

@ -1,9 +0,0 @@
|!-fit.ActionFixture-!|
|start|!-BuyActions-!|
|check|total|00.00|
|enter|price|12.00|
|press|buy|
|check|total|12.00|
|enter|price|100.00|
|press|buy|
|check|total|112.00|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1083363523383</saveId>
<ticketId>8717702176529013388</ticketId>
</properties>

View File

@ -1,6 +0,0 @@
|!-BuyActionsWithColumn-!|
|price|total()|
|12.00|12.00|
|100.00|112.00|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1089169912703</saveId>
<ticketId>-364015300698810554</ticketId>
</properties>

View File

@ -1,11 +0,0 @@
|!-fit.ActionFixture-!|
|start|!-ChatServerActions-!|
|enter|user|anna|
|press|connect|
|enter|room|lotr|
|press|new room|
|press|enter room|
|enter|user|luke|
|press|connect|
|press|enter room|
|check|occupant count|2|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1083363450385</saveId>
<ticketId>5611163036537390981</ticketId>
</properties>

View File

@ -1,11 +0,0 @@
|!-fit.ActionFixture-!|
|start|!-BuyActions-!|
|check|total|00.00|
|enter|price|45.00|
|press|buy|
|check|total|55.00|
|enter|price|100.00|
|press|buy|
|check|total|145.00|
|enter|price|100.00|
|check|total|255.00|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1083363629662</saveId>
<ticketId>1418326968744198500</ticketId>
</properties>

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Suite/>
<Versions/>
<saveId>1076810628968</saveId>
<ticketId>1568487397853997998</ticketId>
</properties>

View File

@ -1,4 +0,0 @@
|!-fit.ActionFixture-!|
|start|!-ChatServerActions2-!|
|enter|room|lotr|
|check|occupant count|error|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1089594766984</saveId>
<ticketId>7180552580960954487</ticketId>
</properties>

View File

@ -1,4 +0,0 @@
|!-CalculateDiscount-!|
|amount|discount()|
|-100.00|'''error'''|
|1200.00|60.00|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1090017107937</saveId>
<ticketId>1261126190088502527</ticketId>
</properties>

View File

@ -1,7 +0,0 @@
|!-fit.ActionFixture-!|
|start|!-ChatServerActions2-!|
|enter|user|anna|
|press|connect|
|enter|room|lotr|
|press|enter room|
|check|room entered|no|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1095115437076</saveId>
<ticketId>2910920018774991765</ticketId>
</properties>

View File

@ -1,6 +0,0 @@
|!-StressTest-!|
|clients|max reaction time|within time()|
|1|10|true|
|10|10|true|
|100|30|true|
|500|60|true|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1089596463359</saveId>
<ticketId>-1391225353279735631</ticketId>
</properties>

View File

@ -1,6 +0,0 @@
|!-StressTest-!|
|clients|reaction time()|
|1|<10|
|10|<10|
|100|<30|
|500|<60|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1089611735218</saveId>
<ticketId>5175404554707105141</ticketId>
</properties>

View File

@ -1,4 +0,0 @@
|!-CalculateDiscount-!|
|amount|discount()|
|-100.00|0.00|
|1200.00|60.00|

View File

@ -1,11 +0,0 @@
<?xml version="1.0"?>
<properties>
<Edit/>
<Properties/>
<Refactor/>
<Search/>
<Test/>
<Versions/>
<saveId>1090015655093</saveId>
<ticketId>6952782694010871122</ticketId>
</properties>

Some files were not shown because too many files have changed in this diff Show More