Pruebas Automatizadas Masivas utilizando GitHub Actions y Classroom

Gonzalo Pincheira A. - @gpincheiraa

Agenda

Historia en la pedagogía

Boolean Academia

eDX, Pruebas de Software y MAGIC

Integración Continua y Github Classroom

Octokit y JavaScript

Conclusiones

Mi Historia en la pedagogía

Desde Joven me interesó el trabajo con computadoras y pedagogía.

En 6to de primaria, a los 12 años, fui seleccionado para enseñar a todos los compañeros que se interesaran en aprender a computadores.

A los 13 años hice mi primer programa: Un pokedéx con fotos de emulador de GameBoy editadas en paint, incluidas en un script Visual Basic.

Mi Historia en la pedagogía

En mi adolescencia en el liceo y universidad, acostumbraba realizar clases particulares gratuitas a familiares, amigos y conocidos

Desde el 2014 he participado en comunidades de Software: Más de 15 charlas y Workshops en Vivo

+ 300hrs de pedagogía en el sistema REUF de Estado

2019 junto a Sebastián Jiménez fundamos Boolean Academia. Más de 30 egresados del curso FullStack JavaScript PRO

Boolean Academia

Nace en 2016 como idea de enseñar tecnologías pero con profundo hincapié en material práctico

Primeras consultorías en Desarollo de Software con Angular en 2018

Experiencia online como parte de nuestra metodología enseñando prácticas como Pruebas de Software e Integración Continua

Sabiamos que la experiencia que queriamos construir para los estudiantes era DEMASIADO COSTOSA

eDX, Pruebas de Software y MAGIC

EdX es un proveedor de cursos en línea masivos y abiertos.

Trabajan con las universidades y organizaciones líderes a nivel mundial

Me interesó porque enseñaban Pruebas de Software e Integración Continua

Al cursarlo descubri que no solo enseñaban pruebas, sino que las usaban para revisar los trabajos

eDX, Pruebas de Software y MAGIC

Ahora ya estaba egresado…

… ¿Cómo lo habrán hecho para revisar masivamente todos los trabajos?

eDX, Pruebas de Software y MAGIC

MAGIC: Massive Automated Grading in the Cloud

eDX, Pruebas de Software y MAGIC

MAGIC: Massive Automated Grading in the Cloud

eDX, Pruebas de Software y MAGIC

MAGIC: Massive Automated Grading in the Cloud

eDX, Pruebas de Software y MAGIC

MAGIC: Massive Automated Grading in the Cloud

Integración Continua y Github Classroom

Cómo implementamos?

Que herramienta usamos para dar más fluidez a los laboratorios

Respuestas: Github Classroom y Jest

Jest

Framework de pruebas con una interfaz super fluida en terminal y fácil integración

Github Classroom

Ecosistema para administrar repositorios y administrar tareas que se pueden Auto-Evaluar

Octokit y JavaScript


#!/usr/bin/env node
const {
  getStudentsList,
  getCourseLaboratories,
  writeCourseLabsByStudent,
  checkStudentLaboratoriesResults,
  wait
} = require('../src/main')


Octokit y JavaScript


async function main() {
  const studentsList = await getStudentsList()
  const laboratories = await getCourseLaboratories() || []
  const usernamesList = studentsList
    .map(({ username }) => username)

  let courseLabsResultsMap = {}
  // obtener resultados y obtener objeto, e imprimir objeto en cada iteración (privado)
  for (username of usernamesList) {
    try {
      const studentResults = await checkStudentLaboratoriesResults(username, laboratories.length)

      courseLabsResultsMap = {
        ...courseLabsResultsMap,
        ...studentResults,
      }
      await wait(3)
    } catch(error) {
      console.error(error)
      process.exit(1)
    }
  }

  await writeCourseLabsByStudent(courseLabsResultsMap)
}
main()

Octokit y JavaScript


async function checkStudentLaboratoriesResults(studentUsername, totalLabs) {
  return collectResults(studentUsername, totalLabs)
    .then(results => {
      const { progressDisplay, progressValue } = getCourseCompletedPercentage(results, totalLabs)
      const studentResults = { 
        [studentUsername]: {
          username: studentUsername,
          lastRevision: (new Date()).getTime(),
          successTasks: progressDisplay,
          completedPercentage: progressValue,
          results
        }
      }
      return prettyCheckLabsMessage(studentUsername, studentResults)
    })
}

Octokit y JavaScript


async function collectResults(studentUsername, totalLabs) {
  // https://github.com/booleancl/lab-19-DiegoMoralesC/actions/workflows/classroom.yml/badge.svg
  // https://docs.github.com/en/rest/reference/actions#list-jobs-for-a-workflow-run
  const studentLabsRequesPaths = getStudentLabsRequestPaths(
    studentUsername,
    totalLabs
  )
  const results = []
  
  for (labRequest of studentLabsRequesPaths) {
    console.log(`Processing ${labRequest.labPath} for student ${studentUsername}`)

    try {
      const workflowsResponse = await octokit.request(labRequest.url)
      const [ lastWorkflow ] = workflowsResponse.data.workflow_runs
      
      if (!lastWorkflow) {
        const message = `Lab ${labRequest.labPath} without Autograding`
        results.push({
          id: labRequest.labPath,
          status: 'completed',
          conclusion: 'success-with-failure',
          message
        })
        continue
      }

      const jobsResponse = await octokit.request(`GET /repos/booleancl/${labRequest.labPath}/actions/runs/${lastWorkflow.id}/jobs`)
      const [ lastPipeline ] = jobsResponse.data.jobs
        .map(({ status, conclusion, html_url}) => ({
          id: labRequest.labPath,
          status,
          conclusion,
          html_url,
          message: `Lab ${labRequest.labPath} ${conclusion === 'success'? 'OK' : 'FAILED'}`
        }))

      results.push(lastPipeline)
    } catch (error) {
      results.push({
        id: labRequest.labPath,
        status: 'failure',
        conclusion: 'failure',
        message: `Lab ${labRequest.labPath} FAILED: ${error.message}` 
      })
    }
    clearLastLine()
  }

  return Promise.resolve(results)
}

Octokit y JavaScript


function getStudentLabsRequestPaths(studentUsername, totalLabs) {
  const labPaths = []

  for (let labIndex = 1; labIndex <= totalLabs; labIndex++) {
    const labId = labIndex < 10 ? `0${labIndex}` : labIndex
    const labPath =  `${PREFIX_LAB}-${labId}-${studentUsername}`
    const url =  `GET /repos/booleancl/${labPath}/actions/runs`

    labPaths.push({ labPath, url })
  }
  return labPaths
}

Conclusiones y Ejemplo en vivo

Gonzalo Pincheira A. - @gpincheiraa