Tested Cell
Mix.install([{:kino, "~> 0.6.2"}, {:tested_cell, path: "."}])
Installation
Install the TestedCell project using Mix Install. Typically this belongs in the setup section at the top of every livebook. TestedCell relies on the :kino project.
Mix.install([{:kino, "~> 0.6.2"}, {:tested_cell, github: "BrooklinJazz/tested_cell"}])
Usage
Click the + Smart button to see the smart cells available in the livebook and select Tested Cell.
This creates a TestedCell like the one below. Double click the title Exercise to edit the title, assertions, and solution.
In the Assertions you can write ExUnit style assertions.
The Solution will appear after 3 failed attempts.
ExUnit.start(auto_run: false)
defmodule Assertion do
use ExUnit.Case
test "Exercise" do
try do
Process.flag(:trap_exit, true)
defmodule Math do
def double(int) do
int + 2
end
end
assert Math.double(2) == 4
assert Math.double(4) == 8
catch
error ->
flunk("""
Your solution threw the following error:
#{inspect(error)}
""")
:exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
flunk("""
GenServer crashed with the following error:
#{inspect(error)}
When it recieved: #{inspect(message)} #{message_type}
Likely you need to define the corresponding handler for #{inspect(message)}.
Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.
def handle_call(:message, _from, state) do
...
end
""")
:exit, error ->
flunk("""
Unhandled exit with the following error:
#{inspect(error)}
""")
after
# all warnings and errors are printed to the previous Kino Frame
# to avoid cluttering the test results display.
Process.sleep(10)
Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
end
end
end
ExUnit.run()
# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
defmodule Math do
def double(int) do
int + 2
end
end
TestedCell does not show the solution if it is empty.
ExUnit.start(auto_run: false)
defmodule Assertion do
use ExUnit.Case
test "Exercise" do
try do
Process.flag(:trap_exit, true)
author = nil
test = nil
assert author == "Patrick Rothfuss"
catch
error ->
flunk("""
Your solution threw the following error:
#{inspect(error)}
""")
:exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
flunk("""
GenServer crashed with the following error:
#{inspect(error)}
When it recieved: #{inspect(message)} #{message_type}
Likely you need to define the corresponding handler for #{inspect(message)}.
Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.
def handle_call(:message, _from, state) do
...
end
""")
:exit, error ->
flunk("""
Unhandled exit with the following error:
#{inspect(error)}
""")
after
# all warnings and errors are printed to the previous Kino Frame
# to avoid cluttering the test results display.
Process.sleep(10)
Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
end
end
end
ExUnit.run()
# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
author = nil
test = nil
Warnings
Unfortunately, in order to provide access to code defined inside of the TestedCell, we have to evaluate the code inside of the test, and outside of the test. This results in duplicating warning messages.
ExUnit.start(auto_run: false)
defmodule Assertion do
use ExUnit.Case
test "Exercise" do
try do
Process.flag(:trap_exit, true)
author = nil
test = nil
defmodule UnusedWarning do
def has(unused_variable) do
2 + 2
end
end
assert author == nil
catch
error ->
flunk("""
Your solution threw the following error:
#{inspect(error)}
""")
:exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
flunk("""
GenServer crashed with the following error:
#{inspect(error)}
When it recieved: #{inspect(message)} #{message_type}
Likely you need to define the corresponding handler for #{inspect(message)}.
Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.
def handle_call(:message, _from, state) do
...
end
""")
:exit, error ->
flunk("""
Unhandled exit with the following error:
#{inspect(error)}
""")
after
# all warnings and errors are printed to the previous Kino Frame
# to avoid cluttering the test results display.
Process.sleep(10)
Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
end
end
end
ExUnit.run()
# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
author = nil
test = nil
defmodule UnusedWarning do
def has(unused_variable) do
2 + 2
end
end
Error Handling
TestedCell catches crashes and exits to print the error nicely.
ExUnit.start(auto_run: false)
defmodule Assertion do
use ExUnit.Case
test "Exercise Example" do
try do
Process.flag(:trap_exit, true)
throw("Generic Throw")
catch
error ->
flunk("""
Your solution threw the following error:
#{inspect(error)}
""")
:exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
flunk("""
GenServer crashed with the following error:
#{inspect(error)}
When it recieved: #{inspect(message)} #{message_type}
Likely you need to define the corresponding handler for #{inspect(message)}.
Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.
def handle_call(:message, _from, state) do
...
end
""")
:exit, error ->
flunk("""
Unhandled exit with the following error:
#{inspect(error)}
""")
after
# all warnings and errors are printed to the previous Kino Frame
# to avoid cluttering the test results display.
Process.sleep(10)
Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
end
end
end
ExUnit.run()
# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
throw("Generic Throw")
ExUnit.start(auto_run: false)
defmodule Assertion do
use ExUnit.Case
test "Exercise" do
try do
Process.flag(:trap_exit, true)
raise "Generic Raise"
{:ok, pid} = Server.start_link([])
assert GenServer.call(pid, :NOTHING) == :green
catch
error ->
flunk("""
Your solution threw the following error:
#{inspect(error)}
""")
:exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
flunk("""
GenServer crashed with the following error:
#{inspect(error)}
When it recieved: #{inspect(message)} #{message_type}
Likely you need to define the corresponding handler for #{inspect(message)}.
Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.
def handle_call(:message, _from, state) do
...
end
""")
:exit, error ->
flunk("""
Unhandled exit with the following error:
#{inspect(error)}
""")
after
# all warnings and errors are printed to the previous Kino Frame
# to avoid cluttering the test results display.
Process.sleep(10)
Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
end
end
end
ExUnit.run()
# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
raise "Generic Raise"
We try to provide helpful hints to debug the error. We also separate the error from the test results to make the test results easier to read.
ExUnit.start(auto_run: false)
defmodule Assertion do
use ExUnit.Case
test "Exercise" do
try do
Process.flag(:trap_exit, true)
defmodule Server do
def start_link(opts) do
GenServer.start_link(__MODULE__, :green, opts)
end
def init(state) do
{:ok, state}
end
end
{:ok, pid} = Server.start_link([])
GenServer.call(pid, :unhandled_message)
catch
error ->
flunk("""
Your solution threw the following error:
#{inspect(error)}
""")
:exit, {error, {GenServer, message_type, [_pid, message, _timeout]}} ->
flunk("""
GenServer crashed with the following error:
#{inspect(error)}
When it recieved: #{inspect(message)} #{message_type}
Likely you need to define the corresponding handler for #{inspect(message)}.
Ensure you defined a `handle_call/3`, `handle_info/2`, or `handle_cast/2` or appropriate handler function.
def handle_call(:message, _from, state) do
...
end
""")
:exit, error ->
flunk("""
Unhandled exit with the following error:
#{inspect(error)}
""")
after
# all warnings and errors are printed to the previous Kino Frame
# to avoid cluttering the test results display.
Process.sleep(10)
Kino.render(Kino.Markdown.new("### Test Results
<hr/>"))
end
end
end
ExUnit.run()
# Make variables and modules defined in the test available.
# Also allows for exploration using the output of the cell.
# Unfortunately, this results in duplication of warnings.
defmodule Server do
def start_link(opts) do
GenServer.start_link(__MODULE__, :green, opts)
end
def init(state) do
{:ok, state}
end
end