UNC BACS 350

Web Apps with Python/Django

Logo

Hammer Script Test Design Pattern

Demo Code

Data

probe/models.py

```python from django.db import models from django.urls.base import reverse_lazy

class Test(models.Model):

name = models.CharField(max_length=100)
expected = models.TextField(default='Initial Output', null=True)
source = models.TextField(default='none')

def __str__(self):
    return f'{self.name}'

def get_absolute_url(self):
    return reverse_lazy('test_list')

@staticmethod
def create(**kwargs):
    x = Test.objects.get_or_create(name=kwargs.get('name'))[0]
    expected = kwargs.get('expected')
    if expected:
        x.expected = expected
    x.source = kwargs.get('source')
    x.save()

class Result(models.Model):

probe = models.ForeignKey(Test, on_delete=models.CASCADE, editable=False)
date = models.DateTimeField(auto_now=True)
output = models.TextField(default='Not yet run')
passed = models.BooleanField(default=False)

def __str__(self):
    return f'{self.probe.name} - {self.date} - {self.passed}'

def get_absolute_url(self):
    return reverse_lazy('test_list')

@staticmethod
def create(**kwargs):
    c = Result.objects.create(probe=kwargs.get('probe'))
    c.output = kwargs.get('output')
    c.passed = kwargs.get('passed')
    c.save()
    return c

```

Find the Page Probe Tests

probe/probe.py

```python all_probes = {}

def find_tests():

def module_list(directory):
    return [f.replace('.py', '') for f in listdir(directory) if f.startswith('test_')]

def test_functions(module_name):
    module = get_module(module_name)
    functions = getmembers(module, isfunction)
    return [f for f in functions if f[0].startswith('test_')]

global all_probes

for module_name in module_list('test'):
    for f in test_functions(module_name):
        source = '.'.join(['test', module_name, f[0]])
        all_probes[f[0]] = f[1]

```

test/test_system.py

```python def test_system_source(): files = len(recursive_files('.')) return f'System files: {files}'

def test_python_source(): files = len(recursive_files('.', filetype='.py')) return f'Python files: {files}'

```

Execute Page Probe Tests

probe/probe.py

```python all_probes = {}

def execute_probe(probe):

def get_test_function(probe):
    global all_probes
    return all_probes[probe.source]

try:
    output = get_test_function(probe)()
    passed = output == probe.expected
except:
    output = f'Test Failed to execute:  {probe.name}'
    passed = False

return Result.create(probe=probe, output=output, passed=passed)

```

Views

probe/probe_views.py

```python from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.urls import reverse_lazy, reverse from django.views.generic import CreateView, DeleteView, DetailView, ListView, RedirectView, UpdateView

from .models import Result, Test from .probe import approve_result, clear_probe_history, execute_probe, reset_tests, result_list

class TestView(RedirectView): url = reverse_lazy('test_list')

class TestApproveView(RedirectView):

def get_redirect_url(self, **kwargs):
    pk = self.kwargs.get('pk')
    result = Result.objects.get(pk=pk)
    result = approve_result(result)
    return reverse('test_detail', args=[result.probe.pk])

class TestListView(ListView): template_name = 'test_list.html' model = Test

class TestDetailView(DetailView): template_name = 'test_detail.html' model = Test

def get_context_data(self, **kwargs):
    kwargs = super().get_context_data(**kwargs)
    probe = kwargs['object']
    # execute_probe(probe)
    kwargs['results'] = result_list(probe)
    return kwargs

class TestCreateView(LoginRequiredMixin, CreateView): template_name = "test_add.html" model = Test fields = 'all'

def form_valid(self, form):
    form.instance.author_id = 1
    return super().form_valid(form)

class TestResetView(RedirectView):

def get_redirect_url(self, **kwargs):
    pk = self.kwargs.get('pk')
    if pk == 0:
        reset_tests()
        return reverse('test_list')
    else:
        clear_probe_history(pk)
        return reverse('test_detail', args=[pk])

class TestRunView(RedirectView):

def get_redirect_url(self, **kwargs):
    pk = self.kwargs.get('pk')
    if pk == 0:
        for probe in Test.objects.all():
            execute_probe(probe)
        return reverse('test_list')
    else:
        probe = Test.objects.get(pk=pk)
        execute_probe(probe)
        return reverse('test_detail', args=[pk])

class TestUpdateView(LoginRequiredMixin, UpdateView): template_name = "test_edit.html" model = Test fields = 'all'

class TestDeleteView(LoginRequiredMixin, DeleteView): model = Test template_name = 'test_delete.html' success_url = reverse_lazy('test_list')

```

Templates

templates/probe_detail.html

```html
{% extends 'theme.html' %}

{% block content %}

Test Details - {{ object.name }}

Expected Output

{{ object.expected }}

Python Source Code for Test

{{ object.source }}
Test List Edit Test Delete Test Run Test Clear History

Test History

{% for r in results %} {% endfor %}
Date Pass/Fail Text Output
{{ r.date }} {% if r.passed %}PASS{% else %}FAIL{% endif %} {{ r.output }} Approve

{% endblock content %} ```

URL Routes

config/urls.py

```python from hammer.views_probe import TestView

urlpatterns = [ path('', TestView.as_view()), path('test/', include('hammer.urls_probe')), ]

```

probe/probe_urls.py

```python

from django.urls import path from .views_probe import (TestApproveView, TestCreateView, TestDeleteView, TestDetailView, TestListView, TestResetView, TestRunView, TestUpdateView) urlpatterns = [

# Test
path('',                       TestListView.as_view(),      name='test_list'),
path('<int:pk>',               TestDetailView.as_view(),    name='test_detail'),
path('add',                    TestCreateView.as_view(),    name='test_add'),
path('<int:pk>/',              TestUpdateView.as_view(),    name='test_edit'),
path('<int:pk>/delete',        TestDeleteView.as_view(),    name='test_delete'),

# Results
path('<int:pk>/run',           TestRunView.as_view(),       name='test_run'),
path('<int:pk>/approve',       TestApproveView.as_view(),   name='test_approve'),
path('<int:pk>/reset',         TestResetView.as_view(),     name='test_reset'),

]

```