Skip to content

Commit

Permalink
New keywords with assertion engine and require min. Python 3.8 and RF…
Browse files Browse the repository at this point in the history
… 5.0.1 (#215)

* Keyword 'Check Row Count' using assertion engine

* Deprecate old "row count" assertion keywords

* Link to Assertion Engine in docs

* Deprecate duplicating "check exists" assertion keywords

* Log returned number of rows for possible debugging purposes

* Put Assertion Engine to dependencies

* Require min. Python 3.8 and RF 5.0.1

* improve common docs

* New keyword 'Check Query Result'

* Typo in docs
  • Loading branch information
amochin authored Aug 12, 2024
1 parent b916815 commit b52eb0a
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 18 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ jobs:
fail-fast: false
matrix:
include:
- os: 'ubuntu-latest'
python-version: '3.7'
rf-version: '3.2.2'
- os: 'ubuntu-latest'
python-version: '3.8'
rf-version: '4.1.3'
rf-version: '5.0.1'
- os: 'ubuntu-latest'
python-version: '3.9'
rf-version: '5.0.1'
Expand All @@ -29,7 +26,7 @@ jobs:
rf-version: '6.1.1'
- os: 'ubuntu-latest'
python-version: '3.12'
rf-version: '7.0a1'
rf-version: '7.0.1'
runs-on: ${{ matrix.os }}

steps:
Expand Down
10 changes: 6 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[build-system]
requires = [
"setuptools>=61.0",
"robotframework"
"robotframework>=5.0.1",
"robotframework-assertion-engine"
]
build-backend = "setuptools.build_meta"

Expand All @@ -11,10 +12,11 @@ authors = [{name="Franz Allan Valencia See", email="[email protected]"},
]
description = "Database Library for Robot Framework"
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.8.1"
dependencies = [
"robotframework",
"robotframework-excellib"
"robotframework>=5.0.1",
"robotframework-excellib",
"robotframework-assertion-engine"
]
classifiers = [
"Programming Language :: Python :: 3",
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
robotframework
robotframework-excellib
robotframework-assertion-engine
psycopg2-binary
pre-commit
build
twine
22 changes: 16 additions & 6 deletions src/DatabaseLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion):
The Database Library for [https://robotframework.org|Robot Framework] allows you to query a database and verify the results.
It requires an appropriate *Python module to be installed separately* - depending on your database, like e.g. `oracledb` or `pymysql`.
== Requirements ==
== Table of contents ==
%TOC%
= Requirements =
- Python
- Robot Framework
- Python database module you're going to use - e.g. `oracledb`
== Installation ==
= Installation =
| pip install robotframework-databaselibrary
Don't forget to install the required Python database module!
== Usage example ==
=== Basic usage ===
= Usage example =
== Basic usage ==
| *** Settings ***
| Library DatabaseLibrary
| Test Setup Connect To My Oracle DB
Expand Down Expand Up @@ -65,7 +68,7 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion):
| Check If Not Exists In Database ${sql}
|
=== Handling multiple database connections ===
== Handling multiple database connections ==
| *** Settings ***
| Library DatabaseLibrary
| Test Setup Connect To All Databases
Expand All @@ -89,7 +92,14 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion):
| Switch Database mysql
| Execute Sql String drop table XYZ
|
== Database modules compatibility ==
= Inline assertions =
Keywords that accept arguments ``assertion_operator`` <`AssertionOperator`> and ``expected_value``
perform a check according to the specified condition - using the [https://github.com/MarketSquare/AssertionEngine|Assertion Engine].
| Check Row Count SELECT id FROM person == 2
| Check Query Result SELECT first_name FROM person contains Allan
= Database modules compatibility =
The library is basically compatible with any [https://peps.python.org/pep-0249|Python Database API Specification 2.0] module.
However, the actual implementation in existing Python modules is sometimes quite different, which requires custom handling in the library.
Expand Down
116 changes: 115 additions & 1 deletion src/DatabaseLibrary/assertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
# 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.
from typing import Optional, Tuple
from typing import Any, Optional, Tuple

from assertionengine import AssertionOperator, verify_assertion
from robot.api import logger


Expand All @@ -30,6 +31,9 @@ def check_if_exists_in_database(
parameters: Optional[Tuple] = None,
):
"""
*DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
The deprecated keyword will be removed in future versions.
Check if any row would be returned by given the input ``selectStatement``. If there are no results, then this will
throw an AssertionError.
Expand Down Expand Up @@ -67,6 +71,9 @@ def check_if_not_exists_in_database(
parameters: Optional[Tuple] = None,
):
"""
*DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
The deprecated keyword will be removed in future versions.
This is the negation of `check_if_exists_in_database`.
Check if no rows would be returned by given the input ``selectStatement``. If there are any results, then this
Expand Down Expand Up @@ -106,6 +113,9 @@ def row_count_is_0(
parameters: Optional[Tuple] = None,
):
"""
*DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
The deprecated keyword will be removed in future versions.
Check if any rows are returned from the submitted ``selectStatement``. If there are, then this will throw an
AssertionError.
Expand Down Expand Up @@ -143,6 +153,9 @@ def row_count_is_equal_to_x(
parameters: Optional[Tuple] = None,
):
"""
*DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
The deprecated keyword will be removed in future versions.
Check if the number of rows returned from ``selectStatement`` is equal to the value submitted. If not, then this
will throw an AssertionError.
Expand Down Expand Up @@ -181,6 +194,9 @@ def row_count_is_greater_than_x(
parameters: Optional[Tuple] = None,
):
"""
*DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
The deprecated keyword will be removed in future versions.
Check if the number of rows returned from ``selectStatement`` is greater than the value submitted. If not, then
this will throw an AssertionError.
Expand Down Expand Up @@ -219,6 +235,9 @@ def row_count_is_less_than_x(
parameters: Optional[Tuple] = None,
):
"""
*DEPRECATED* Use new `Check Row Count` keyword with assertion engine instead.
The deprecated keyword will be removed in future versions.
Check if the number of rows returned from ``selectStatement`` is less than the value submitted. If not, then this
will throw an AssertionError.
Expand Down Expand Up @@ -247,6 +266,101 @@ def row_count_is_less_than_x(
msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'"
)

def check_row_count(
self,
selectStatement: str,
assertion_operator: AssertionOperator,
expected_value: int,
assertion_message: Optional[str] = None,
sansTran: bool = False,
alias: Optional[str] = None,
parameters: Optional[Tuple] = None,
):
"""
Check the number of rows returned from ``selectStatement`` using ``assertion_operator``
and ``expected_value``. See `Inline assertions` for more details.
Use optional ``assertion_message`` to override the default error message.
Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback.
Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
than one connection open.
Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different
depending on the database client).
Examples:
| Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *==* | 1 |
| Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *>=* | 2 | assertion_message=my error message |
| Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *inequal* | 3 | alias=my_alias |
| Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *less than* | 4 | sansTran=True |
| @{parameters} | Create List | John |
| Check Row Count | SELECT id FROM person WHERE first_name = %s | *equals* | 5 | parameters=${parameters} |
"""
logger.info(f"Executing : Check Row Count | {selectStatement} | {assertion_operator} | {expected_value}")
num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
return verify_assertion(num_rows, assertion_operator, expected_value, "Wrong row count:", assertion_message)

def check_query_result(
self,
selectStatement,
assertion_operator: AssertionOperator,
expected_value: Any,
row=0,
col=0,
assertion_message: Optional[str] = None,
sansTran: bool = False,
alias: Optional[str] = None,
parameters: Optional[Tuple] = None,
):
"""
Check value in query result returned from ``selectStatement`` using ``assertion_operator`` and ``expected_value``.
The value position in results can be adjusted using ``row`` and ``col`` parameters (0-based).
See `Inline assertions` for more details.
*The assertion in this keyword is type sensitive!*
The ``expected_value`` is taken as a string, no argument conversion is performed.
Use RF syntax like ``${1}`` for numeric values.
Use optional ``assertion_message`` to override the default error message.
Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback.
Use optional ``alias`` parameter to specify what connection should be used for the query if you have more
than one connection open.
Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different
depending on the database client).
Examples:
| Check Query Result | SELECT first_name FROM person | *contains* | Allan |
| Check Query Result | SELECT first_name, last_name FROM person | *==* | Schneider | row=1 | col=1 |
| Check Query Result | SELECT id FROM person WHERE first_name = 'John' | *==* | 2 | # Fails, if query returns an integer value |
| Check Query Result | SELECT id FROM person WHERE first_name = 'John' | *==* | ${2} | # Works, if query returns an integer value |
| Check Query Result | SELECT first_name FROM person | *equal* | Franz Allan | assertion_message=my error message |
| Check Query Result | SELECT first_name FROM person | *inequal* | John | alias=my_alias |
| Check Query Result | SELECT first_name FROM person | *contains* | Allan | sansTran=True |
| @{parameters} | Create List | John |
| Check Query Result | SELECT first_name FROM person | *contains* | Allan | parameters=${parameters} |
"""
logger.info(
f"Executing : Check Query Results | {selectStatement} | {assertion_operator} | {expected_value} | row = {row} | col = {col} "
)
query_results = self.query(selectStatement, sansTran, alias=alias, parameters=parameters)

row_count = len(query_results)
assert row < row_count, f"Checking row '{row}' is not possible, as query results contain {row_count} rows only!"
col_count = len(query_results[row])
assert (
col < col_count
), f"Checking column '{col}' is not possible, as query results contain {col_count} columns only!"

actual_value = query_results[row][col]
return verify_assertion(
actual_value, assertion_operator, expected_value, "Wrong query result:", assertion_message
)

def table_must_exist(
self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None
):
Expand Down
7 changes: 5 additions & 2 deletions src/DatabaseLibrary/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,11 @@ def row_count(
self.__execute_sql(cur, selectStatement, parameters=parameters)
data = cur.fetchall()
if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]:
return len(data)
return cur.rowcount
current_row_count = len(data)
else:
current_row_count = cur.rowcount
logger.info(f"Retrieved {current_row_count} rows")
return current_row_count
finally:
if cur and not sansTran:
db_connection.client.rollback()
Expand Down
28 changes: 28 additions & 0 deletions test/tests/common_tests/assertion_error_messages.robot
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,31 @@ Verify Row Count Is Greater Than X Fails With Message
... Row Count Is Greater Than X
... ${Existing Select} 1
... msg=${Error Message}

Check Row Count With Assertion Engine Fails
${expected value}= Set Variable 5
${expected error}= Catenate
... Wrong row count: '1' (int) should be '${expected value}' (int)
Run Keyword And Expect Error
... ${expected error}
... Check Row Count ${Existing Select} equals ${expected value}

Check Row Count With Assertion Engine Fails With Message
Run Keyword And Expect Error ${Error Message}
... Check Row Count ${Existing Select} less than 1
... assertion_message=${Error Message}


Check Query Result With Assertion Engine Fails
${expected value}= Set Variable ${5}
${expected error}= Catenate
... Wrong query result: '1' (int) should be '${expected value}' (int)
Run Keyword And Expect Error
... ${expected error}
... Check Query Result ${Existing Select} equals ${expected value}


Check Query Result With Assertion Engine Fails With Message
Run Keyword And Expect Error ${Error Message}
... Check Query Result ${Existing Select} less than ${1}
... assertion_message=${Error Message}
18 changes: 18 additions & 0 deletions test/tests/common_tests/basic_tests.robot
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ Retrieve Row Count
Log ${output}
Should Be Equal As Strings ${output} 2

Check Row Count With Assertion Engine
Check Row Count SELECT id FROM person == 2

Check Query Result With Assertion Engine
Check Query Result SELECT first_name FROM person contains Allan

Check Query Result With Assertion Engine - Different Row And Col
Check Query Result SELECT first_name, last_name, id FROM person >= ${2} row=1 col=2

Check Query Result With Assertion Engine - Row Out Of Range
Run Keyword And Expect Error Checking row '2' is not possible, as query results contain 2 rows only!
... Check Query Result SELECT first_name FROM person == Blah row=2

Check Query Result With Assertion Engine - Col Out Of Range
Run Keyword And Expect Error Checking column '5' is not possible, as query results contain 2 columns only!
... Check Query Result SELECT id, first_name FROM person == Blah col=5


Retrieve records from person table
${output}= Execute SQL String SELECT * FROM person
Log ${output}
Expand Down

0 comments on commit b52eb0a

Please sign in to comment.