Skip to content

Commit

Permalink
add RSG support and few minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
RokuRnD committed Apr 5, 2017
1 parent 9c5599e commit a7e0e01
Show file tree
Hide file tree
Showing 67 changed files with 3,062 additions and 256 deletions.
580 changes: 352 additions & 228 deletions UnitTestFramework.brs

Large diffs are not rendered by default.

78 changes: 63 additions & 15 deletions docs/unit-test-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ The Logger object is used to output test results. It takes information from a Te

## Usage

###General

To use the unit test framework in your channel, place the framework files in the directory pkg:/source /testFramework/.

To run your unit tests, follow these instruction:
Expand All @@ -125,29 +127,19 @@ To run your unit tests, follow these instruction:

Create new .brs files for each test suite. The default prefix for test suite files is “Test\__”. You can use any prefix you want just don’t forget to specify it in the next step. In this new file define a function TestSuite__Main(). This function will return the test suite object. Create a new test suite object from BaseTestSuite:

```
_this = BaseTestSuite()_
```

then set its name

```
_this.Name = "MainTestSuite"_
```

Then add test cases:

```
this.addTest("CheckDataCount",TestCase\__Main_CheckDataCount)
```

this.addTest("CheckDataCount",TestCase\__Main_CheckDataCount).
TestCase\__Main_CheckDataCount is a test function.

```
Function TestCase__Main_CheckDataCount() as String
return m.assertArrayCount(m.mainData, 15)
return m.assertArrayCount(m.mainData, 15)
End Function
```

3) Run all your tests.

Expand All @@ -160,10 +152,11 @@ http://{Roku_box_IP_address}:8060/launch/dev?RunTests=true
* View the test results by opening a telnet console on the device on port 8085:
telnet {Roku box IP address} 8085.

To set verbosity level call Runner.logger.SetVerbosity(level). There are three verbosity levels in this framework:
To set verbosity level call Runner.logger.SetVerbosity(level). There are four verbosity levels in this framework:
* “0” – basic level
* “1” – normal level
* “2” – detailed level.
* “2” – detailed only for failed tests, normal for others
* “3” – detailed level.

You can overwrite printStatistic if you like, such as

Expand All @@ -187,7 +180,40 @@ Detailed level shows verbose statistics for every test suite and any error messa
![Detailed verbosity level](../images/detailed-verbosity-level.png)

**Figure 5:** Detailed verbosity level


###RSG

If you want to test RSG nodes you should:

1) Create new folder "tests" under components directory of your project: "pkg:/components/tests".
* This will be the root folder for RSG unit tests. In this folder you can also create subfolders for every test suite collection.

2) Create test suite files in "tests" folder or subfolders.

Create new .xml file for each node you want to test. This .xml file should be a node and it must extend the node you want to test. The default prefix for test nodes is "Test\__". You can use any prefix you want just don’t forget to specify it. File name must match node name. For each node you may create as many test suites as you wish. Details on how to create test suites you can find in the previous section. The only difference is that every test suite should have following prefix: TEST\_FILE\_PREFIX + NODE\_NAME + "\__" + TEST\_SUITE\_NAME, where default TEST\_FILE\_PREFIX is "Test\__", NODE\_NAME may be "MyNode" and TEST\_SUITE\_NAME can be "TestSuite1". So the name of a test suite file may look like this: "Test\__MyNode\__TestSuite1". In every test node (.xml file) you should:
* Add interface function for running tests. This function will be used by framework and must not be addressed in any other way:

<interface>
<function name="TestFramework__RunNodeTests"/>
</interface>

* Import unit test framework:

<script type="text/brightscript" uri="pkg:/source/testFramework/UnitTestFramework.brs"/>

* Import all the test suites, associated with this node.

<script type="text/brightscript" uri="pkg:/components/tests/Test__MyNode__TestSuite1.brs"/>
<script type="text/brightscript" uri="pkg:/components/tests/Test__MyNode__TestSuite2.brs"/>

Note that if you want to test RSG nodes, you must run tests only after screen is shown. If you had previously set up test runner, move it below screen showing statement.

m.screen.show()
if runUnitTests
runner = TestRunner()
runner.run()
end if

## Example


