New demo module to teach developers how to hack on the BlenderBIM Add-on.

This commit is contained in:
Dion Moult
2022-01-26 21:27:41 +11:00
parent 36a7985f5a
commit 1d47ad3cd4
17 changed files with 796 additions and 0 deletions
@@ -0,0 +1,45 @@
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# This allows us to write full integration tests that test how all systems work
# as a whole. Whilst other tests focus on portions of the software, these tests
# simulate what happens when a user opens Blender, presses buttons, and does
# things.
# These tests read like english. You can see all the possible sentences defined
# in test_feature.py. Most of the time, there is already a sentence defined for
# what you want to test.
@demo
Feature: Demo
# Every operator has at least one scenario associated with it to test it.
Scenario: Demonstrate hello world
Given an empty IFC project
When I press "bim.demonstrate_hello_world"
# Blender doesn't have a way of testing that things are visible in the
# interface and layout. We can check properties, and whats in the 3D
# scenegraph, but not layout. There is no "DOM" like in web applications.
# Too bad, we can't check the results, but we still write the test, that way
# we can still check for errors like crashes or Python errors, like a "smoke
# test".
Then nothing happens
# This operator has two scenarios because there are two possibilities of a user
# interacting with it.
Scenario: Demonstrate rename project - with a name provided
Given an empty IFC project
When I set "scene.BIMDemoProperties.name" to "Foobar"
And I press "bim.demonstrate_rename_project"
Then the object "IfcProject/Foobar" is an "IfcProject"
# This is the other possible scenario for the rename project operator.
Scenario: Demonstrate rename project - with no name
Given an empty IFC project
When I set "scene.BIMDemoProperties.name" to ""
And I press "bim.demonstrate_rename_project"
Then the object "IfcProject/My Project" is an "IfcProject"
+13
View File
@@ -209,6 +209,19 @@ def i_set_prop_to_value(prop, value):
exec(f"bpy.context.{prop} = {value}")
@given(parsers.parse('I set "{prop}" to ""'))
@when(parsers.parse('I set "{prop}" to ""'))
def i_set_prop_to_value(prop):
try:
eval(f"bpy.context.{prop}")
except:
assert False, "Property does not exist"
try:
exec(f'bpy.context.{prop} = r""')
except:
pass
@when(parsers.parse('I am on frame "{number}"'))
def i_am_on_frame_number(number):
bpy.context.scene.frame_set(int(number))
+7
View File
@@ -70,6 +70,13 @@ def debug():
prophet.verify()
@pytest.fixture
def demo():
prophet = Prophecy(blenderbim.core.tool.Demo)
yield prophet
prophet.verify()
@pytest.fixture
def drawing():
prophet = Prophecy(blenderbim.core.tool.Drawing)
+83
View File
@@ -0,0 +1,83 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Testing the core might seem strange if you haven't written this type of
# abstract test before. You essentially want to test that things are called in
# the right sequence. This seems almost like writing the code twice, like in
# double entry bookkeeping in accounting. However, it guards against typos,
# helps document intention of different logical flows, and ensures that all
# permutations of logic flows meet sanity checks.
# We always call the module we're testing the "test subject". This makes our
# tests simple to read: just look for where the "subject" is called!
import blenderbim.core.demo as subject
# These are like mocks, stubs, or spy objects. They don't do anything, but they
# let us check our test expectations.
from test.core.bootstrap import ifc, demo
# Let's test the hello world function.
class TestDemonstrateHelloWorld:
# This function has only one logical flow, there is no benefit to describing
# it further, so we have a test_run function. We need to specify all the
# mock objects we need in the signature.
def test_run(self, demo):
# We set an expectation that in this default sequence of events, the
# demo tool should have the set_message called with the "Hello, World!"
# string as its argument. Notice how our test also reads like English.
demo.set_message("Hello, World!").should_be_called()
# After we've finished specifying our test expectations, let's run the
# test subject!
subject.demonstrate_hello_world(demo)
# Another test, but this time with two logical flows.
class TestDemonstrateRenameProject:
# The default logical flow is where we rename the project successfully.
def test_renaming_the_project(self, ifc, demo):
# This time, we describe an expectation that the demo tool should have
# its get_project() function called with no attributes. When it is
# called, we expect it to return "project" as a string.
# This might sound strange. How is get_project implemented? Does it
# actually return a string? We don't know, and we don't care. That's a
# detail that our core isn't interested in. All our core is interested
# in is that we get back a project - we're arbitrarily using a string to
# represent it.
demo.get_project().should_be_called().will_return("project")
# Here, again, we're not interested in the details. However, we are
# interested in checking that the project we previously retrieved is
# passed verbatim into the Ifc tool.
ifc.run("attribute.edit_attributes", product="project", attributes={"Name": "name"}).should_be_called()
demo.clear_name_field().should_be_called()
demo.hide_user_hints().should_be_called()
# Let's test our test subject! Notice that just like the arbitrary
# string "project", we've specified an arbitrary input string of "name".
subject.demonstrate_rename_project(ifc, demo, name="name")
# One alternative flow is when no name is provided. What happens then?
def test_showing_a_hint_if_no_name_provided(self, ifc, demo):
demo.show_user_hints().should_be_called()
subject.demonstrate_rename_project(ifc, demo, name=None)
+87
View File
@@ -0,0 +1,87 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the BlenderBIM Add-on code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Because our tools have well defined, isolated functions, it means we can test
# them very easily in isolation. Tests are fun, fast, and easy to setup!
import bpy
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
from test.bim.bootstrap import NewFile
from blenderbim.tool.demo import Demo as subject
# Our first test is that our tool implements all the abstract methods defined by
# the `core/tool.py` interface. Anytime the core wants something that the tools
# don't provide, this test will catch it. This type of test comes for free in
# other languages, but not Python, so we test it explicitly.
class TestImplementsTool(NewFile):
def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.Demo)
# These are fairly boring tests. What it does demonstrate is that no matter how
# complex a BIM application can get, it can always be reduced down to small,
# easily tested functions. Note that these functions actually are concrete
# implementations, so that means that you need to use Blender headlessly to run
# these tests.
class TestClearNameField(NewFile):
def test_run(self):
bpy.context.scene.BIMDemoProperties.name = "name"
subject.clear_name_field()
assert bpy.context.scene.BIMDemoProperties.name == ""
class TestGetProject(NewFile):
def test_run(self):
# Sometimes, there is a bit of preparation work to setup a scenario that
# can be tested. In this case, we need to set an active IFC dataset with
# a project. This is normal.
ifc = ifcopenshell.file()
project = ifc.createIfcProject()
tool.Ifc.set(ifc)
assert subject.get_project() == project
class TestHideUserHints(NewFile):
def test_run(self):
bpy.context.scene.BIMDemoProperties.show_hints = True
subject.hide_user_hints()
assert bpy.context.scene.BIMDemoProperties.show_hints == False
class TestSetMessage(NewFile):
def test_run(self):
bpy.context.scene.BIMDemoProperties.message = ""
subject.set_message("message")
assert bpy.context.scene.BIMDemoProperties.message == "message"
class TestShowUserHints(NewFile):
def test_run(self):
bpy.context.scene.BIMDemoProperties.show_hints = False
subject.show_user_hints()
assert bpy.context.scene.BIMDemoProperties.show_hints == True