Spaces:
Sleeping
Sleeping
rmm
commited on
Commit
·
be719f6
1
Parent(s):
d530f0c
test: test harness for the whale_viewer module, using AppTest
Browse files- for this module, the tests are split into backend (tested with plain
pytest) and frontend (tested with st.Apptest).
- the streamlit AppTest framework allows access and manipulation of
several streamlit elements - but not all (here, notably `st.image`).
- to do an isolated test of solely the `display_whale` function, I
also added a demo script. TODO: find a cleaner way to organise this.
- src/demo_wv.py +20 -0
- tests/test_demo_wv.py +129 -0
- tests/test_whale_viewer.py +50 -0
src/demo_wv.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# a minimal snippet for the whale viewer, for testing purposes
|
| 2 |
+
# - using AppTest to validate that the display_whale functionality
|
| 3 |
+
# is ok
|
| 4 |
+
# - currently placed in the src directory (not optimal) because
|
| 5 |
+
# I couldn't get pytest to pick it up from the tests directory.
|
| 6 |
+
# - TODO: find a cleaner solution for organisation (maybe just config to pytest?)
|
| 7 |
+
|
| 8 |
+
import streamlit as st
|
| 9 |
+
import whale_viewer as sw_wv
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# a menu to pick one of the images
|
| 13 |
+
title = st.title("Whale Viewer testing")
|
| 14 |
+
species = st.selectbox("Species", sw_wv.WHALE_CLASSES)
|
| 15 |
+
|
| 16 |
+
if species is not None:
|
| 17 |
+
# and display the image + reference
|
| 18 |
+
st.write(f"Selected species: {species}")
|
| 19 |
+
sw_wv.display_whale([species], 0, st)
|
| 20 |
+
|
tests/test_demo_wv.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from streamlit.testing.v1 import AppTest
|
| 2 |
+
import pytest # for the exception testing
|
| 3 |
+
|
| 4 |
+
import whale_viewer as sw_wv # for data
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_selectbox_ok():
|
| 8 |
+
'''
|
| 9 |
+
test the snippet demoing whale viewer - relating to AppTest'able elements
|
| 10 |
+
|
| 11 |
+
we validate that
|
| 12 |
+
- there is one selectbox present, with initial value "beluga" and index 0
|
| 13 |
+
- the two markdown elems generated dynamically by the selection corresponds
|
| 14 |
+
|
| 15 |
+
- then changing the selection, we do the same checks again
|
| 16 |
+
|
| 17 |
+
- finally, we check there are the right number of options (26)
|
| 18 |
+
|
| 19 |
+
'''
|
| 20 |
+
at = AppTest.from_file("src/demo_wv.py").run()
|
| 21 |
+
assert len(at.selectbox) == 1
|
| 22 |
+
assert at.selectbox[0].value == "beluga"
|
| 23 |
+
assert at.selectbox[0].index == 0
|
| 24 |
+
|
| 25 |
+
# let's check that the markdown is right
|
| 26 |
+
# the first markdown should be "Selected species: beluga"
|
| 27 |
+
assert at.markdown[0].value == "Selected species: beluga"
|
| 28 |
+
# the second markdown should be "### :whale: #1: Beluga"
|
| 29 |
+
print("markdown 1: ", at.markdown[1].value)
|
| 30 |
+
assert at.markdown[1].value == "### :whale: #1: Beluga"
|
| 31 |
+
|
| 32 |
+
# now let's select a different element. index 4 is commersons_dolphin
|
| 33 |
+
v4 = "commersons_dolphin"
|
| 34 |
+
v4_str = v4.replace("_", " ").title()
|
| 35 |
+
|
| 36 |
+
at.selectbox[0].set_value(v4).run()
|
| 37 |
+
assert at.selectbox[0].value == v4
|
| 38 |
+
assert at.selectbox[0].index == 4
|
| 39 |
+
# the first markdown should be "Selected species: commersons_dolphin"
|
| 40 |
+
assert at.markdown[0].value == f"Selected species: {v4}"
|
| 41 |
+
# the second markdown should be "### :whale: #1: Commersons Dolphin"
|
| 42 |
+
assert at.markdown[1].value == f"### :whale: #1: {v4_str}"
|
| 43 |
+
|
| 44 |
+
# test there are the right number of options
|
| 45 |
+
print("PROPS=> ", dir(at.selectbox[0])) # no length unfortunately,
|
| 46 |
+
# test it dynamically intead.
|
| 47 |
+
# should be fine
|
| 48 |
+
at.selectbox[0].select_index(len(sw_wv.WHALE_CLASSES)-1).run()
|
| 49 |
+
# should fail
|
| 50 |
+
with pytest.raises(Exception):
|
| 51 |
+
at.selectbox[0].select_index(len(sw_wv.WHALE_CLASSES)).run()
|
| 52 |
+
|
| 53 |
+
def test_img_props():
|
| 54 |
+
'''
|
| 55 |
+
test the snippet demoing whale viewer - relating to the image
|
| 56 |
+
|
| 57 |
+
we validate that
|
| 58 |
+
- one image is displayed
|
| 59 |
+
- the caption corresponds to the data in WHALE_REFERENCES
|
| 60 |
+
- the url is a mock url
|
| 61 |
+
|
| 62 |
+
- then changing the image, we do the same checks again
|
| 63 |
+
|
| 64 |
+
'''
|
| 65 |
+
at = AppTest.from_file("src/demo_wv.py").run()
|
| 66 |
+
ix = 0 # we didn't interact with the dropdown, so it should be the first one
|
| 67 |
+
# could fetch the property - maybe better in case code example changes
|
| 68 |
+
ix = at.selectbox[0].index
|
| 69 |
+
|
| 70 |
+
elem = at.get("imgs") # hmm, apparently the naming is not consistent with the other AppTest f/w.
|
| 71 |
+
# type(elem[0]) -> "streamlit.testing.v1.element_tree.UnknownElement" haha
|
| 72 |
+
assert len(elem) == 1
|
| 73 |
+
img0 = elem[0]
|
| 74 |
+
|
| 75 |
+
# we can't check the image, but maybe the alt text?
|
| 76 |
+
#assert at.image[0].alt == "beluga" # no, doesn't have that property.
|
| 77 |
+
|
| 78 |
+
# for v1.39, the proto comes back something like this:
|
| 79 |
+
exp_proto = '''
|
| 80 |
+
imgs {
|
| 81 |
+
caption: "https://www.fisheries.noaa.gov/species/beluga-whale"
|
| 82 |
+
url: "/mock/media/6a21db178fcd99b82817906fc716a5c35117f4daa1d1c1d3c16ae1c8.png"
|
| 83 |
+
}
|
| 84 |
+
width: -3
|
| 85 |
+
'''
|
| 86 |
+
# from the proto string we can look for <itemtype>: "<value>" pairs and make a dictionary
|
| 87 |
+
import re
|
| 88 |
+
|
| 89 |
+
def parse_proto(proto_str):
|
| 90 |
+
pattern = r'(\w+):\s*"([^"]+)"'
|
| 91 |
+
matches = re.findall(pattern, proto_str)
|
| 92 |
+
return {key: value for key, value in matches}
|
| 93 |
+
|
| 94 |
+
parsed_proto = parse_proto(str(img0.proto))
|
| 95 |
+
# we're expecting the caption to be WHALE_REFERENCES[ix]
|
| 96 |
+
print(parsed_proto)
|
| 97 |
+
assert "caption" in parsed_proto
|
| 98 |
+
assert parsed_proto["caption"] == sw_wv.WHALE_REFERENCES[ix]
|
| 99 |
+
assert "url" in parsed_proto
|
| 100 |
+
assert parsed_proto["url"].startswith("/mock/media")
|
| 101 |
+
|
| 102 |
+
print(sw_wv.WHALE_REFERENCES[ix])
|
| 103 |
+
|
| 104 |
+
# now let's switch to another index
|
| 105 |
+
ix = 15
|
| 106 |
+
v15 = sw_wv.WHALE_CLASSES[ix]
|
| 107 |
+
v15_str = v15.replace("_", " ").title()
|
| 108 |
+
at.selectbox[0].set_value(v15).run()
|
| 109 |
+
|
| 110 |
+
elem = at.get("imgs")
|
| 111 |
+
img0 = elem[0]
|
| 112 |
+
print("[INFO] image 0 after adjusting dropdown:")
|
| 113 |
+
print(img0.type, type(img0.proto))#, "\t", i0.value) # it doesn't have a value
|
| 114 |
+
print(img0.proto)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
parsed_proto = parse_proto(str(img0.proto))
|
| 118 |
+
# we're expecting the caption to be WHALE_REFERENCES[ix]
|
| 119 |
+
print(parsed_proto)
|
| 120 |
+
assert "caption" in parsed_proto
|
| 121 |
+
assert parsed_proto["caption"] == sw_wv.WHALE_REFERENCES[ix]
|
| 122 |
+
assert "url" in parsed_proto
|
| 123 |
+
assert parsed_proto["url"].startswith("/mock/media")
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
|
tests/test_whale_viewer.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
from whale_viewer import format_whale_name
|
| 5 |
+
|
| 6 |
+
# testing format_whale_name
|
| 7 |
+
# - testing with valid whale names
|
| 8 |
+
# - testing with invalid whale names
|
| 9 |
+
# - empty string
|
| 10 |
+
# - with the wrong datatype
|
| 11 |
+
|
| 12 |
+
def test_format_whale_name_ok():
|
| 13 |
+
# some with 1 word, most with 2 words, others with 3 or 4.
|
| 14 |
+
assert format_whale_name("right_whale") == "Right Whale"
|
| 15 |
+
assert format_whale_name("blue_whale") == "Blue Whale"
|
| 16 |
+
assert format_whale_name("humpback_whale") == "Humpback Whale"
|
| 17 |
+
assert format_whale_name("sperm_whale") == "Sperm Whale"
|
| 18 |
+
assert format_whale_name("fin_whale") == "Fin Whale"
|
| 19 |
+
assert format_whale_name("sei_whale") == "Sei Whale"
|
| 20 |
+
assert format_whale_name("minke_whale") == "Minke Whale"
|
| 21 |
+
assert format_whale_name("gray_whale") == "Gray Whale"
|
| 22 |
+
assert format_whale_name("bowhead_whale") == "Bowhead Whale"
|
| 23 |
+
assert format_whale_name("beluga") == "Beluga"
|
| 24 |
+
|
| 25 |
+
assert format_whale_name("long_finned_pilot_whale") == "Long Finned Pilot Whale"
|
| 26 |
+
assert format_whale_name("melon_headed_whale") == "Melon Headed Whale"
|
| 27 |
+
assert format_whale_name("pantropic_spotted_dolphin") == "Pantropic Spotted Dolphin"
|
| 28 |
+
assert format_whale_name("spotted_dolphin") == "Spotted Dolphin"
|
| 29 |
+
assert format_whale_name("killer_whale") == "Killer Whale"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_format_whale_name_invalid():
|
| 33 |
+
# not so clear what this would be, except perhaps a string that has gone through the fucn alrealdy?
|
| 34 |
+
assert format_whale_name("Right Whale") == "Right Whale"
|
| 35 |
+
assert format_whale_name("Blue Whale") == "Blue Whale"
|
| 36 |
+
assert format_whale_name("Long Finned Pilot Whale") == "Long Finned Pilot Whale"
|
| 37 |
+
|
| 38 |
+
# testing with empty string
|
| 39 |
+
def test_format_whale_name_empty():
|
| 40 |
+
assert format_whale_name("") == ""
|
| 41 |
+
|
| 42 |
+
# testing with the wrong datatype
|
| 43 |
+
# we should get a TypeError - currently it fails with a AttributeError
|
| 44 |
+
@pytest.mark.xfail
|
| 45 |
+
def test_format_whale_name_none():
|
| 46 |
+
with pytest.raises(TypeError):
|
| 47 |
+
format_whale_name(None)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# display_whale requires UI to test it.
|