Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
**.mod
**build
**build-cmake
pfunit
./pfunit
.vscode
44 changes: 35 additions & 9 deletions docker/Dockerfile.ep-3
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,52 @@ COPY --chown=vscode episodes/3-writing-your-first-unit-test /home/vscode/3-writi

WORKDIR /home/vscode/3-writing-your-first-unit-test/challenge

# Rebuild without pFUnit
# Build with CMake
RUN cmake -B build-std && \
cmake --build build-std

# Test without pFUnit
# Test with CMake
RUN ctest --test-dir build-std --output-on-failure

# Build with Make
RUN FC=/usr/bin/f95 make tests

# Test with Make
RUN ./build/standard_fortran_tests

# Test the Solution code

# Cleanup directories
RUN rm -rf build-std build-pf
# Test the Solution code

# Copy solution code
RUN cp ../solution/test_temp_conversions.f90 test/standard_fortran/ && \
cp ../solution/test_temp_conversions.pf test/pfunit/
RUN cp ../solution/standard_fortran/test_temp_conversions.f90 test/standard_fortran/test_temp_conversions.f90 && \
cp ../solution/pfunit/* test/pfunit/

# Build with make

# Uncomment pfunit comments
RUN sed -i -E 's/tests: standard_fortran_tests \#\| pfunit_tests/tests: standard_fortran_tests \| pfunit_tests/g' Makefile
RUN sed -i -E 's/clean: \#clean_pfunit/clean: clean_pfunit/g' Makefile

# Clean up anything from the previous builds
RUN make clean
RUN rm -rf build-std

# Build
RUN FC=/usr/bin/f95 make tests

# Run pFUnit tests
RUN ./test/pfunit/tests

# Run standard_fortran tests
RUN ./build/standard_fortran_tests

# Run cleanup again
RUN make clean


RUN ls test/pfunit/
# Build with CMake

# Rebuild without pFUnit
# Build without pFUnit
RUN cmake -B build-std && \
cmake --build build-std

Expand Down
70 changes: 70 additions & 0 deletions episodes/3-writing-your-first-unit-test/challenge/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Top level variables
ROOT_DIR = $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
FC ?= gfortran

#------------------------------------#
# Targets for compiling src #
#------------------------------------#
SRC_DIR = $(ROOT_DIR)/src
BUILD_DIR = $(ROOT_DIR)/build

# List source files
SRC_FILES = temp_conversions.f90

# Map src files to .o files
SRC_OBJS = $(patsubst %.f90, $(BUILD_DIR)/%.o, $(SRC_FILES))

# Build src .o files
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.f90 | $(BUILD_DIR)
$(FC) -c -J $(BUILD_DIR) -o $@ $<

# Ensure the build dir exists
$(BUILD_DIR):
mkdir -p $@

#------------------------------------#
# Targets for testing #
#------------------------------------#
TEST_DIR = $(ROOT_DIR)/test
STANDARD_FORTRAN_TEST_DIR = $(TEST_DIR)/standard_fortran
STANDARD_FORTRAN_TEST_EXE = $(BUILD_DIR)/standard_fortran_tests
PFUNIT_TEST_DIR = $(TEST_DIR)/pfunit

# Include make command from test Makefiles
standard_fortran_tests: $(SRC_OBJS)
@echo "Building standard_fortran test suite..."
@$(MAKE) -C $(STANDARD_FORTRAN_TEST_DIR) tests

pfunit_tests: $(SRC_OBJS)
@echo "Building pFUnit test suite..."
@$(MAKE) -C $(PFUNIT_TEST_DIR) tests

# Uncomment here...
tests: standard_fortran_tests #| pfunit_tests

#------------------------------------#
# Targets for cleaning #
#------------------------------------#
# Define target for cleaning objects built by pfunit
clean_pfunit:
$(MAKE) -C $(PFUNIT_TEST_DIR) clean

# Define target for cleaning all built objects
# Uncomment here...
clean: #clean_pfunit
rm -rf build

.PHONY: clean

#--------------------------------------#
# Export variables for child Makefiles #
#--------------------------------------#
# Export variables for the other Makefiles to use
export BUILD_DIR
export ROOT_DIR
export SRC_OBJS
export SRC_DIR
export FC
export STANDARD_FORTRAN_TEST_DIR
export STANDARD_FORTRAN_TEST_EXE
export PFUNIT_TEST_DIR
48 changes: 37 additions & 11 deletions episodes/3-writing-your-first-unit-test/challenge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ as indicated in `test_temp_conversions.f90`.

### Part 2 - Convert tests to use pFUnit

#### Writing the test

Convert your tests from [Part 1](#part-1---test-with-standard-fortran), to use
[pFUnit](https://github.com/Goddard-Fortran-Ecosystem/pFUnit).

Expand All @@ -48,16 +50,40 @@ the pFUnit test you must write.
> to pFUnit. If your version of test_temp_conversions.f90, produced in Part 1, is significantly
> different, You may prefer to use a different structure to the one provided in the template.

To build and run your pFUnit test(s) you must add the pFUnit lib to the `CMAKE_PREFIX_PATH`
when building via the following command.

```bash
cmake -B build -DCMAKE_PREFIX_PATH=/path/to/pfunit/install
cmake --build build
ctest --test-dir build --output-on-failure
```

If your test does not get built, ensure you have added it to the list of tests (`test_src`)
in [test/pfunit/CMakeLists.txt](./test/pfunit/CMakeLists.txt)
#### Building the test

- **i. Build your new test(s) with Make** - A top level [Makefile](./Makefile) has already been provided to build the
src objects and the standard Fortran tests, via [test/standard_fortran/Makefile](./test/standard_fortran/Makefile).
Add a new Makefile to the [test/pfunit/](./test/pfunit/) dir which will build your new pFUnit test(s). Note that
the top level [Makefile](./Makefile) is already setup to work with this new Makefile (look for lines which look
like **"# Uncomment here..."**). Once you have added this Makefile, you should be able to build and run your tests
via the following command.

```sh
make tests
./test/pfunit/tests
```

If you are not using the devcontainer, you will likely need to specify the path to your pFUnit include dir. You can do
this by passing an environment variable like so.

```sh
PFUNIT_INCLUDE_DIR=/path/to/pfunit/include make tests
```

The Fortran compiler will default to **gfortran**. If you wish to use a different compiler, set the env var, **FC**.

- **ii. Build your new test(s) with CMake** - A top level [CMakeLists.txt](./CMakeLists.txt) has already been provided to
build the src objects and the standard Fortran tests, via
[test/standard_fortran/CMakeLists.txt](./test/standard_fortran/CMakeLists.txt). Add a new CMakeLists.txt to the
[test/pfunit/](./test/pfunit/) dir which will build your new pFUnit test(s). Note that the top level
[CMakeLists.txt](./CMakeLists.txt) is already setup to work with this new CMakeLists.txt, all you need to do is
add the pFUnit lib to the `CMAKE_PREFIX_PATH` when building i.e...

```bash
cmake -B build -DCMAKE_PREFIX_PATH=/path/to/pfunit/install
cmake --build build
ctest --test-dir build --output-on-failure
```

> If you are using the devcontainer, there is an installation of pFUnit at /home/vscode/pfunit/build/installed
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
*.f90
*.F90
*.inc
*.mod
*.o
tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
TEST_FILES = test_temp_conversions.f90

TEST_OBJS = $(patsubst %.f90, $(BUILD_DIR)/%.o, $(TEST_FILES))

$(BUILD_DIR)/%.o: $(STANDARD_FORTRAN_TEST_DIR)/%.f90 | $(BUILD_DIR)
$(FC) -c -J $(BUILD_DIR) -o $@ $<

# Build test executable
$(STANDARD_FORTRAN_TEST_EXE): $(TEST_OBJS) $(SRC_OBJS)
$(FC) -o $@ $?

tests: $(STANDARD_FORTRAN_TEST_EXE)
35 changes: 20 additions & 15 deletions episodes/3-writing-your-first-unit-test/solution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Part 1 - Test with Standard Fortran

The solution is provided in the form of a single test file [test_temp_conversions.f90](./test_temp_conversions.f90)
The solution is provided in the form of a single test file [standard_fortran/test_temp_conversions.f90](./standard_fortran/test_temp_conversions.f90)
which replaces the file of the same name provided in the
[challenge/test/standard_fortran](../challenge/test/standard_fortran/) directory.

Expand All @@ -12,8 +12,8 @@ There are several key aspects within the solution that are important to implemen

#### Isolated test subroutine

Each test subroutine in [test_temp_conversions.f90](./test_temp_conversions.f90) calls and tests only one src
function. For example, `test_fahrenheit_to_celsius` calls and tests `fahrenheit_to_celsius` and
Each test subroutine in [standard_fortran/test_temp_conversions.f90](./standard_fortran/test_temp_conversions.f90) calls and tests
only one src function. For example, `test_fahrenheit_to_celsius` calls and tests `fahrenheit_to_celsius` and
`test_celsius_to_kelvin` calls and tests `celsius_to_kelvin`.

This is important as, in the event of a test failing it will be clear which src function is the cause of the failure.
Expand All @@ -23,15 +23,15 @@ investigation would be required.

#### Parameterised tests

Each test subroutine in [test_temp_conversions.f90](./test_temp_conversions.f90) takes in an `input` and an `expected_output`.
This allows the same test subroutine to be called with multiple different inputs to test several scenarios with the same test
code. Therefore, we are able to test edge cases and other key scenarios more easily.
Each test subroutine in [standard_fortran/test_temp_conversions.f90](./standard_fortran/test_temp_conversions.f90) takes in an
`input` and an `expected_output`. This allows the same test subroutine to be called with multiple different inputs to test several
scenarios with the same test code. Therefore, we are able to test edge cases and other key scenarios more easily.

#### Clear failure message

Each test subroutine in [test_temp_conversions.f90](./test_temp_conversions.f90) populates `failure_message` with a clear
message that aims to make it as easy as possible to diagnose a failing test. Importantly, the `failure_message` includes the
`input`, the `expected_output` and the actual value which we have compared to the `expected_output`.
Each test subroutine in [standard_fortran/test_temp_conversions.f90](./standard_fortran/test_temp_conversions.f90) populates
`failure_message` with a clear message that aims to make it as easy as possible to diagnose a failing test. Importantly, the
`failure_message` includes the `input`, the `expected_output` and the actual value which we have compared to the `expected_output`.

#### Comparing floats within an appropriate tolerance

Expand All @@ -42,16 +42,21 @@ should be as small as possible whilst still making sense with the code we are te

## Part 2 - Convert tests to use pFUnit

The solution is provided in the form of a single test file [test_temp_conversions.pf](./test_temp_conversions.pf)
which replaces the file of the same name provided in the [challenge/test/pfunit](../challenge/test/pfunit/) directory.
### Writing the test

### Key points

All the key points mention within [Part 1](#key-points) are upheld in this solution. Any additional points are
detailed below.
The solution is provided in the form of a single test file [pfunit/test_temp_conversions.pf](./pfunit/test_temp_conversions.pf)
which replaces the file of the same name provided in the [challenge/test/pfunit](../challenge/test/pfunit/) directory. All the key
points mention within [Part 1](#key-points) are upheld in this solution. Any additional points are detailed below.

#### Add a description for each test

For each set of parameters in [test_temp_conversions.pf](./test_temp_conversions.pf) there is a description provided
which aims to make clear what is being tested. This can be useful when thinking about test coverage of edge cases.
It can also help in the event of a test failure to hint at what the problem may be.

### Building the test

- **i. Build your new test(s) with Make**
- A solution is provided in [pfunit/Makefile](./pfunit/Makefile).
- **ii. Build your new test(s) with CMake**
- A solution is provided in [pfunit/CMakeLists.txt](./pfunit/CMakeLists.txt).
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
message(STATUS "Using pFUnit")

set(TEST_DIR "${PROJECT_SOURCE_DIR}/test/pfunit")

# Create library for src code
add_library (sut STATIC ${PROJ_SRC_FILES})

# List all test files
set(test_srcs "test_temp_conversions.pf")

add_pfunit_ctest (pfunit_test_temp_conversions_exec
TEST_SOURCES ${test_srcs}
TEST_SOURCES "test_temp_conversions.pf"
LINK_LIBRARIES sut # your application library
)

24 changes: 24 additions & 0 deletions episodes/3-writing-your-first-unit-test/solution/pfunit/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
PFUNIT_INCLUDE_DIR ?= /home/vscode/pfunit/build/installed/PFUNIT-4.12/include

# Don't try to include if we're cleaning as this doesn't depend on pFUnit
ifneq ($(MAKECMDGOALS),clean)
include $(PFUNIT_INCLUDE_DIR)/PFUNIT.mk
TEST_FLAGS = -I$(BUILD_DIR) $(PFUNIT_EXTRA_FFLAGS)
endif

# Define variables to be picked up by make_pfunit_test
tests_TESTS = \
test_temp_conversions.pf
tests_OTHER_SOURCES = $(filter-out $(BUILD_DIR)/main.o, $(SRC_OBJS))
tests_OTHER_LIBRARIES = $(TEST_FLAGS)

# Triggers pre-processing and defines rule for building test executable
$(eval $(call make_pfunit_test,tests))

# Converts pre-processed test files into objects ready for building of the executable
%.o: %.F90
$(FC) -c $(TEST_FLAGS) $<

# Cleanup generated files
clean:
\rm -f *.o *.mod *.F90 *.inc tests