Expand Down Expand Up @@ -329,3 +355,25 @@ Function TestCase__Main_TestAddPrefixFunction__Passed() as string
return m.assertNotInvalid(result, "Input data is invalid. All values should be strings.")
End Function```
###Test node example
Here is shown what a test node for "MyNode" may look like:
<?xml version="1.0" encoding="UTF-8"?>
<!--********** Copyright 2017 Roku Corp. All Rights Reserved. **********-->
<component name="Test__MyNode" extends="MyNode" xsi:noNamespaceSchemaLocation="https://devtools.web.roku.com/schema/RokuSceneGraph.xsd">
<interface>
<function name="TestFramework__RunNodeTests"/>
</interface>
<script type="text/brightscript" uri="pkg:/source/testFramework/UnitTestFramework.brs"/>
<script type="text/brightscript" uri="pkg:/components/tests/Test__MyNode__TestSuite1.brs"/>
<script type="text/brightscript" uri="pkg:/components/tests/Test__MyNode__TestSuite2.brs"/>
</component>
7 changes: 7 additions & 0 deletions samples/RSGTestsExample/dev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
1. Run import_unit_test_framework.bat or ./import_unit_test_framework.sh or
place the UnitTestFramework.brs file to the testFramework folder manually.
2. Sideload the app to the box.
3. To run the tests, issue the following ECP command:
curl -d '' 'http://{Roku Device IP Address}:8060/launch/dev?RunTests=true'

Documentation: https://github.com/rokudev/unit-testing-framework
93 changes: 93 additions & 0 deletions samples/RSGTestsExample/dev/components/BaseScene.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
' ********** Copyright 2016 Roku Corp. All Rights Reserved. **********

'If the <script> element contains the definition of a function named init()
'that has no parameters, that function will be invoked after the XML file
'has been parsed and the nodes contained in the file have been created and had
'their fields set to the values in the XML.
'Typical uses of the init() function are to cache roSGNode values in the
'script's global variable that will be frequently used in other functions
'in the script, and to set up field observers that will call
'other BrightScript functions when the observed field changes value.
sub init()
'Specifies a graphic image file to be used for the Scene node background.
m.top.backgroundURI = "pkg:/images/background.jpg"

'set fake content before real content will be available
SetLoadingContent()
'creating new request node to get data from Api
m.request = createObject("roSGNode", "DataRequest")
'specifying function that will get proper data
m.request.name = "GetChannelContent"
'setting the observer to receive result
m.request.ObserveField("result", "OnContent")
'appending request to the main loop request queue
m.global.requestQueue.appendChild(m.request)
End sub

'This is a call-back function that is called when we set parsed and valid content
'to content field of the scene that is observed. (m.scene.Content = ...)
'Here we can check if our custom SlidingTemplate component is initialized and
'set it content.
Sub OnContent()
?"Initilization of SlidingTemplate"
m.top.content = m.request.result
m.SlidingTemplate = m.top.findNode("SlidingTemplate")
if m.SlidingTemplate <> invalid then
m.SlidingTemplate.content = m.top.content
'adding this contnet as stub content that will be used in other places,
'such as search screen
m.global.addField("sampleContent", "node", false)
m.global.sampleContent = m.top.content
m.SlidingTemplate.setFocus(true)
end if

end Sub

'this is base function for handling keypresses
'@param key [String] string representation of remote key
'@param pressed [boolean] tells if it's a press or release event
'@return boolean,if you return false this function will be called for you parent node
function OnKeyEvent(key as String, pressed as boolean) as boolean
?"OnKeyEvent [BaseScene]"
handled = false

if pressed
if key = "options"
'show global options dialog
showOptionsDialog()
handled = true
end if
end if

return handled
end function

'function for showing options dialog
sub showOptionsDialog()
dialog = CreateObject("roSGNode","Dialog")
dialog.title = "Options"
dialog.message = "This is options dialog"
dialog.buttons = ["Close"]
dialog.observeField("buttonSelected","onOptionsDialogButtonSelected")
'in order for dialog to work properly you have to set it to dialog field of scene
m.top.dialog = dialog
end sub

'handle dialog close
sub onOptionsDialogButtonSelected()
'check if we have close button pressed
if lcase(m.top.dialog.buttonGroup.buttons[m.top.dialog.buttonSelected]) = "close"
m.top.dialog.close = true
end if
end sub

' set fake content on the panel until real content will be populated
Sub SetLoadingContent()
fakeContent = CreateObject("roSGNode", "ContentNode")
fakeitem = CreateObject("roSGNode", "ContentNode")
fakeitem.title = tr("Loading") + "..."
fakeitem.id = "fake"
fakeContent.appendChild(fakeitem)
m.top.content = fakeContent
End Sub

42 changes: 42 additions & 0 deletions samples/RSGTestsExample/dev/components/BaseScene.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--********** Copyright 2016 Roku Corp. All Rights Reserved. **********-->

<!-- Component element defines the name of the component and the type of node whose behavior it extends.
This is the base node which handles home screen children.
Also this is the place where XSD is included. XSD requires that the order of elements be deterministic,
and so for validation to work, Scene Graph node elements must be contained in an element themselves). -->
<component name="BaseScene" extends="Scene" xsi:noNamespaceSchemaLocation="https://devtools.web.roku.com/schema/RokuSceneGraph.xsd">

<!-- Interface element defines a set of fields to be exposed by a component,
To allow instances of the component to be manipulated externally to the component,
while hiding details of the component implementation -->
<interface>
<!-- Add content field to specify the in-channel content
id - A string containing the name of the field.
type - A string containing the type of the field. In current example, content will be Scene Graph node object reference.
onChange - Sets an observer call-back function to be added for the top-level field.
The value of the attribute is a call-back function name in BrightScript code associated with the component.
This attribute is provided as a quick way to set up an observer call-back function for top-level fields.
It is equivalent to calling ObserveField() in BrightScript code associated with the component.-->
<field id="content" type="node"/>
</interface>

<!-- Script element allows the definition of functions to initialize the component, and to respond to events (including key events)
and field value changes. The BrightScript interfaces for the Scene Graph nodes used by BrightScript are the same interfaces
defined for roSGNode objects. -->
<script type="text/brightscript" uri="pkg:/components/BaseScene.brs"/>
<!-- We can easily manage commonly used utility functions by including them here. -->
<script type="text/brightscript" uri="pkg:/source/Utils.brs"/>

<!-- Children element contains all the Scene Graph node elements in a Scene Graph XML component. -->
<children>
<!--
Here we can add default BrightScript Components or use custom ones, like in the example below,
where we have SlidingTemplate component, that can be found in:
SlidingTemplate/SlidingTemplate.xml
SlidingTemplate/SlidingTemplate.brs
-->
<SlidingTemplate
id="SlidingTemplate"/>
</children>
</component>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
' ********** Copyright 2016 Roku Corp. All Rights Reserved. **********

Sub Init()
? "[CustomVideoNode] Init"
End Sub

'this is base function for handling keypresses
'@param key [String] string representation of remote key
'@param pressed [boolean] tells if it's a press or release event
'@return boolean, if you return false this function will be called for you parent node
Function OnKeyEvent(key, press)
result = false
if press
if key = "back"
m.top.visible = false
result = true
end if
end if
return result
End Function

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--********** Copyright 2016 Roku Corp. All Rights Reserved. **********-->

<!-- This is a panel that has a list inside of it and handles proper focusing of it.
List is accessible via interface field "list" so you can customize it easily. -->
<component name="CustomVideoNode" extends="Video" xsi:noNamespaceSchemaLocation="https://devtools.web.roku.com/schema/RokuSceneGraph.xsd">

<interface/>

<!-- Include referrence to other file with common functionality -->
<script type="text/brightscript" uri="pkg:/components/Nodes/CustomVideoNode/CustomVideoNode.brs"/>

<children/>

</component>
12 changes: 12 additions & 0 deletions samples/RSGTestsExample/dev/components/Nodes/DataRequest.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
' ********** Copyright 2016 Roku Corp. All Rights Reserved. **********

'singleton that initializes global request node to handle data requests
sub init()
if m.global.requestQueue = invalid
' add request node to global node to have a possibility to
' handle requests in main loop
m.global.addField("requestQueue", "node", false)
m.global.observeField("requestQueue", m.port)
m.global.requestQueue = createObject("roSGNode", "Node")
end if
end sub
21 changes: 21 additions & 0 deletions samples/RSGTestsExample/dev/components/Nodes/DataRequest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--********** Copyright 2016 Roku Corp. All Rights Reserved. **********-->

<!-- Node that represents data request and hanles received result -->
<component name="DataRequest" extends="Node" xsi:noNamespaceSchemaLocation="https://devtools.web.roku.com/schema/RokuSceneGraph.xsd">

<!-- Interface element defines a set of fields to be exposed by a component -->
<interface>
<!-- name of Api function to be called to receive data -->
<field id="name" type="string" />
<!-- pass the parameters to the Api function -->
<field id="params" type="assocarray" />
<!-- node that contains the resulting data -->
<field id="result" type="node" />
</interface>

<!-- Script element allows the definition of functions to initialize the component,
or include referrence to other file with common functionality -->
<script type="text/brightscript" uri="pkg:/components/Nodes/DataRequest.brs"/>

</component>
Loading

0 comments on commit a7e0e01

Please sign in to comment.