From a213ef4d31794bcae3da55253359bfa5f646f27b Mon Sep 17 00:00:00 2001 From: acampbel Date: Mon, 9 Feb 2026 22:20:52 -0500 Subject: [PATCH 1/2] Test updates, including figure screenhsots in the app test. Also small workshop updates --- WorkshopGuide.m | 6 +-- WorkshopGuide.md | 34 ++++++------ tests/tTMDDApp.m | 117 +++++++++++++++++++++++++--------------- tests/tgenerateSimFun.m | 18 +++++-- 4 files changed, 110 insertions(+), 65 deletions(-) diff --git a/WorkshopGuide.m b/WorkshopGuide.m index a6a1eda..28d55fc 100644 --- a/WorkshopGuide.m +++ b/WorkshopGuide.m @@ -508,7 +508,7 @@ %[text] ![](text:image:5321) %[text] %[text] *Note*: If you want to manually update the links, you will need to make the following changes to all of the links in the [`README.md`](file:.\README.md) file: -%[text] - replace the "mathworks" GitHub username with your GitHub username +%[text] - replace the "simkaryote" GitHub username with your GitHub username %[text] - if you renamed the repository during the fork process in [Part 1.1](internal:H_DB2C80D4), replace "MATLAB-SimBiology-DevOps-Workflow-Example" repository name with your GitHub repository name \ %[text] %[text] Your badges will now be up-to-date and point to your personal repository once you commit and push your changes. @@ -528,7 +528,7 @@ %[text] %[text] You have now committed your changes. %[text] -%[text] Usually, the next step is to push our changes back to GitHub. This is really easy and straightforward when using Git from a desktop application (e.g., the desktop version of MATLAB). However, since we are pushing our changes from an online application (MATLAB Online), GitHub requires a personal access token to allow MATLAB Online to make changes to your GitHub repository. +%[text] Usually, the next step is to push our changes back to GitHub. This is really easy and straightforward when using Git from a desktop application (e.g., the desktop version of MATLAB). If we are pushing our changes from an online application like MATLAB Online, or if you don't have the Git Credential Manager on your machine, GitHub requires a personal access token to allow MATLAB Online to make changes to your GitHub repository. %[text] %[text] %% @@ -546,7 +546,7 @@ %[text] %[text] When you install Git on your desktop, it comes with the Git Credential Manager. The Git Credential Manager will accept a username/password combination, it will securely get and store a personal access token for you, and it will make it very easy to push and pull changes without manually generating a personal access token. %[text] -%[text] For this MATLAB Online session, you will need to manually create a personal access token to give MATLAB Online permission to push changes to your GitHub repository. GitHub provides directions for [how to create a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), but this part of the workshop will give you a step-by-step guide to creating your own personal access token. +%[text] For a MATLAB Online session or other situations, you will need to manually create a personal access token to give MATLAB Online permission to push changes to your GitHub repository. GitHub provides directions for [how to create a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), but this part of the workshop will give you a step-by-step guide to creating your own personal access token. %[text] %[text] %[text] %[text:anchor:H_7AE18FA9] **On GitHub, press your profile picture (top right of the page) and select "Settings"** diff --git a/WorkshopGuide.md b/WorkshopGuide.md index 117005f..76637ba 100644 --- a/WorkshopGuide.md +++ b/WorkshopGuide.md @@ -19,19 +19,19 @@ In this workshop, you will: ## Table of Contents  [Workshop Requirements](#H_34C2FB57) - [Part 1: Getting the workshop files and configuring GitHub for automated testing and results publishing](#TMP_569f) + [Part 1: Getting the workshop files and configuring GitHub for automated testing and results publishing](#TMP_347b) - [Part 2: Generating your first tests](#TMP_6930) + [Part 2: Generating your first tests](#TMP_8fa0) - [Part 3: Finding existing tests and measuring coverage](#TMP_50f4) + [Part 3: Finding existing tests and measuring coverage](#TMP_73c3) - [Part 4: Updating badges, committing our changes, and pushing to GitHub](#TMP_5540) + [Part 4: Updating badges, committing our changes, and pushing to GitHub](#TMP_5f22) - [Part 5: Create a pull request, watch GitHub Actions automatically test your changes and publish results](#TMP_3054) + [Part 5: Create a pull request, watch GitHub Actions automatically test your changes and publish results](#TMP_344c) - [Part 6: Compile the App in the CI workflow and download the artifact](#TMP_1fc1) + [Part 6: Compile the App in the CI workflow and download the artifact](#TMP_431c) - [Workshop wrap\-up and additional information](#TMP_225a) + [Workshop wrap\-up and additional information](#TMP_657c) @@ -56,7 +56,7 @@ The following steps cover all of the things you will need to successfully comple - The workshop leverages the free repository and CI capabilities offered by GitHub and GitHub Actions - Go to: [**https://github.com/signup**](https://github.com/signup) - + # Part 1: Getting the workshop files and configuring GitHub for automated testing and results publishing @@ -308,7 +308,7 @@ Click on the 'Run App' shortcut to start the app in MATLAB: ![image_20.png](WorkshopGuide_media/image_20.png) - + # Part 2: Generating your first tests @@ -529,7 +529,7 @@ Congratulations! You just created multiple tests for your MATLAB code! It was easier than you thought, right? - + # Part 3: Finding existing tests and measuring coverage @@ -868,7 +868,7 @@ It looks like we've achieved full statement coverage for [`generateSimFun`](./co ![image_65.png](WorkshopGuide_media/image_65.png) - + # Part 4: Updating badges, committing our changes, and pushing to GitHub @@ -904,7 +904,7 @@ To make this easy, the workshop provides a useful project shortcut that will aut *Note*: If you want to manually update the links, you will need to make the following changes to all of the links in the [`README.md`](./README.md) file: -- replace the "mathworks" GitHub username with your GitHub username +- replace the "simkaryote" GitHub username with your GitHub username - if you renamed the repository during the fork process in [Part 1.1](#H_DB2C80D4), replace "MATLAB\-SimBiology\-DevOps\-Workflow\-Example" repository name with your GitHub repository name @@ -935,7 +935,7 @@ Now that you've added tests, and updated your badges, it's time to commit your c You have now committed your changes. -Usually, the next step is to push our changes back to GitHub. This is really easy and straightforward when using Git from a desktop application (e.g., the desktop version of MATLAB). However, since we are pushing our changes from an online application (MATLAB Online), GitHub requires a personal access token to allow MATLAB Online to make changes to your GitHub repository. +Usually, the next step is to push our changes back to GitHub. This is really easy and straightforward when using Git from a desktop application (e.g., the desktop version of MATLAB). If we are pushing our changes from an online application like MATLAB Online, or if you don't have the Git Credential Manager on your machine, GitHub requires a personal access token to allow MATLAB Online to make changes to your GitHub repository. @@ -963,7 +963,7 @@ Personal access tokens are meant to be treated like passwords: When you install Git on your desktop, it comes with the Git Credential Manager. The Git Credential Manager will accept a username/password combination, it will securely get and store a personal access token for you, and it will make it very easy to push and pull changes without manually generating a personal access token. -For this MATLAB Online session, you will need to manually create a personal access token to give MATLAB Online permission to push changes to your GitHub repository. GitHub provides directions for [how to create a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), but this part of the workshop will give you a step\-by\-step guide to creating your own personal access token. +For a MATLAB Online session or other situations, you will need to manually create a personal access token to give MATLAB Online permission to push changes to your GitHub repository. GitHub provides directions for [how to create a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), but this part of the workshop will give you a step\-by\-step guide to creating your own personal access token. @@ -1088,7 +1088,7 @@ At this point, all of your changes will be pushed to GitHub. ![image_85.png](WorkshopGuide_media/image_85.png) - + # Part 5: Create a pull request, watch GitHub Actions automatically test your changes and publish results @@ -1249,7 +1249,7 @@ The code coverage report looks like this: Now anyone that visits your repository can immediately see the quality of your code, explore your test and code coverage results, and will have more confidence in the code you are writing! - + # Part 6: Compile the App in the CI workflow and download the artifact @@ -1350,7 +1350,7 @@ Once the `Deploy/ Release (main)` job is done, a new artifact is available at th You can now download the CTF file and upload it to your Web App Server! - + # Workshop wrap\-up and additional information diff --git a/tests/tTMDDApp.m b/tests/tTMDDApp.m index 56cc3f9..5b6ee0b 100644 --- a/tests/tTMDDApp.m +++ b/tests/tTMDDApp.m @@ -4,9 +4,25 @@ end methods (TestMethodSetup) + function suppressSnapshotWarning(testCase) + import matlab.unittest.fixtures.SuppressedWarningsFixture; + testCase.applyFixture(SuppressedWarningsFixture(... + "MATLAB:appdesigner:appdesigner:SaveObjWarning")); + end + function launchApp(testCase) + import matlab.unittest.diagnostics.ScreenshotDiagnostic; + testCase.App = TMDDApp; - testCase.addTeardown(@delete,testCase.App) + testCase.addTeardown(@delete,testCase.App); + + % Take a snapshot at app open and close + snapshot(testCase, "App open"); + testCase.addTeardown(@snapshot,testCase, "App close") + + + % Log a screenshot for any failure + testCase.onFailure(ScreenshotDiagnostic); end end @@ -15,68 +31,85 @@ function launchApp(testCase) function testStartup(testCase) % Check plot update - testCase.verifyNotEmpty(testCase.App.ROViewObj.lhRO.XData, "x values for RO are empty"); - testCase.verifyNotEmpty(testCase.App.ROViewObj.lhRO.YData, "y values for RO are empty"); - testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhDrug.XData, "x values for Drug are empty"); - testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhDrug.YData, "y values for Drug are empty"); - testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhReceptor.XData, "x values for Receptor are empty"); - testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhReceptor.YData, "y values for Receptor are empty"); - testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhComplex.XData, "x values for Complex are empty"); - testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhComplex.YData, "y values for Complex are empty"); + testCase.verifyNotEmpty(testCase.App.ROViewObj.lhRO.XData, "x values for RO should not be empty"); + testCase.verifyNotEmpty(testCase.App.ROViewObj.lhRO.YData, "y values for RO should not be empty"); + testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhDrug.XData, "x values for Drug should not be empty"); + testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhDrug.YData, "y values for Drug should not be empty"); + testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhReceptor.XData, "x values for Receptor should not be empty"); + testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhReceptor.YData, "y values for Receptor should not be empty"); + testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhComplex.XData, "x values for Complex should not be empty"); + testCase.verifyNotEmpty(testCase.App.ConcViewObj.lhComplex.YData, "y values for Complex should not be empty"); % Check NCA table - testCase.verifyNotEmpty(testCase.App.NCAViewObj.NCAtable.Data,"NCA table is empty"); + testCase.verifyNotEmpty(testCase.App.NCAViewObj.NCAtable.Data,"NCA table should not be empty"); end % testStartup function testChangeDosingAmountManualUpdate(testCase) - % Deactivate automatic plot update - testCase.App.AutomaticUpdateSwitch.Value = "Off"; + app = testCase.App; - oldlhRO_XData = testCase.App.ROViewObj.lhRO.XData; - oldlhRO_YData = testCase.App.ROViewObj.lhRO.YData; - oldlhDrug_XData = testCase.App.ConcViewObj.lhDrug.XData; - oldlhDrug_YData = testCase.App.ConcViewObj.lhDrug.YData; - oldlhReceptor_XData = testCase.App.ConcViewObj.lhReceptor.XData; - oldlhReceptor_YData = testCase.App.ConcViewObj.lhReceptor.YData; - oldlhComplex_XData = testCase.App.ConcViewObj.lhComplex.XData; - oldlhComplex_YData = testCase.App.ConcViewObj.lhComplex.YData; + % Deactivate automatic plot update + testCase.choose(testCase.App.AutomaticUpdateSwitch,"Off"); + + + oldlhRO_XData = app.ROViewObj.lhRO.XData; + oldlhRO_YData = app.ROViewObj.lhRO.YData; + oldlhDrug_XData = app.ConcViewObj.lhDrug.XData; + oldlhDrug_YData = app.ConcViewObj.lhDrug.YData; + oldlhReceptor_XData = app.ConcViewObj.lhReceptor.XData; + oldlhReceptor_YData = app.ConcViewObj.lhReceptor.YData; + oldlhComplex_XData = app.ConcViewObj.lhComplex.XData; + oldlhComplex_YData = app.ConcViewObj.lhComplex.YData; % Drag slider - testCase.drag(testCase.App.DosingAmountSlider,100,200); % requires display + testCase.drag(app.DosingAmountSlider,100,200); % requires display + snapshot(testCase, "Post dosing adjustment"); % Check plot update - testCase.verifyEqual(oldlhRO_XData, testCase.App.ROViewObj.lhRO.XData, "x values for RO were updated"); - testCase.verifyEqual(oldlhRO_YData, testCase.App.ROViewObj.lhRO.YData, "y values for RO were updated"); - testCase.verifyEqual(oldlhDrug_XData, testCase.App.ConcViewObj.lhDrug.XData, "x values for Drug were updated"); - testCase.verifyEqual(oldlhDrug_YData, testCase.App.ConcViewObj.lhDrug.YData, "y values for Drug were updated"); - testCase.verifyEqual(oldlhReceptor_XData, testCase.App.ConcViewObj.lhReceptor.XData, "x values for Receptor were updated"); - testCase.verifyEqual(oldlhReceptor_YData, testCase.App.ConcViewObj.lhReceptor.YData, "y values for Receptor were updated"); - testCase.verifyEqual(oldlhComplex_XData, testCase.App.ConcViewObj.lhComplex.XData, "x values for Complex were updated"); - testCase.verifyEqual(oldlhComplex_YData, testCase.App.ConcViewObj.lhComplex.YData, "y values for Complex were updated"); - - % Check that lamp is set to false - testCase.verifyTrue(testCase.App.LampViewObj.IsOn); + testCase.verifyEqual(oldlhRO_XData, app.ROViewObj.lhRO.XData, "x values for RO should not be updated"); + testCase.verifyEqual(oldlhRO_YData, app.ROViewObj.lhRO.YData, "y values for RO should not be updated"); + testCase.verifyEqual(oldlhDrug_XData, app.ConcViewObj.lhDrug.XData, "x values for Drug should not be updated"); + testCase.verifyEqual(oldlhDrug_YData, app.ConcViewObj.lhDrug.YData, "y values for Drug should not be updated"); + testCase.verifyEqual(oldlhReceptor_XData, app.ConcViewObj.lhReceptor.XData, "x values for Receptor should not be updated"); + testCase.verifyEqual(oldlhReceptor_YData, app.ConcViewObj.lhReceptor.YData, "y values for Receptor should not be updated"); + testCase.verifyEqual(oldlhComplex_XData, app.ConcViewObj.lhComplex.XData, "x values for Complex should not be updated"); + testCase.verifyEqual(oldlhComplex_YData, app.ConcViewObj.lhComplex.YData, "y values for Complex should not be updated"); + + % Check that lamp is on + testCase.verifyTrue(app.LampViewObj.IsOn, "The lamp should be on"); % Update app to run simulation manually - testCase.App.updateApp(); + testCase.press(app.SimulateButton) + snapshot(testCase, "Post simulation"); % Check plot update - testCase.verifyNotEqual(oldlhRO_XData, testCase.App.ROViewObj.lhRO.XData, "x values for RO not updated"); - testCase.verifyNotEqual(oldlhRO_YData, testCase.App.ROViewObj.lhRO.YData, "y values for RO not updated"); - testCase.verifyNotEqual(oldlhDrug_XData, testCase.App.ConcViewObj.lhDrug.XData, "x values for Drug not updated"); - testCase.verifyNotEqual(oldlhDrug_YData, testCase.App.ConcViewObj.lhDrug.YData, "y values for Drug not updated"); - testCase.verifyNotEqual(oldlhReceptor_XData, testCase.App.ConcViewObj.lhReceptor.XData, "x values for Receptor not updated"); - testCase.verifyNotEqual(oldlhReceptor_YData, testCase.App.ConcViewObj.lhReceptor.YData, "y values for Receptor not updated"); - testCase.verifyNotEqual(oldlhComplex_XData, testCase.App.ConcViewObj.lhComplex.XData, "x values for Complex not updated"); - testCase.verifyNotEqual(oldlhComplex_YData, testCase.App.ConcViewObj.lhComplex.YData, "y values for Complex not updated"); + testCase.verifyNotEqual(oldlhRO_XData, app.ROViewObj.lhRO.XData, "x values for RO should update"); + testCase.verifyNotEqual(oldlhRO_YData, app.ROViewObj.lhRO.YData, "y values for RO should update"); + testCase.verifyNotEqual(oldlhDrug_XData, app.ConcViewObj.lhDrug.XData, "x values for Drug should update"); + testCase.verifyNotEqual(oldlhDrug_YData, app.ConcViewObj.lhDrug.YData, "y values for Drug should update"); + testCase.verifyNotEqual(oldlhReceptor_XData, app.ConcViewObj.lhReceptor.XData, "x values for Receptor should update"); + testCase.verifyNotEqual(oldlhReceptor_YData, app.ConcViewObj.lhReceptor.YData, "y values for Receptor should update"); + testCase.verifyNotEqual(oldlhComplex_XData, app.ConcViewObj.lhComplex.XData, "x values for Complex should update"); + testCase.verifyNotEqual(oldlhComplex_YData, app.ConcViewObj.lhComplex.YData, "y values for Complex should update"); % Check that lamp is set to false - testCase.verifyFalse(testCase.App.LampViewObj.IsOn); + testCase.verifyFalse(app.LampViewObj.IsOn, "The lamp should be off"); end % testChangeDosingAmountManualUpdate end + methods(Access=private) + function snapshot(testCase, diag) + arguments + testCase (1,1) matlab.uitest.TestCase + diag matlab.unittest.diagnostics.Diagnostic + end + import matlab.unittest.diagnostics.FigureDiagnostic; + + testCase.log("Terse", [diag(:)', FigureDiagnostic(testCase.App.TMDDsimUIFigure)]); + end + end + end \ No newline at end of file diff --git a/tests/tgenerateSimFun.m b/tests/tgenerateSimFun.m index b67d056..a26ed37 100644 --- a/tests/tgenerateSimFun.m +++ b/tests/tgenerateSimFun.m @@ -16,18 +16,26 @@ methods (Test) function testMATfileCreation(testCase) + import matlab.unittest.constraints.IsFile; + % Verify that the MAT file is created - testCase.verifyTrue(isfile(testCase.MATfilefullpath), 'MAT file was not created.'); + testCase.verifyThat(testCase.MATfilefullpath, IsFile, ... + 'MAT file was not created.'); end function testSimFunctionCreation(testCase, fieldName) + import matlab.unittest.constraints.HasField; + % Verify that the MAT file contains required fields - testCase.verifyTrue(isfield(testCase.LoadedData, fieldName), fieldName + " was not saved in " + testCase.MATfilefullpath); + testCase.verifyThat(testCase.LoadedData, HasField(fieldName), ... + fieldName + " should be saved in " + testCase.MATfilefullpath); end function testSimFunctionAcceleration(testCase) % Verify that the SimFunction is accelerated - testCase.verifyTrue(testCase.LoadedData.simFun.isAccelerated, "simFun was not accelerated."); + simFun = testCase.LoadedData.simFun; + testCase.verifyReturnsTrue(@()simFun.isAccelerated, ... + "simFun was not accelerated."); end end @@ -35,8 +43,12 @@ function testSimFunctionAcceleration(testCase) methods (TestClassSetup) function classSetup(testCase, MATfilename) + import matlab.unittest.fixtures.WorkingFolderFixture; % Set up shared state for all tests. + % Generate the simulation function in a working folder + testCase.applyFixture(WorkingFolderFixture); + % Test if the simulation function is created successfully if isempty(MATfilename) testCase.MATfilefullpath = generateSimFun(); From 71527ad224944f32b5aa0250579755d7f42c6da5 Mon Sep 17 00:00:00 2001 From: Andy Campbell <47190204+acampbel@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:32:30 -0500 Subject: [PATCH 2/2] Add deploy step --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f92018..96432e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: - name: Run buildtool uses: matlab-actions/run-build@v2 with: - tasks: test # change to compile + tasks: compile # change to compile # Configure GitHub Pages to accept your artifact uploads - name: Setup Pages @@ -113,12 +113,12 @@ jobs: with: path: results # Upload results - # # Upload compiled CTF file to deploy Web App - # - name: Upload CTF file - # uses: actions/upload-artifact@v6 - # with: - # name: WebApp_CTF - # path: WebAppArchive/*.ctf + # Upload compiled CTF file to deploy Web App + - name: Upload CTF file + uses: actions/upload-artifact@v6 + with: + name: WebApp_CTF + path: WebAppArchive/*.ctf # Publish reports to GitHub Pages so they can be viewed in a browser - name: Deploy to GitHub Pages